É certo que o número de usuários Swing Java decaiu muito ao longo dos anos, mas para os que ainda usam a tecnologia padrão do Java para desenvolver interfaces gráficas em nível desktop, conhecer o melhor que o Swing pode fazer por você faz toda a diferença.

Especificamente os componentes gráficos de tabelas, representado em código pela classe JTable, não dispõe de um modo interativo de preencher suas telas como o encontrado em planilhas simples. Adicionando um registro simples, muitas vezes, exige-se que a interface também tenha um botão "Adicionar" ou uma caixa de diálogo pedindo para valores, por exemplo, para em seguida atualizar a JTable em si.

Neste artigo veremos um exemplo de uma tabela de listagem de pessoas contendo Nome, Endereço e Fone, sem a necessidade de um botão "Adicionar". Tudo o que precisamos é o nosso TableModel próprio definido, um TableModelListener, um TableCellRenderer personalizado e alguns métodos auxiliares.

Primeiro vamos criar a classe que conterá os valores das pessoas. Como um Value Object comum (Listagem 1).


package br.edu.devmedia.jtable;

public class RegistroPessoa {

	protected String nome;
	protected String endereco;
	protected String fone;

	public RegistroPessoa() {
		nome = "";
		endereco = "";
		fone = "";
	}

	public String getNome() {
		return nome;
	}

	public void setNome(String nome) {
		this.nome = nome;
	}

	public String getEndereco() {
		return endereco;
	}

	public void setEndereco(String endereco) {
		this.endereco = endereco;
	}

	public String getFone() {
		return fone;
	}

	public void setFone(String fone) {
		this.fone = fone;
	}
}
Listagem 1. Classe RegistroPessoa.java

Em seguida, criaremos o TableModelInterativo, uma classe que será responsável por implementar o AbstractTableModel e criar o nosso próprio modelo de tabela. Veja na Listagem 2 o código para tal.


package br.edu.devmedia.jtable;
import java.util.Vector;
import javax.swing.table.AbstractTableModel;


/**
 * Classe que representa o modelo de tabela a ser seguido para desenhar a tabela
 * principal
 */
public class TableModelInterativo extends AbstractTableModel {

	private static final long serialVersionUID = 1L;

	public static final int INDEX_NOME = 0;
	public static final int INDEX_ENDERECO = 1;
	public static final int INDEX_FONE = 2;
	public static final int INDEX_ESCONDIDO = 3;

	protected String[] nomeColunas;
	protected Vector<RegistroPessoa> vetorDados;

	public TableModelInterativo(String[] columnNames) {
		this.nomeColunas = columnNames;
		vetorDados = new Vector<RegistroPessoa>();
	}

	public String getNomeColuna(int coluna) {
		return nomeColunas[coluna];
	}

	public boolean isCellEditable(int linha, int coluna) {
		if (coluna == INDEX_ESCONDIDO) {
			return false;
		} else {
			return true;
		}
	}

	public Object getValueAt(int linha, int coluna) {
		RegistroPessoa registroPessoa = (RegistroPessoa) vetorDados.get(linha);
		switch (coluna) {
		case INDEX_NOME:
			return registroPessoa.getNome();
		case INDEX_ENDERECO:
			return registroPessoa.getEndereco();
		case INDEX_FONE:
			return registroPessoa.getFone();
		default:
			return new Object();
		}
	}

	public void setValorEm(Object valor, int linha, int coluna) {
		RegistroPessoa record = (RegistroPessoa) vetorDados.get(linha);
		switch (coluna) {
		case INDEX_NOME:
			record.setNome((String) valor);
			break;
		case INDEX_ENDERECO:
			record.setFone((String) valor);
			break;
		case INDEX_FONE:
			record.setFone((String) valor);
			break;
		default:
			System.out.println("invalid index");
		}
		fireTableCellUpdated(linha, coluna);
	}

	public int getRowCount() {
		return vetorDados.size();
	}

	public int getColumnCount() {
		return nomeColunas.length;
	}

	public boolean hasLinhasVazias() {
		if (vetorDados.size() == 0) {
			return false;
		}
		RegistroPessoa registroPessoa =
			(RegistroPessoa) vetorDados.get(vetorDados.size() - 1);
		if (registroPessoa.getNome().trim().equals("")
				&& registroPessoa.getEndereco().trim().equals("")
				&& registroPessoa.getFone().trim().equals("")) {
			return true;
		} else {
			return false;
		}
	}

	public void addLinhaVazia() {
		vetorDados.add(new RegistroPessoa());
		fireTableRowsInserted(vetorDados.size() - 1, vetorDados.size() - 1);
	}
	
}
Listagem 2. Classe TableModelInterativo.java
Nota: A constant INDEX_ESCONDIDO servirá para guardar uma coluna para uso posterior.

Em seguida a classe de formulário (JFrame, Listagem 3) deverá ser criada para fazer as chamadas ao modelo interativo.


package br.edu.devmedia.jtable;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;


/**
 * Classe que produz o form e o gerencia
 */
public class FormInterativo extends JPanel {

	private static final long serialVersionUID = 1L;

	public static final String[] nomeColunas =
			{ "Nome", "Endereço", "Fone", "" };

	protected JTable table;
	protected JScrollPane scroller;
	protected TableModelInterativo modeloInterativo;

	public FormInterativo() {
		iniciarComponentes();
	}

	public void iniciarComponentes() {
		modeloInterativo = new TableModelInterativo(nomeColunas);
		modeloInterativo.addTableModelListener(new FormInterativo.TableModelListenerInterativo());
		table = new JTable();
		table.setModel(modeloInterativo);
		table.setSurrendersFocusOnKeystroke(true);
		if (!modeloInterativo.hasLinhasVazias()) {
			modeloInterativo.addLinhaVazia();
		}

		scroller = new javax.swing.JScrollPane(table);
		table.setPreferredScrollableViewportSize(new java.awt.Dimension(500, 300));
		// Essa coluna ficará escondida até que o Tab seja dado
		TableColumn colunaEscondida = table.getColumnModel().getColumn(TableModelInterativo.INDEX_ESCONDIDO);
		colunaEscondida.setMinWidth(2);
		colunaEscondida.setPreferredWidth(2);
		colunaEscondida.setMaxWidth(2);
		colunaEscondida.setCellRenderer(new InteractiveRenderer(TableModelInterativo.INDEX_ESCONDIDO));

		setLayout(new BorderLayout());
		add(scroller, BorderLayout.CENTER);
	}

	public void destacarUltimaLinha(int linha) {
		int ultimaLinha = modeloInterativo.getRowCount();
		if (linha == ultimaLinha - 1) {
			table.setRowSelectionInterval(ultimaLinha - 1, ultimaLinha - 1);
		} else {
			table.setRowSelectionInterval(linha + 1, linha + 1);
		}

		table.setColumnSelectionInterval(0, 0);
	}

	/**
	 * Classe interna para rendereizar as interações
	 */
	private class InteractiveRenderer extends DefaultTableCellRenderer {

		private static final long serialVersionUID = 1L;
		
		protected int colunaInterativa;

		public InteractiveRenderer(int colunaInterativa) {
			this.colunaInterativa = colunaInterativa;
		}

		public Component getTableCellRendererComponent(JTable table,
				Object valor, boolean isSelecionado, boolean hasFoco, int linha,
				int coluna) {
			Component c =
					super.getTableCellRendererComponent(table, valor,
							isSelecionado, hasFoco, linha, coluna);
			if (coluna == colunaInterativa && hasFoco) {
				if ((FormInterativo.this.modeloInterativo.getRowCount() - 1) == linha
						&& !FormInterativo.this.modeloInterativo.hasLinhasVazias()) {
					FormInterativo.this.modeloInterativo.addLinhaVazia();
				}

				destacarUltimaLinha(linha);
			}

			return c;
		}
	}

	/**
	 * Ouvinte para o modelo interativo
	 */
	public class TableModelListenerInterativo implements TableModelListener {

		/**
		 * Método que recebe os valores de linha modificados
		 */
		public void tableChanged(TableModelEvent evt) {
			if (evt.getType() == TableModelEvent.UPDATE) {
				int column = evt.getColumn();
				int row = evt.getFirstRow();
				table.setColumnSelectionInterval(column + 1, column + 1);
				table.setRowSelectionInterval(row, row);
			}
		}
	}

	/*
	 * Método main
	 */
	public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
			JFrame frame = new JFrame("Formulário Interativo");
			
			frame.addWindowListener(new WindowAdapter() {
				public void windowClosing(WindowEvent evt) {
					System.exit(0);
				}
			});
			frame.getContentPane().add(new FormInterativo());
			frame.pack();
			frame.setVisible(true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
Listagem 3. Classe FormInterativo.java

A fim de se obter o efeito de se mover o cursor da esquerda para a coluna da direita, nós adicionamos um InteractiveTableModelListener para ouvir todas as atualizações feitas no interior das células. Você pode visualizar o uso do mesmo através do método “tableChanged()”, na Listagem 3. Além disso, a linha que contém a chamada ao método “setSurrendersFocusOnKeystroke()” deve ser adicionada depois de inicializar nossa JTable.

Precisamos fazer com que a largura da coluna da tabela de INDEX_ESCONDIDO seja tão pequena quanto possível e ao mesmo tempo a célula da tabela renderizadora também poderia ser chamada quando ele recebe o foco da célula:

A Coluna INDEX_ESCONDIDO atuará como uma célula manequim para interceptar qualquer foco que recebe e em troca ela irá adicionar automaticamente uma nova linha abaixo dele, se ele já não tiver um.

A principal dica para passar da linha superior para a próxima linha é encontrada na classe InteractiveRenderer, que contempla todo o código para tal (Listagem 3).

No final, o resultado da tabela pode ser visualizado na Figura 1.

Tela de Formulário Interativo
Figura 1. Tela de Formulário Interativo

Conclusão

Nesse artigo aprendemos a criar uma JTable interativa em java usando a tecnologia Swing, caso tenham tido alguma dúvida, fiquem a vontade em usar os comentários, espero que tenham gostado e até o próximo artigo.