Implementando seu próprio TableModel

Veja neste artigo em detalhes como implementar um TableModel próprio e como utilizá-lo de forma correta.

Um dos recursos mais interessantes do Java, em relação a componentes do pacote Swing, é o Jtable, por tratar-se de uma estrutura que fornece um poder de manipulação inimaginável. Mas neste artigo não trataremos sobre Jtable e sim sobre o que o faz tão poderoso: O TableModel.

O TableModel nada mais é do que uma interface, ou seja, como um “contrato” para que possamos implementar as suas regras. Vejamos a sua estrutura no código da Listagem 1.

Listagem 1. TableModel

package javax.swing.table; import javax.swing.*; import javax.swing.event.*; public interface TableModel { public int getRowCount(); public int getColumnCount(); public String getColumnName(int columnIndex); public Class<?> getColumnClass(int columnIndex); public boolean isCellEditable(int rowIndex, int columnIndex); public Object getValueAt(int rowIndex, int columnIndex); public void setValueAt(Object aValue, int rowIndex, int columnIndex); public void addTableModelListener(TableModelListener l); public void removeTableModelListener(TableModelListener l); }

Na Listagem 1 podemos perceber que a estrutura do TableModel é relativamente simples, porém muito poderosa, e é a partir desta interface que o Jtable consegue realizar manipulações independentemente do tipo de dado, então vamos entender a função de cada um dos métodos acima, mas lembre-se que eles são apenas assinaturas de métodos e ainda não possuem nenhuma lógica implementada, por isso trata-se apenas de uma Interface.

Entendendo o TableModelListener e o TableModelEvent

O TableModelListener é importante para entendermos os dois últimos métodos da interface TableModel. Vamos conhece-lo através do código da Listagem 2.

Listagem 2. TableModelListener

public interface TableModelListener extends java.util.EventListener { public void tableChanged(TableModelEvent e); }

O TableModelListener, assim como o TableModel, também é uma interface e nossa função é implementar o método tableChanged() com a lógica necessária. Qualquer alteração que ocorra no nosso TableModel será “monitorado” pelo nosso Listener, muito útil quando desejamos saber o que o usuário alterou sem que ele precise ficar clicando em botões a todo instante.

Perceba na Listagem 2 que o argumento passado para o método tableChanged() é um TableModelEvent que diz respeito as alterações que foram realizadas. Observe a Listagem 3.

Listagem 3. TableModelEvent

package javax.swing.event; import java.util.EventObject; import javax.swing.table.*; public class TableModelEvent extends java.util.EventObject { public static final int INSERT = 1; public static final int UPDATE = 0; public static final int DELETE = -1; public static final int HEADER_ROW = -1; public static final int ALL_COLUMNS = -1; protected int type; protected int firstRow; protected int lastRow; protected int column; public TableModelEvent(TableModel source) { this(source, 0, Integer.MAX_VALUE, ALL_COLUMNS, UPDATE); } public TableModelEvent(TableModel source, int row) { this(source, row, row, ALL_COLUMNS, UPDATE); } public TableModelEvent(TableModel source, int firstRow, int lastRow) { this(source, firstRow, lastRow, ALL_COLUMNS, UPDATE); } public TableModelEvent(TableModel source, int firstRow, int lastRow, int column) { this(source, firstRow, lastRow, column, UPDATE); } public TableModelEvent(TableModel source, int firstRow, int lastRow, int column, int type) { super(source); this.firstRow = firstRow; this.lastRow = lastRow; this.column = column; this.type = type; } public int getFirstRow() { return firstRow; }; public int getLastRow() { return lastRow; }; public int getColumn() { return column; }; public int getType() { return type; } }

Não implementaremos o TableModelEvent, apenas usaremos ele, mas precisamos entender como utilizá-lo e porque estamos utilizando os seus métodos.

Logo no início temos três variáveis: INSERT, UPDATE e DELETE. Essas variáveis irão especificar que tipo de alteração está sendo feita: uma inserção, uma Atualização ou uma deleção? Esse retorno é dado através do método getType() que retorna a variável type que consequentemente deve estar armazenando um dos tipos acima que citamos. Ou seja, quando um TableEvent é criado um tipo é especificado para ele e conseguimos identificar este tipo através do método getType().

Os atributos HEADER_ROW e ALL_COLUMNS servem como auxilio para passagem de parâmetro no construtor do TableModelEvent, ou seja, se desejarmos criar um Evento onde uma linha inteira foi alterada (todas as colunas) usamos o ALL_COLUMNS.

Ainda no TableModelEvent, temos três métodos que não podemos deixar passar despercebidos, são eles:

O último método relativo a ação que está sendo realizada:

Temos agora o conhecimento de como funciona o TableModelEvent e já podemos implementar um Listener simples apenas para estudo, como ostra a Listagem 4.

Listagem 4. Implementando um TableModelListener simples

import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; public class DevmediaTableModelListener implements TableModelListener { @Override public void tableChanged(TableModelEvent e) { int primeiraLinha = e.getFirstRow(); int ultimaLinha = e.getLastRow(); int coluna = e.getColumn(); String acao = ""; if (e.getType() == TableModelEvent.INSERT){ acao = "inseriu"; }else if (e.getType() == TableModelEvent.UPDATE){ acao = "alterou"; }else{ acao = "deletou"; } if (primeiraLinha == TableModelEvent.HEADER_ROW){ System.out.println("Você "+acao+" a META-DATA da tabela"); }else{ System.out.println("Você "+acao+" da linha "+primeiraLinha+" até "+ultimaLinha); } if (coluna == TableModelEvent.ALL_COLUMNS){ System.out.println("Você "+acao+" todas as colunas"); }else{ System.out.println("Você "+acao+" a coluna "+coluna); } } }

Nosso exemplo da Listagem 4 apenas demonstra tudo que explicamos sobre o TableModelListener e o TableModelEvent, veja que estamos apenas mostrando o que está sendo feito mas seu uso vai muito além disso.

Voltando ao TableModel

Até aqui já sabemos como funciona o TableModel, TableModelListener e o TableModelEvent, vimos com detalhes o uso de cada um, porém ainda ficamos com pendência em dois métodos: addTableModelListener() e removeTableModelListener(). Agora que temos um conhecimento mais aprofundado no Listener fica fácil perceber que os métodos mencionados acima servem para adicionar e remover um Listener do TableModel, respectivamente. É como se estivessemos “anexando” nosso DevmediaTableModelListener, mostrado na Listagem 4, ao nosso TableModel.

Antes de iniciarmos a implementação do nosso TableModel ainda temos mais alguns métodos de suma importância para aprender, são eles:

Os outros métodos com prefixo “fireTable...” são todos métodos provenientes do fireTableChanged(), ou seja, eles apenas passam argumentos distintos para o fireTableChanged(). Vejamos:

Perceba que os métodos “fire...” todos fazem chamada aos listeners, ou seja, é nos listeners que devem implementar as alterações que serão realizadas no Jtable.

Por exemplo, Você adiciona um novo Usuario ao TableModel, e faz chamada ao método fireTableRowsInserted() que por sua vez chama um listener que irá realizar a alteração no Jtable.

Antes precisaremos de um Bean para manipular então criamos o Bean Usuario, como mostra a Listagem 5.

Listagem 5. Bean Usuario

package br.com.crudusuario.bean; public class Usuario { private String login; private String senha; private String nome; public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getSenha() { return senha; } public void setSenha(String senha) { this.senha = senha; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } }

Agora podemos construir nosso DevmediaTableModel, pois já temos o conhecimento necessário para tal. Observe a Listagem 6.

Listagem 6. DevmediaTableModel

package br.com.crudusuario.bean; import java.util.ArrayList; import java.util.List; import javax.swing.table.AbstractTableModel; public class DevmediaTableModel extends AbstractTableModel { private List<Usuario> usuarios; private String[] colunas = new String[]{ "Login","Nome", "Senha"}; /** Creates a new instance of DevmediaTableModel */ public DevmediaTableModel(List<Usuario> usuarios) { this.usuarios = usuarios; } public DevmediaTableModel(){ this.usuarios = new ArrayList<Usuario>(); } public int getRowCount() { return usuarios.size(); } public int getColumnCount() { return colunas.length; } @Override public String getColumnName(int columnIndex){ return colunas[columnIndex]; } @Override public Class<?> getColumnClass(int columnIndex) { return String.class; } public void setValueAt(Usuario aValue, int rowIndex) { Usuario usuario = usuarios.get(rowIndex); usuario.setLogin(aValue.getLogin()); usuario.setNome(aValue.getNome()); usuario.setSenha(aValue.getSenha()); fireTableCellUpdated(rowIndex, 0); fireTableCellUpdated(rowIndex, 1); fireTableCellUpdated(rowIndex, 2); } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { Usuario usuario = usuarios.get(rowIndex); switch (columnIndex) { case 0: usuario.setLogin(aValue.toString()); case 1: usuario.setNome(aValue.toString()); case 2: usuario.setSenha(aValue.toString()); default: System.err.println("Índice da coluna inválido"); } fireTableCellUpdated(rowIndex, columnIndex); } public Object getValueAt(int rowIndex, int columnIndex) { Usuario usuarioSelecionado = usuarios.get(rowIndex); String valueObject = null; switch(columnIndex){ case 0: valueObject = usuarioSelecionado.getLogin(); break; case 1: valueObject = usuarioSelecionado.getNome(); break; case 2 : valueObject = usuarioSelecionado.getSenha(); break; default: System.err.println("Índice inválido para propriedade do bean Usuario.class"); } return valueObject; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } public Usuario getUsuario(int indiceLinha) { return usuarios.get(indiceLinha); } public void addUsuario(Usuario u) { usuarios.add(u); int ultimoIndice = getRowCount() - 1; fireTableRowsInserted(ultimoIndice, ultimoIndice); } public void removeUsuario(int indiceLinha) { usuarios.remove(indiceLinha); fireTableRowsDeleted(indiceLinha, indiceLinha); } public void addListaDeUsuarios(List<Usuario> novosUsuarios) { int tamanhoAntigo = getRowCount(); usuarios.addAll(novosUsuarios); fireTableRowsInserted(tamanhoAntigo, getRowCount() - 1); } public void limpar() { usuarios.clear(); fireTableDataChanged(); } public boolean isEmpty() { return usuarios.isEmpty(); } }

Baseado em todos os conceitos aprendidos acima, fica como exercício para você, caro leitor, estudar o uso da Listagem 5. Mas você pode estar se perguntando: mas e o nosso listener? Como irá funcionar se não criamos um listener? Como serão adicionadas as linhas e colunas sem um listener para ser disparado no “fireTableChanged()”?

O Jtable por si só já implementa o TableModelListener, ou seja, além de ser um componente ele também é um listener, e isso faz com que não precisemos criar um listener e controlar as alterações do Jtable. Veja como podemos fazer com o código da Listagem 7.

Listagem 7. Adicionando nosso Model no Jtable

DevmediaTableModel model = new DevmediaTableModel(); JTable jTableUsuarios = new Jtable(model); Usuario u = new Usuario(); u.setLogin("ronaldo"); u.setNome("Ronaldo Lanhellas"); u.setSenha("123"); model.addUsuario(u);

Acima nós criamos nosso model, depois criamos um Jtable associando o nosso model ao Jtable. Daqui em diante é só trabalhar com os métodos que criamos no nosso DevmediaTableModel. Se você depurar o método “addUsuario()” verá que na chamada ao fireTableRowsInserted() ele acha um listener que possui o tableChanged() e esse listener está dentro da própria classe Jtable, pois como dissemos anteriormente além de ser um componente ele também é um listener.

Obviamente que se houverem ações especificas que o listener do Jtable não implementa, você poderá criar seu próprio listener e “anexar” ao seu TableModel através do método addTableModelListener(), pois agora você já tem conhecimento suficiente para criar um listener.

Neste artigo vimos com detalhes vários conceitos importantes para só então conseguirmos de fato construir nosso próprio TableModel. Aprendemos primeiro sobre a interface TableModel, logo em seguida vimos como funciona o TableModelListener e juntamente com este assunto já abordamos o TableModelEvent, sendo todas esses três assuntos de extrema importância para entender o funcionamento do nosso DevmediaTableModel.

Ainda há uma discussão que não foi abordada neste artigo, que é o uso do DefaultTableModel, um TableModel padrão utilizado para quem não quer fazer o seu próprio. Muitos condenam o seu uso e dizem que a qualquer custo você deve criar o seu próprio e deixar o Default de lado, pois ele pode trazer lentidão a sua aplicação e outros problemas, como alto acoplamento de código. De fato o melhor é sempre criar o seu próprio TableModel por tratar-se de uma classe mais específica para o que você precisa e mais “consistente” do ponto de vista funcional, mas vale a pena conferir o que o DefaultTableModel faz e estudar um pouco mais sobre a relevância do mesmo.

Artigos relacionados