Olá pessoal, tudo bom? Um processo de negócio é composto basicamente de dois pontos:
- O processo em si
- As informações geradas por esse processo
A maioria dos softwares não estão restritos apenas ao cadastro de informações, mas a sua utilização destas e principalmente a iteração destas entre si. Essa combinação de informações faz com que seja gerada “vida” em um software. Um dos principais aspectos que um processo de negócio deve possuir é o estabelecimento de resumos de informações, comummente conhecidos como relatórios. Esses relatórios mostram os pontos chaves para saber se um processo está em seu curso correto. Uma das formas de apresentar um relatório é através de uma tabela, como mostra a Figura 01:
Uma tabela é uma estrutura de dados de duas dimensões que combina linhas e colunas em um único elemento. As colunas representam as características do relatório e as linhas representam uma informação a qual é descrita através dessas características. O encontro entre uma linha e uma coluna é chamado de célula. Ao trabalhar com Java Swing é possível exibir essa estrutura de dados através do componente javax.swing.JTable. Esse componente permite a criação de relatórios de informações. Mas antes de tudo é necessário pensar a respeito de agrupamento de informações.
Agrupamento de Informações
No paradigma da Orientação a Objetos todas as abstrações do mundo real são realizadas através de Classes. Assim para realizar uma abstração de uma informação definida como Endereço, podemos criar a seguinte classe:
E um código Java desta classe pode ser representado da seguinte forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package br.com.mauda.javaSwing.model; public class Endereco { private long id; private String rua, complemento, bairro, cidade, estado, pais; private int numero; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getRua() { return rua; } public void setRua(String rua) { this.rua = rua; } public String getComplemento() { return complemento; } public void setComplemento(String complemento) { this.complemento = complemento; } public String getBairro() { return bairro; } public void setBairro(String bairro) { this.bairro = bairro; } public String getCidade() { return cidade; } public void setCidade(String cidade) { this.cidade = cidade; } public String getEstado() { return estado; } public void setEstado(String estado) { this.estado = estado; } public String getPais() { return pais; } public void setPais(String pais) { this.pais = pais; } public int getNumero() { return numero; } public void setNumero(int numero) { this.numero = numero; } } |
Cada atributo desta classe (rua, numero, bairro, etc.) representa uma característica dessa classe no mundo real. Assim quando um cliente cria um requisito de um relatório para endereços, podemos criar em Java Swing uma tela com a seguinte Jtable:
Assim o objetivo principal deste post é criar uma JTable que possa receber uma lista de objetos de uma determinada classe e apresentar esta visualmente para o usuário.
TableModel Definition
Uma instância do componente JTable não utiliza diretamente as abstrações do mundo real geradas em nossos projetos de software, mas utiliza uma adaptação de uma tabela. Dessa forma para que possa ser atingido o objetivo deste post é necessário pensar nessa adaptação que teremos de fazer para que o componente JTable entenda a nossa lista de objetos. Um componente JTable possui diversos construtores, mas nesse post estaremos focados em apenas 1 deles:
- public Jtable(javax.swing.table.TableModel dm)
Esse construtor recebe como parâmetro uma instância da interface javax.swing.table.TableModel:
- API do Java ((API Java da interface TableModel (2014). http://docs.oracle.com/javase/7/docs/api/javax/swing/table/TableModel.html))
- Código da interface ((Código da interface TableModel (2014). http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/javax/swing/table/TableModel.java))
É bom salientar que toda interface Java na verdade é um “contrato” de cláusulas que uma classe deverá implementar. Dessa forma, ao implementar essa interface estamos criando uma ponte entre uma abstração do mundo real e um componente Jtable. Assim vamos nos aprofundar melhor para entender as “cláusulas” desse contrato, explorando o que cada método deve realizar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package javax.swing.table; import javax.swing.event.TableModelListener; public interface TableModel{ int getRowCount(); int getColumnCount(); String getColumnName(int columnIndex); Class<?> getColumnClass(int columnIndex); boolean isCellEditable(int rowIndex, int columnIndex); Object getValueAt(int rowIndex, int columnIndex); void setValueAt(Object aValue, int rowIndex, int columnIndex); void addTableModelListener(TableModelListener listener); void removeTableModelListener(TableModelListener listener); } |
Os 2 primeiros métodos, getRowCount() e getColumnCount(), devem retornar respectivamente a quantidade de linhas e a quantidade de colunas.
- O método String getColumnName(int columnIndex), deverá retornar qual é o nome da coluna na respectiva posição. Lembrando que a primeira posição de uma coluna é Zero.
- O método getColumnClass(int columnIndex), deverá retornar qual é a classe que representa a coluna na respectiva posição.
- O método isCellEditable(int rowIndex, int columnIndex), indica se a célula da linha (rowIndex) e coluna (columnIndex) é editável.
- Os métodos getValueAt(int rowIndex, int columnIndex) e setValueAt(Object aValue, int rowIndex, int columnIndex), recuperam/setam o objeto disponível na célula linha (rowIndex) x coluna (columnIndex).
- Os métodos addTableModelListener(TableModelListener listener) e void removeTableModelListener(TableModelListener listener) inserem/removem um listener para a Table.
- A TableModelListener ((API Java da interface TableModelListener (2014). http://docs.oracle.com/javase/7/docs/api/javax/swing/event/TableModelListener.html)) é uma interface própria para tratar eventos de uma tabela, como seleção de linhas/colunas.
Essa interface nos indica que está cobrindo todos os pontos necessários para preencher/obter informações de uma JTable. Bom o que deve ser feito então? Criar uma classe que implemente essa interface? Quase!
AbstractTableModel Class
Existe uma classe chamada javax.swing.table.AbstractTableModel que implementa essa interface. ((Código da classe AbstractTableModel (2014). http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/javax/swing/table/AbstractTableModel.java)) Um ponto importante é verificar que ela já implementa diversos métodos definidos na interface, mas de uma forma muito básica, ou seja, não retratando as condições necessárias para uma JTable mais especializada. Caso sua JTable necessite de edição direta na tabela, a implementação dos seguintes métodos poderão ser reaproveitados sem a necessidade de um override:
1 2 3 |
Class<?> getColumnClass(int columnIndex); void addTableModelListener(TableModelListener listener); void removeTableModelListener(TableModelListener listener); |
Caso sua JTable não necessite de uma edição direta e seja somente de “visualização”, no máximo seleção de uma linha, os seguintes métodos também poderão ser reaproveitados:
1 2 |
boolean isCellEditable(int rowIndex, int columnIndex); void setValueAt(Object aValue, int rowIndex, int columnIndex); |
O método findColumn(String columnName) é implementado nessa classe e pode ser útil para buscar uma posição de uma coluna a partir do seu nome. Dessa forma, os métodos básicos que deverão ser implementados em uma nova classe TableModel são os seguintes:
- int getRowCount();
- int getColumnCount();
- String getColumnName(int columnIndex);
- Object getValueAt(int rowIndex, int columnIndex);
Outro aspecto ruim desta classe é que não há uma integração entre um objeto abstrato do mundo real e o bloco de informações a ser exibido em tela, não tratando a possibilidade de enviar uma lista de objetos para o TableModel. Por essas observações a classe AbstractTableModel não atende as necessidades de uma JTable básica no Java Swing de um projeto mais orientado a objeto. Dessa forma iremos criar uma nova classe que crie uma integração com as abstrações do mundo real de um projeto de software, mas de uma forma genérica para qualquer classe.
ViewAbstractTableModel Class
Vamos criar uma classe abstrata chamada ViewAbstractTableModel. Essa classe será filha de AbstractTableModel.
1 2 3 4 5 6 7 |
package br.com.mauda.javaSwing.view.model; import javax.swing.table.AbstractTableModel; public abstract class ViewAbstractTableModel extends AbstractTableModel{ } |
Agora vamos atender ao último ponto das nossas observações acima:
“Outro aspecto ruim desta classe é que não há uma integração entre um objeto abstrato do mundo real e o bloco de informações a ser exibido em tela”
Vamos começar desmembrando esta observação. Uma tabela é composta por várias linhas. Dessa forma, uma tabela não seria uma lista de linhas? Não poderíamos adicionar um atributo java.util.List a essa nova classe para representar essa lista de linhas? Outro aspecto, o que seria uma linha? Não seriam informações básicas de um determinado escopo? Em Orientação a Objetos esse escopo não seria uma Classe que represente uma abstração do mundo real? Mas ao dizer que esta lista será do tipo de um escopo, ou seja de uma classe, não estamos restringindo a utilização desta lista para um único e exclusivo nicho de classes? É possível em Java criar itens genéricos? Sim! Utilizando o conceito de Generics ((Tutorial de Generics – Oracle (2014) – http://docs.oracle.com/javase/tutorial/java/generics/)) Bom, vamos codificar na classe esses nossos devaneios:
1 2 3 4 5 6 7 8 9 |
package br.com.mauda.javaSwing.view.model; import java.util.List; import javax.swing.table.AbstractTableModel; public abstract class ViewAbstractTableModel<E> extends AbstractTableModel{ protected List<E> rows; } |
Legal. Com isso já conseguimos atingir o objetivo da observação? Relembrando…
“Outro aspecto ruim desta classe é que não há uma integração entre um objeto abstrato do mundo real e o bloco de informações a ser exibido em tela”
Estamos realizando a integração entre o objeto abstrato e o bloco de informações? Não! Apenas criamos o objeto abstrato dentro do nosso bloco, mas a integração ainda não foi realizada. Para resolver isso vamos lembrar um pouco de Orientação a Objetos… novamente 🙂
Qual é o único método que qualquer classe executa em seu ciclo de vida? Qual é a uma das coisas que qualquer classe deve fazer? Se o que você pensou foi a inicialização de uma classe, você está certo! Para inicializar uma classe é necessário que sejam chamados os métodos construtores de uma classe. Assim para realizar a integração entre um objeto do mundo real e o bloco de informações, nós podemos utilizar o construtor. Dessa forma obrigamos ao desenvolvedor a passar essa informação. (Tudo bem ele pode passar um objeto nulo, pode, mas estamos falando de desenvolvedores que cooperam com o sistema 🙂 )
Assim vamos criar um construtor que receba uma lista genérica de objetos de qualquer classe, e vamos atribuir esta ao atributo criado em nossa classe:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package br.com.mauda.javaSwing.view.model; import java.util.List; import javax.swing.table.AbstractTableModel; public abstract class ViewAbstractTableModel<E> extends AbstractTableModel{ protected List<E> rows; public ViewAbstractTableModel(List<E> rows) { this.rows = rows; } } |
Dessa forma a observação foi concluída com sucesso. Vamos agora pensar nos métodos que nós devemos implementar:
Dessa forma, os métodos básicos que deverão ser implementados em uma nova classe TableModel são os seguintes:
- int getRowCount();
- int getColumnCount();
- String getColumnName(int columnIndex);
- Object getValueAt(int rowIndex, int columnIndex);
Vamos continuar seguindo o pensamento sobre as linhas de uma tabela, depois passaremos para as colunas. O primeiro método descrito na observação pode ser implementado; O método getRowCount(), indica a quantidade de linhas que uma tabela tem. Se todas as linhas estão dentro de um objeto List, nós podemos utilizar o método size() para obter a quantidade de linhas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package br.com.mauda.javaSwing.view.model; import java.util.List; import javax.swing.table.AbstractTableModel; public abstract class ViewAbstractTableModel<E> extends AbstractTableModel{ protected List<E> rows; public ViewAbstractTableModel(List<E> rows) { this.rows = rows; } @Override public int getRowCount() { return rows.size(); } } |
Ainda podemos criar 2 novos métodos para trabalhar com as linhas da nossa tabela; O primeiro método E getValueAtRow(int row), obtém o objeto E da List a partir da linha informada, através do método get() da interface List. O segundo método void setValueAtRow(int row, E object), seta um objeto E na lista na posição da linha informada. Dessa forma é possível ter uma liberdade de obter e setar informações na List interna da classe, sem precisar criar uma instância de DateTable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package br.com.mauda.javaSwing.view.model; import java.util.List; import javax.swing.table.AbstractTableModel; public abstract class ViewAbstractTableModel<E> extends AbstractTableModel{ protected List<E> rows; public ViewAbstractTableModel(List<E> rows) { this.rows = rows; } @Override public int getRowCount() { return rows.size(); } public E getValueAtRow(int row){ return rows.get(row); } public void setValueAtRow(int row, E object){ rows.set(row, object); } } |
Voltando agora nosso olhar para as colunas de uma tabela. Toda coluna possui um título e todo título é uma String. Como existem várias colunas, uma estrutura de dados poderia ser utilizada para armazenar essas Strings com os títulos das colunas. Poderia ser uma List<String>?. Poderia, mas, normalmente, as colunas de uma tabela sofrem muito menos alterações do que as linhas, dessa forma é mais fácil utilizar um array convencional para representar essa lista de Strings. Como não sabemos nesse momento, quais são as colunas que compõem uma tabela, será criado apenas um array do tipo String como atributo na classe, e em uma futura herança dessa classe o construtor poderia inicializar esse array com os nomes correspondentes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package br.com.mauda.javaSwing.view.model; import java.util.List; import javax.swing.table.AbstractTableModel; public abstract class ViewAbstractTableModel<E> extends AbstractTableModel{ protected List<E> rows; protected String[] columns; public ViewAbstractTableModel(List<E> rows) { this.rows = rows; } @Override public int getRowCount() { return rows.size(); } public E getValueAtRow(int row){ return rows.get(row); } public void setValueAtRow(int row, E object){ rows.set(row, object); } } |
Pensando novamente nas observações:
Dessa forma, os métodos básicos que deverão ser implementados em uma nova classe TableModel são os seguintes:
- int getRowCount();
- int getColumnCount();
- String getColumnName(int columnIndex);
- Object getValueAt(int rowIndex, int columnIndex);
Podemos criar o método getColumnCount(), pois apenar não possuirmos valores reais dentro do nosso array estático, existe uma propriedade chamada length que nos indica a quantidade de objetos existentes dentro desse array. Assim pode ser feito o override deste método:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package br.com.mauda.javaSwing.view.model; import java.util.List; import javax.swing.table.AbstractTableModel; public abstract class ViewAbstractTableModel<E> extends AbstractTableModel{ protected List<E> rows; protected String[] columns; public ViewAbstractTableModel(List<E> rows) { this.rows = rows; } @Override public int getRowCount() { return rows.size(); } @Override public int getColumnCount() { return columns.length; } public E getValueAtRow(int row){ return rows.get(row); } public void setValueAtRow(int row, E object){ rows.set(row, object); } } |
Com a criação do array estático também é possível fazer o override do método getColumnName(int columnIndex), já que o nome das colunas estará nesse array. Há uma pequena observação sobre esse método, pois ele possui uma implementação na classe AbstractDataTable, o qual retorna como nomes de colunas A, B, C,…, Z, AA,… e assim por diante (Como o Microsoft Excel faz). Dessa forma, podemos fazer uma validação se o inteiro passado é menor que o retorno do método getColumnCount(). Se for, retorna o valor contido no array estático, senão chama o método da classe Pai.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package br.com.mauda.javaSwing.view.model; import java.util.List; import javax.swing.table.AbstractTableModel; public abstract class ViewAbstractTableModel<E> extends AbstractTableModel{ protected List<E> rows; protected String[] columns; public ViewAbstractTableModel(List<E> rows) { this.rows = rows; } @Override public int getRowCount() { return rows.size(); } @Override public int getColumnCount() { return columns.length; } @Override public String getColumnName(int column) { if(column < getColumnCount()) return columns[column]; return super.getColumnName(column); } public E getValueAtRow(int row){ return rows.get(row); } public void setValueAtRow(int row, E object){ rows.set(row, object); } } |
A classe ViewAbstractTableModel é uma classe abstrata, portanto não será possível concluir a observação dos métodos a serem implementados, pois não temos as informações especializadas de um objeto abstraído do mundo real. Assim uma classe que seja filha da ViewAbstractTableModel deverá implementar apenas 2 itens:
- A inicialização do array estático que armazena os nomes das colunas
- Realizar o override do método Object getValueAt(int rowIndex, int columnIndex);
Na próxima parte desse texto iremos criar uma filha da classe ViewAbstractTableModel para representar uma tabela de Enderecos e vamos codificar um pequeno exemplo em Java Swing. Nos vemos na próxima segunda feira, neste nova parte do texto. Obrigado!
Leave a Reply