Criando um model personalizado para um JTable com AbstractTableModel
Como vimos no post passado, um JTable
utiliza um objeto TableModel
para manipular seus valores e no exemplo que vimos utilizamos a classe DefaultTableModel
que funciona bem para muitos casos mas se quisermos uma maior personalização quando estamos manipulando os dados da JTable
devemos criar nosso próprio TableModel
.
Para começarmos devemos criar uma classe que estenda a classe abstrata AbstractTableModel
, que irá nos obrigar a implementar os seguintes métodos:
public int getRowCount();
public int getColumnCount();
public Object getValueAt(int i, int i1);
Estes métodos devem retornar respectivamente o numero de linhas, o numero de colunas e o valor da célula correspondente aos valores de linha e coluna fornecidos por parâmetro.
Mas lembrando que estamos criando uma classe para armazenar os dados de uma JTable
então devemos ter uma estrutura para armazenar estes valores, eu particularmente gosto do List
, mas o que vamos armazenar, se vocês se lembram do exemplo do outro post que usamos o DefaultTableModel
, podíamos pegar os dados da tabela e ele nos devolvia em uma estrutura que era um vetor(Vector
) sendo cada elemento deste uma linha que também era um vetor e que cada de seus elementos seriam as células da linha. No nosso caso pretendemos que cada linha seja as informações de um objeto(no caso Pessoa) então nada mais natural que os valores serem armazenados em um List
que contenha objetos do tipo Pessoa, então antes de tudo vamos criar a nossa classe Pessoa.
public class Pessoa {
private String nome;
private String sobrenome;
private int idade;
private double altura;
//gets e sets
}
Agora podemos começar a criar nosso TableModel
:
public class PessoaTableModel extends AbstractTableModel{
private List<Pessoa> dados;
private String[] colunas = {"Nome" , "Sobrenome" ,"Idade","Altura"};
public PessoaTableModel(){
dados = new ArrayList<Pessoa>();
}
public void addRow(Pessoa p){
this.dados.add(p);
this.fireTableDataChanged();
}
public String getColumnName(int num){
return this.colunas[num];
}
@Override
public int getRowCount() {
return dados.size();
}
@Override
public int getColumnCount() {
return colunas.length;
}
@Override
public Object getValueAt(int linha, int coluna) {
switch(coluna){
case 0: return dados.get(linha).getNome();
case 1: return dados.get(linha).getSobrenome();
case 2: return dados.get(linha).getIdade();
case 3: return dados.get(linha).getAltura();
}
return null;
}
}
Na linha definimos uma tributo List
que irá armazenar os valores da tabela, na linha 4 criamos uma array de string que será os nomes das colunas do nosso model(ele será utilizados no método getColumnName
definido na linha 15) que será chamado pelo JTable
quando for mostrar os nomes das colunas no topo.
Na linha 6 temos o construtor que instancia o ArrayList
também poderíamos sobrecarregar este construtor para receber por parâmetro o ArrayList
já populado.
Seguindo na linha 10 temos o método addRow
para adicionar um valor a tabela, nele chamamos o método add
do nosso ArrayList
e logo em seguida chamamos o método fireTableDataChanged
herdado de AbstractTableModel
que faz com que os valores da JTable
sejam recarregados, este método deve ser chamado toda vez que fizermos alguma modificação nos dados da tabela.
Nas linhas 20 e 25 simplesmente retornamos o número de linhas e o número de colunas.
Agora vamos ao método getValueAt(int,int)
, na linha 30, que é responsável por devolver o valor da célula, como cada coluna de nossa tabela corresponde a um atributo de nosso objeto Pessoa iremos utilizar um switch
para, dependendo do número da coluna passado por parâmetro, retorne o atributo apropriado, e utilizamos o índice passado para linha para pegar o objeto em nosso ArrayList
através do método get(int)
.
Também iremos precisar de um método que remova uma linha da tabela:
public void removeRow(int linha){
this.dados.remove(linha);
this.fireTableRowsDeleted(linha, linha);
}
Como vemos o código é bem simples, apenas removemos a linha recebida por parâmetro através do método remove(int)
do nosso ArrayList
e depois chamamos o método fireTableRowsDeleted
para atualizar a tabela e retirar o intervalo de linhas passados, no nosso caso somente uma.
Outro método interessante para termos seria um que devolva um objeto Pessoa
dado o número da linha;
public Pessoa get(int linha){
return this.dados.get(linha);
}
Se você deve lembrar quando utilizávamos o DefaultTableModel
podíamos alterar a tabela simplesmente dando um duplo clique em cima e alguma célula e ela permitiria a edição. Isso acontecia porque no DefaultTableModel
o método isCellEditable(int linha, int coluna)
que é chamado para saber se uma célula é editável sempre retornava true, mas no AbstractTableModel
ele retorna sempre false, então devemos sobrescreve-lo:
public boolean isCellEditable(int linha, int coluna) {
return true;
}
O código acima irá permitir a edição mas o valor não ficará fixo pois não foi criado o método setValueAt(Object valor, int linha, int coluna)
que é responsável por colocar um valor em uma célula.
public void setValueAt(Object valor, int linha, int coluna){
if( valor == null) return;
switch(coluna){
case 0: dados.get(linha).setNome( (String) valor);break;
case 1: dados.get(linha).setSobrenome( (String) valor);break;
case 2: dados.get(linha).setIdade( Integer.parseInt( (String) valor) );break;
case 3: dados.get(linha).setAltura( Double.parseDouble( (String) valor) );break;
}
this.fireTableRowsUpdated(linha, linha);
}
O código acima irá altera o valor no ArrayList
mas se você precisar que os dados também sejam imediatamente alterados no banco de dados por exemplo, você vai precisar adicionar um TableModelListener
ao seu model que executará o método tableChanged
toda vez que os dados da JTable
forem alterados. Adicionaremos eles no construtor:
public PessoaTableModel(){
dados = new ArrayList<Pessoa>();
this.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent tme) {
int linha = tme.getFirstRow();
Pessoa p = dados.get(linha);
// aqui você atualiza no banco ou em outro lugar qualquer
}
} );
}
Era isso pessoal, espero que tenha dado para entender alguma coisa, t++