Projeto Towel: AutoFiltro em JTable

Conheçam a classe do Auto-Filtro em JTable, do projeto Towel.
Funciona exatamente igual ao AutoFiltro do Excel, porém, também suporta filtro por wildcards (estilo os do DOS).

Dêem uma olhada.

Pena que vi isso só agora precisei disso a um tempo e acabei criando o meu.

Mas o meu TableModel, TableHeader e RowFilter se baseia em Reflection.

[quote=ViniGodoy]Depois de ver muita gente pedindo aqui no GUJ, decidi postar nossa classe de AutoFiltro para JTable. Funciona exatamente igual ao AutoFiltro do Excel, porém, também suporta filtro por wildcards (estilo os do DOS).

Também anexei um TableModel muito mais inteligente que o DefaultTableModel. Ele se baseia num enum de colunas e já tem todas as funcionalidades que esperamos de um model (ele é muito próximo de um ArrayList).

Dêem uma olhada. Aí tem também um exemplo, que escrevi para mostrar para um aluno meu como se fazia para usar.[/quote]
Me desculpe por voltar nesse assunto mas eu peguei seu código e não consegui colocar ele na minha tabela.

Tenho um AbstractTabelModel com os seguintes tipos de colunas:

[code]public class FinanceiroTableModel extends AbstractTableModel implements TableModelListener {

public static String[] TOOLTIP = {
    "Selecionar para fazer uma ação na linha",
    "Situação do lançamento",
    "Data de emissão do documento",
    "Data de vencimento do documento",
    "Descrição do lançamento",
    "Número do documento utilizado",
    "Valor lançado no documento",
    "",
    "", "", "", "", "", "", ""};
private static final String[] NOME_COLUNA = new String[]{
    "",
    "Status",
    "Emissão",
    "Vencimento",
    "Descrição",
    "No. Doc",
    "Valor",
    "Observação",
    "Tipo",
    "Conta",
    "Subconta",
    "Liquidação",
    "Valor Pago",
    "Vendedor",
    "Cobrança"};
public static final int[] WIDTH = {20, 150, 90, 90, 350, 90, 110, 350, 30, 160, 160, 90, 110, 200, 200};
private static final Class[] TIPO_COLUNA = {
    Boolean.class,
    String.class,
    Date.class,
    Date.class,
    String.class,
    Integer.class,
    Double.class,
    String.class,
    String.class,
    Conta.class,
    SubConta.class,
    Date.class,
    Double.class,
    String.class,
    String.class
};
ArrayList<Financeiro> lista;
JTable table;

public FinanceiroTableModel(JTable table) {
    this.table = table;
    this.lista = new ArrayList<Financeiro>();
}

public FinanceiroTableModel(ArrayList<Financeiro> lista) {
    this.lista = lista == null ? new ArrayList<Financeiro>() : lista;
}

…[/code]

Na view:

[code] FinanceiroTableModel modelo = new FinanceiroTableModel(tabela);
tabela.setModel(modelo);
TableColumnModel cm = tabela.getColumnModel();
for (int i = 0; i < FinanceiroTableModel.WIDTH.length; i++) {
cm.getColumn(i).setPreferredWidth(FinanceiroTableModel.WIDTH[i]);
}
//Lib.setHeaderTabela(tabela);
cm.getColumn(5).setCellRenderer(new IntegerRenderer());
cm.getColumn(6).setCellRenderer(new MoneyRenderer());
cm.getColumn(12).setCellRenderer(new MoneyRenderer());

    JTextField textField = new JTextField();
    textField.setBorder(BorderFactory.createEmptyBorder());
    DefaultCellEditor editor = new DefaultCellEditor(textField);
    editor.setClickCountToStart(1);
    cm.getColumn(4).setCellEditor(new StringActionTableCellEditor(editor));
    cm.getColumn(7).setCellEditor(new StringActionTableCellEditor(editor));

…[/code]
Será que você poderia me dar uma dica como eu posso usar esse seu Filtro na minha tabela?

Por que seu model tem uma referência à tabela? Essa referência não deveria existir! =o

FinanceiroTableModel modelo = new FinanceiroTableModel(tabela);  
TableFilter filtro = new TableFilter(tabela.getTableHeader(), modelo);
tabela.setModel(filtro);  

É bom guardar a variável do filtro em uma propriedade, já que você precisará dela para converter as linhas da view na linha do model, e vice-versa.

[quote=ViniGodoy]Por que seu model tem uma referência à tabela? Essa referência não deveria existir! =o

FinanceiroTableModel modelo = new FinanceiroTableModel(tabela);  
TableFilter filtro = new TableFilter(tabela.getTableHeader(), modelo);
tabela.setModel(filtro);  

É bom guardar a variável do filtro em uma propriedade, já que você precisará dela para converter as linhas da view na linha do model, e vice-versa.
[/quote]

Tenho uma referência a tabela porque tenho esse método aqui:

public void tableChanged(TableModelEvent e) { if (e.getType() == TableModelEvent.UPDATE) { int column = e.getColumn(); int row = e.getFirstRow(); table.setColumnSelectionInterval(column + 1, column + 1); table.setRowSelectionInterval(row, row); } }

Esse filtro na verdade apenas reordena as linhas da tabela?

Ele reordena e filtra.

O problema é que você passa a ter dois índices, e isso provavelmente irá quebrar seu método.

Um deles é o que o tablefilter está exibindo, o outro é o do seu model. O tablefilter tem métodos para converter um índice no outro.

[quote=ViniGodoy]Por que seu model tem uma referência à tabela? Essa referência não deveria existir! =o

FinanceiroTableModel modelo = new FinanceiroTableModel(tabela);  
TableFilter filtro = new TableFilter(tabela.getTableHeader(), modelo);
tabela.setModel(filtro);  

É bom guardar a variável do filtro em uma propriedade, já que você precisará dela para converter as linhas da view na linha do model, e vice-versa.
[/quote]

Quando eu uso meu AbastractTableModel:[code]FinanceiroTableModel modelo = new FinanceiroTableModel(tabela);
tabela.setModel(modelo);
TableColumnModel cm = tabela.getColumnModel();
for (int i = 0; i < FinanceiroTableModel.WIDTH.length; i++) {
cm.getColumn(i).setPreferredWidth(FinanceiroTableModel.WIDTH[i]);
}
cm.getColumn(5).setCellRenderer(new IntegerRenderer());
cm.getColumn(6).setCellRenderer(new MoneyRenderer());
cm.getColumn(12).setCellRenderer(new MoneyRenderer());

JTextField textField = new JTextField();
textField.setBorder(BorderFactory.createEmptyBorder());
DefaultCellEditor editor = new DefaultCellEditor(textField);
editor.setClickCountToStart(1);
cm.getColumn(4).setCellEditor(new StringActionTableCellEditor(editor));
cm.getColumn(7).setCellEditor(new StringActionTableCellEditor(editor));


//======= Adicionando uma linha no modelo
ArrayList<Financeiro> lista1 = new ArrayList<Financeiro>();
FinanceiroTableModel modelo = (FinanceiroTableModel) tabelaContaPagarReceber.getModel();
Financeiro item1 = new Financeiro();
item1.setCodigo(1);
item1.setStatus("LIQUIDADO");
item1.setDataEmissao(new Date());
item1.setDataVencimento(new Date());
item1.setDescricao("COPOLA");
item1.setNumeroDocumento(1001);
item1.setValor(10.00);
item1.setObservacao("");
item1.setTipoDocumento("NF");
item1.setConta(new Conta(1, "RECEITA DE CLIENTE", 1));
item1.setSubConta(new SubConta(1, 1, "VENDA DE SERVIÇOS", true));
item1.setDataLiquidacao(new Date());
item1.setValorPagoRecebido(10.00);
item1.setVendedorSolicitante("FRANCISCO");
item1.setCobrançaPagamento("BRADESCO 234327483274932");
lista1.add(item1);
modelo.setLista(lista1);
[/code]Sai com as colunas formatadas com os tamanhos que eu defini e eu consigo adicionar linhas (Financeiro) tranqüilamente:

Agora quando eu aplico o modelo com o Filtro as colunas saem todas desconfiguradas e eu não consigo adicionar linhas:[code]FinanceiroTableModel modelo = new FinanceiroTableModel(tabela);
tabela.setModel(modelo);
TableColumnModel cm = tabela.getColumnModel();
for (int i = 0; i < FinanceiroTableModel.WIDTH.length; i++) {
cm.getColumn(i).setPreferredWidth(FinanceiroTableModel.WIDTH[i]);
}
cm.getColumn(5).setCellRenderer(new IntegerRenderer());
cm.getColumn(6).setCellRenderer(new MoneyRenderer());
cm.getColumn(12).setCellRenderer(new MoneyRenderer());

JTextField textField = new JTextField();
textField.setBorder(BorderFactory.createEmptyBorder());
DefaultCellEditor editor = new DefaultCellEditor(textField);
editor.setClickCountToStart(1);
cm.getColumn(4).setCellEditor(new StringActionTableCellEditor(editor));
cm.getColumn(7).setCellEditor(new StringActionTableCellEditor(editor));
filtroFinanceiro = new TableFilter(tabela.getTableHeader(), modelo);
tabela.setModel(filtroFinanceiro);
[/code]

Exception in thread &quot;AWT-EventQueue-0&quot; java.lang.ClassCastException: util.gui.table.TableFilter cannot be cast to kmoney.lib.model.table.FinanceiroTableModel

Alguma dica de como eu poderia resolver isso?

Nessa linha:
(FinanceiroTableModel) tabelaContaPagarReceber.getModel();

Você está tentando obter o model e fazer um cast para FinanceiroTableModel. No entanto, o model da tabela agora é um TableFilter.

Eu sugiro que você guarde uma referência do seu Model e do Filter, em atributos que você tenha certeza de quem é quem. Então, evite a chamada de table.getModel e use esses atributos diretamente.

Ok, eu guardei a referência ao filtro, minha pergunta é: como eu adiciono dados a JTable?
Quando eu usava o meu modelo eu tinha um método nele que recebia uma lista e atualizava o modelo e a tabela, agora nesse modelo do TableFilter eu não entendi como adicionar dados nele.

Basta adicionar dados ao seu modelo, normalmente (e não ao filtro).
Por isso é importante guardar uma referência para o model e para o filtro.

Ok, muito obrigado, funcionou como você disse, agora eu gostaria de saber porque está sendo perdida todas as minhas configurações no ColumnModel (CellRenders, CellEditor, largura das colunas)?

No construtor da classe TableFilter eu verifiquei as larguras das colunas e estão chegando corretamente.

    public TableFilter(JTableHeader tableHeader, TableModel tableModel) {
        this.filters = new HashMap<Integer, Filter>();
        this.filteredRows = new ArrayList<Integer>();

        this.disableColumns = new TreeSet<Integer>();
        this.sortedOnlyColumn = new TreeSet<Integer>();
        this.upToDateColumns = new HashSet<Integer>();
        this.filterByColumn = new HashMap<Integer, List<Integer>>();

        this.header = tableHeader;
        
        // Codigo adicionado somente para verificar as larguras adicionadas previamente nas colunas
        TableColumnModel cm = tableHeader.getColumnModel();
        for (int i = 0; i < cm.getColumnCount(); i++) {
            System.out.println("Coluna " + i + " = " + cm.getColumn(i).getWidth());
        }
        
        tableHeader.getColumnModel().addColumnModelListener(
                new TableColumnModelAdapter() {

                    @Override
                    public void columnAdded(TableColumnModelEvent e) {
                        int modelIndex = header.getColumnModel().getColumn(e.getToIndex()).getModelIndex();
                        refreshHeader(modelIndex);
                    }
                });
        setTableValues(tableHeader, tableModel);
    }


Coluna 0 = 20
Coluna 1 = 150
Coluna 2 = 90
Coluna 3 = 90
Coluna 4 = 350
Coluna 5 = 90
Coluna 6 = 110
Coluna 7 = 350
Coluna 8 = 30
Coluna 9 = 160
Coluna 10 = 160
Coluna 11 = 90
Coluna 12 = 110
Coluna 13 = 200
Coluna 14 = 200

Não sei, nunca vi esse comportamento. E olha que também uso colunas de tamanho diferente, etc.

Não funciona nem se vc definir esses dados depois de associar o filtro?

Muito obrigado, agora funcionou, espero não mais ter que precisar te incomodar com esse assunto novamente.

[code] private void setMontaTabelaFinanceiro(JTable tabela) {
modeloFinanceiro = new FinanceiroTableModel(tabela);
tabela.setModel(modeloFinanceiro);

    filtroFinanceiro = new TableFilter(tabela.getTableHeader(), modeloFinanceiro);
    tabela.setModel(filtroFinanceiro);

    TableColumnModel cm = tabela.getColumnModel();
    for (int i = 0; i < FinanceiroTableModel.WIDTH.length; i++) {
        cm.getColumn(i).setPreferredWidth(FinanceiroTableModel.WIDTH[i]);
    }
    cm.getColumn(5).setCellRenderer(new IntegerRenderer());
    cm.getColumn(6).setCellRenderer(new MoneyRenderer());
    cm.getColumn(12).setCellRenderer(new MoneyRenderer());

    JTextField textField = new JTextField();
    textField.setBorder(BorderFactory.createEmptyBorder());
    DefaultCellEditor editor = new DefaultCellEditor(textField);
    editor.setClickCountToStart(1);
    cm.getColumn(4).setCellEditor(new StringActionTableCellEditor(editor));
    cm.getColumn(7).setCellEditor(new StringActionTableCellEditor(editor));

    StripedTableCellRenderer.installInTable(tabela, new Color(223, 223, 223), Color.black, null, null);
}[/code][img]http://lh4.ggpht.com/_iPEXgELGyFA/Sp5ugRXEBGI/AAAAAAAAA2U/VS8CFXKjY8c/Financeiro-4.png[/img]

Muito obrigado mesmo.

Sem problemas. Fico feliz em saber que a classe tenha funcionado num table complexo como o seu. :slight_smile:

Na verdade tenho mais uma pergunta.

Onde eu trato a formatação do dado adicionado naquele popUP da coluna, na imagem tem uma coluna do tipo Date que quero mostrar a data no formato: dd/MM/yyyy

Eu (ainda) não consegui localizar nos código onde está sendo feito isso, e também como converter de volta quando for selecionado pelo usuário para atualizar o filtro.

Na verdade, ali não trata. Aliás, é uma boa idéia. Numa dessas definir um renderer para lá.

Enfim, o que fazíamos para mostrar datas era retornar o date encapsulado num tipo chamado DateView.

O tipo era esse aqui:

[code]import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateView implements Comparable<DateView>
{
public static final DateView NULL = new DateView((Date) null);

private Date date;
private DateFormat format;

public DateView(Date date)
{
    this(date, DateFormat.getDateInstance(DateFormat.DEFAULT));
}

public DateView(Calendar cal)
{
    this(cal, DateFormat.getDateInstance(DateFormat.DEFAULT));
}

public DateView(Date date, String format)
{
    this(date, new SimpleDateFormat(format));
}

public DateView(Calendar cal, String format)
{
    this(cal, new SimpleDateFormat(
            format));
}

public DateView(Date date, DateFormat format)
{
    if (format == null)
        throw new IllegalArgumentException("Format cannot be null!");

    this.date = date;
    this.format = format;
}

public DateView(Calendar cal, DateFormat format)
{
    this(cal == null ? (Date) null : cal.getTime(), format);
}

public int compareTo(DateView o)
{
    if (date == null)
        return o.date == null ? 0 : -1;
    if (o.date == null)
        return 1;

    return date.compareTo(o.date);
}

@Override
public String toString()
{
    if (date == null)
        return "";

    return format.format(date);
}

@Override
public int hashCode()
{
    return (date == null) ? 0 : toString().hashCode();
}

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;

    if (obj == null)
        return false;

    if (getClass() != obj.getClass())
        return false;

    final DateView other = (DateView) obj;

    return toString().equals(other.toString());
}

}
[/code]

Nada mais é do que um wrapper que retorna a Data no formato que você quiser no toString(), mas faz a comparação usando os valores de data mesmo.

Aí, basta fazer seu model retornar um DateView no lugar de um Date. E no setValueAt, pegar o calendar que está associado ao DateView.

Não seria possível usar o mesmo renderer da coluna da tabela? Por exemplo: uma coluna moeda (currency) que possui um Double como tipo.

Eu não entendi direito como você usa esse DateView, e também não é apenas Date, tem Double também tem Integer.

Vinícius tenho que te perguntar mais uma coisa sobre esse filtro.

Seguinte, preciso que um método do meu modelo (que foi passado para o TableFilter) se executado apenas sobre as linhas filtradas, esse método retorna a soma dos valores em uma coluna (coluna valor a pagar).

Estou guardando as referências para os atributos conforme você me indicou:

[code] modeloContasPagar = new ContasPagarTableModel(tabela);
tabela.setModel(modeloContasPagar);

    filtroContasPagar = new TableFilter(tabela.getTableHeader(), modeloContasPagar);
    tabela.setModel(filtroContasPagar);

[/code]

Coloquei o TableModelListener na tabela e estou testando pegar a soma tanto do modelo como do filtro, em nenhum dos casos o valor mudou:

[code] tabela.getModel().addTableModelListener(new TableModelListener() {

        public void tableChanged(TableModelEvent e) {
            double valorPagar = modeloContasPagar.getValorTotalPagar();
            System.out.println("=========================");
            System.out.println("Valor a pagar = " + valorPagar);

            valorPagar = ((ContasPagarTableModel)filtroContasPagar.getTableModel()).getValorTotalPagar();
            System.out.println("Valor a pagar = " + valorPagar);

            if (e.getType() == TableModelEvent.INSERT) {
            } else if (e.getType() == TableModelEvent.DELETE) {
            }
        }
    });

[/code]

Qual seria o método do filtro que eu tenho acesso as linhas filtradas?

Mais uma vez obrigado pelas dicas.
Francisco

Você acredita que só hoje vi sua dúvida? Ela ainda existe, ou conseguiu resolver?

Em todo caso, você usa o método getFilteredRows(). Ele retorna um list, com o índice das linhas que foram mantidas, após o filtro.

Ahahaha, já resolvi sim, acho que foi do jeito que você indicou mesmo, mesmo assim obrigado pela resposta.

Abz