O JTable é um componente visual utilizado para visualizar dados em forma de grid, com cabeçalho, colunas e linhas e é um dos componentes mais complexos do pacote Swing. O JTable é um componente MVC (Model, View, Controller), deste modo, o seu trabalho é dividido em três partes:

  • Model: Cuida dos dados da tabela, ou seja, é quem controla e distribui os mesmos. É implementado pela interface TableModel.
  • View: Cuida da apresentação da tabela. É implementado pela interface CellRenderer. A apresentação é dada célula a célula da tabela.
  • Controller: Controla a apresentação dos dados na camada view.

Embora, as tabelas sejam componentes extremamente complexos, a JTable encapsula grande parte desta complexidade. Existem algumas maneiras de trabalhar com o componente JTable, neste tutorial será apresentado como construir uma tabela simples populada através de um array bidimensional de objetos, em seguida, uma tabela populada através de dados presentes em um banco de dados utilizando o modelo default da JTable DefaultTableModel e para finalizar uma demonstração de como criar um modelo próprio de tabela sobrescrevendo os métodos da classe AbstractTableModel.

Criando uma tabela simples

Inicialmente é construído um array de Strings contendo os nomes das colunas da tabela:


String [] colunas = {"Nome", "Telefone", "Email"};
Listagem 1. Declaração do cabeçalho da tabela

Em seguida, é necessário criar uma matriz correspondente aos dados do grid. Neste caso, foi construída uma matriz de Objects. Sendo que o primeiro índice representa as linhas e o segundo índice, as colunas.


Object [][] dados = {
	{"Ana Monteiro", "48 9923-7898", "ana.monteiro@gmail.com"},
	{"João da Silva", "48 8890-3345", "joaosilva@hotmail.com"},
	{"Pedro Cascaes", "48 9870-5634", "pedrinho@gmail.com"}
};
Listagem 2. Matriz com os dados que devem popular a JTable

Para colocar os dados mencionados nos tópicos 2 e 3, é necessário criar e instanciar uma JTable. Existem diversos construtores na classe JTable. Para o nosso exemplo, o construtor mais indicado utiliza um array bidimensional contendo os dados e um array contendo os nomes das colunas.


JTable tabela = new JTable(dados, colunas);
Listagem 3. Criação de uma instância da JTable

Para finalizar, é necessário colocar a JTable dentro de um ScrollPanel. O painel de rolagem adiciona automaticamente na parte superior da tabela o cabeçalho.


JScrollPane barraRolagem = new JScrollPane(tabela);
painelFundo.add(barraRolagem);
Listagem 4. Inserindo a tabela dentro de uma barra de rolagem

O código a seguir, demonstra a construção de uma tabela populada com o uso de um array bidimensional.


import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;

/**
 * @author Rosicléia Frasson
 */
public class ListarContatos extends JFrame {
    JPanel painelFundo;
    JTable tabela;
    JScrollPane barraRolagem;
       
    Object [][] dados = {
        {"Ana Monteiro", "48 9923-7898", "ana.monteiro@gmail.com"},
        {"João da Silva", "48 8890-3345", "joaosilva@hotmail.com"},
        {"Pedro Cascaes", "48 9870-5634", "pedrinho@gmail.com"}
    };
    
    String [] colunas = {"Nome", "Telefone", "Email"}; 
    

    public ListarContatos() {
        super ("Contatos");
    }
    
    public void criaJanela(){
        
        painelFundo = new JPanel();
        painelFundo.setLayout(new GridLayout(1, 1));
        tabela = new JTable(dados, colunas);
        barraRolagem = new JScrollPane(tabela);
        painelFundo.add(barraRolagem); 
        
        getContentPane().add(painelFundo);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(500, 120);
        setVisible(true);
    }
    
    public static void main(String[] args) {
        ListarContatos lc = new ListarContatos();
        lc.criaJanela();
    }
}
Listagem 5. Classe que cria e popula uma JTable

A JTable construída anteriormente é extremamente simples, porém, os dados presentes na mesma são totalmente estáticos. Em sua grande maioria, as tabelas são dinâmicas e necessitam de mais versatilidade. Nesses casos, é recomendado o uso do TableModel. O modelo padrão é o DefaultTableModel, assunto do próximo tópico.

DefaultTableModel

O DefaultTableModel é uma classe do pacote javax.swing.table e implementa a interface TableModel, fornecendo todo o controle dos dados da JTable.

Inicialmente vamos criar a classe de negócio Contato.


public class Contato {

	private int id;
	private String nome;
	private String telefone;
	private String email;

	public Contato() {
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getNome() {
		return nome;
	}

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

	public String getTelefone() {
		return telefone;
	}

	public void setTelefone(String telefone) {
		this.telefone = telefone;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}
}
Listagem 6. Classe de negócio Contato

Para efetuar a conexão, é necessário criar uma base de dados no MySQL com o nome de agenda. Na figura abaixo consta o desenho do banco da tabela contato criada na base de dados agenda.

Tabela contato
Figura 1. Tabela contato

Em seguida deve ser criada a fábrica de conexão com o banco de dados. Nesse exemplo, está sendo utilizado o banco de dados MySQL. É importante perceber que nessa classe além do método de conexão com o banco, também estão sobrecarregados três métodos para fechar a conexão: o primeiro deles fecha apenas a Connection, o segundo fecha o PreparedStatement e chama o primeiro para fechar a Connection e o terceiro fecha o ResultSet e chama o segundo para o fechamento do PreparedStatement e a Connection.


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 *
 * @author Rosicléia Frasson
 */
public class FabricaConexao {

	//Dados para a conexão com o banco
	private static final String USUARIO = "root";
	private static final String SENHA = "root";
	private static final String DATABASE = "agenda";
	private static final String DRIVER_CONEXAO = 
	"com.mysql.jdbc.Driver";
	private static final String STR_CONEXAO = 
	"jdbc:mysql://localhost:3306/";

	public static Connection getConexao() throws SQLException, 
	ClassNotFoundException {

		Connection conn = null;
		try {
			Class.forName(DRIVER_CONEXAO);
			conn = DriverManager.getConnection(STR_CONEXAO 
			+ DATABASE, USUARIO, SENHA);

			return conn;

		} catch (ClassNotFoundException e) {
			throw new ClassNotFoundException(
					"Driver MySql não foi encontrado " 
					+ e.getMessage());

		} catch (SQLException e) {
			throw new SQLException("Erro ao conectar "
					+ "com a base de dados" + 
					e.getMessage());
		}
	}

	public static void fechaConexao(Connection conn) {

		try {
			if (conn != null) {
				conn.close();
				System.out.println("Fechada a conexão 
				com o banco de dados");
			}

		} catch (Exception e) {
			System.out.println("Não foi possível fechar 
			a conexão com o banco de dados " + e.getMessage());
		}
	}

	public static void fechaConexao(Connection conn, 
	PreparedStatement stmt) {

		try {
			if (conn != null) {
				fechaConexao(conn);
			}
			if (stmt != null) {
				stmt.close();
				System.out.println("Statement fechado com sucesso");
			}


		} catch (Exception e) {
			System.out.println("Não foi possível fechar o 
			statement " + e.getMessage());
		}
	}

	public static void fechaConexao(Connection conn, 
	PreparedStatement stmt, ResultSet rs) {

		try {
			if (conn != null || stmt != null) {
				fechaConexao(conn, stmt);
			}
			if (rs != null) {
				rs.close();
				System.out.println("ResultSet fechado com sucesso");
			}


		} catch (Exception e) {
			System.out.println("Não foi possível fechar o 
			ResultSet " + e.getMessage());
		}
	}
}
Listagem 7. Classe de conexão com a base de dados

O próximo passo é implementar os métodos para atualização da base de dados.

  • Inicialmente ficam as Strings correspondentes aos comandos SQL (inserção, atualização, remoção e listagem.
  • Após os métodos responsáveis pela inserção, atualização e remoção na base de dados.
  • O método getContatos é responsável por trazer todos os contatos armazenados na base de dados.
  • O método getContatoById busca o contato correspondente ao id enviado por parâmetro na base de dados.

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane;

/**
 *
 * @author Rosicléia Frasson
 */
public class ContatoDao {

	private final String INSERT = "INSERT INTO CONTATO (NOME, TELEFONE, EMAIL) VALUES (?,?,?)";
	private final String UPDATE = "UPDATE CONTATO SET NOME=?, TELEFONE=?, EMAIL=? WHERE ID=?";
	private final String DELETE = "DELETE FROM CONTATO WHERE ID =?";
	private final String LIST = "SELECT * FROM CONTATO";
	private final String LISTBYID = "SELECT * FROM CONTATO WHERE ID=?";

	public void inserir(Contato contato) {
		if (contato != null) {
			Connection conn = null;
			try {
				conn = FabricaConexao.getConexao();
				PreparedStatement pstm;
				pstm = conn.prepareStatement(INSERT);

				pstm.setString(1, contato.getNome());
				pstm.setString(2, contato.getTelefone());
				pstm.setString(3, contato.getEmail());

				pstm.execute();
				JOptionPane.showMessageDialog(null, "Contato cadastrado com sucesso");
				FabricaConexao.fechaConexao(conn, pstm);

			} catch (Exception e) {
				JOptionPane.showMessageDialog(null, "Erro ao inserir contato no banco de"
						+ "dados " + e.getMessage());
			}
		} else {
			System.out.println("O contato enviado por parâmetro está vazio");
		}
	}

	public void atualizar(Contato contato) {
		if (contato != null) {
			Connection conn = null;
			try {
				conn = FabricaConexao.getConexao();
				PreparedStatement pstm;
				pstm = conn.prepareStatement(UPDATE);

				pstm.setString(1, contato.getNome());
				pstm.setInt(2, contato.getId());
				pstm.setString(3, contato.getTelefone());
				pstm.setString(4, contato.getEmail());

				pstm.execute();
				JOptionPane.showMessageDialog(null, "Contato alterado com sucesso");
				FabricaConexao.fechaConexao(conn);

			} catch (Exception e) {
				JOptionPane.showMessageDialog(null, "Erro ao atualizar contato no banco de"
						+ "dados " + e.getMessage());
			}
		} else {
			JOptionPane.showMessageDialog(null, "O contato enviado por parâmetro está vazio");
		}


	}

	public void remover(int id) {
		Connection conn = null;
		try {
			conn = FabricaConexao.getConexao();
			PreparedStatement pstm;
			pstm = conn.prepareStatement(DELETE);

			pstm.setInt(1, id);

			pstm.execute();
			FabricaConexao.fechaConexao(conn, pstm);

		} catch (Exception e) {
			JOptionPane.showMessageDialog(null, "Erro ao excluir contato do banco de"
					+ "dados " + e.getMessage());
		}
	}

	public List<Contato> getContatos() {
		Connection conn = null;
		PreparedStatement pstm = null;
		ResultSet rs = null;
		ArrayList<Contato> contatos = new ArrayList<Contato>();
		try {
			conn = FabricaConexao.getConexao();
			pstm = conn.prepareStatement(LIST);
			rs = pstm.executeQuery();
			while (rs.next()) {
				Contato contato = new Contato();

				contato.setId(rs.getInt("id"));
				contato.setNome(rs.getString("nome"));
				contato.setTelefone(rs.getString("telefone"));
				contato.setEmail(rs.getString("email"));
				contatos.add(contato);
			}
			FabricaConexao.fechaConexao(conn, pstm, rs);
		} catch (Exception e) {
			JOptionPane.showMessageDialog(null, "Erro ao listar contatos" + e.getMessage());
		}
		return contatos;
	}

	public Contato getContatoById(int id) {
		Connection conn = null;
		PreparedStatement pstm = null;
		ResultSet rs = null;
		Contato contato = new Contato();
		try {
			conn = FabricaConexao.getConexao();
			pstm = conn.prepareStatement(LISTBYID);
			pstm.setInt(1, id);
			rs = pstm.executeQuery();
			while (rs.next()) {
				contato.setId(rs.getInt("id"));
				contato.setNome(rs.getString("nome"));
				contato.setTelefone(rs.getString("telefone"));
				contato.setEmail(rs.getString("email"));
			}
			FabricaConexao.fechaConexao(conn, pstm, rs);
		} catch (Exception e) {
			JOptionPane.showMessageDialog(null, "Erro ao listar contatos" + e.getMessage());
		}
		return contato;
	}
}
Listagem 8. Classe que executa os comandos sql para atualizações na base de dados

Com as classes de negócio e persistência criadas é hora de partir para a visualização. A classe ListarContato é responsável por exibir uma tabela com os contatos cadastrados na base de dados e chama os formulários de inserção, atualização e remoção de um contato.

O método criaJTable instancia um objeto do tipo JTable, adiciona colunas à tabela - addColumn- e seta seus respectivos tamanhos - setPreferredWidth.

O método pesquisar é responsável por chamar o método getContatos da classe ContatoDAO e adicionar os contatos vindos do banco à tabela. Isto é feito através do método addRow. Antes de adicionar os dados na tabela, exclui-se todas as linhas presente nesta. Isso é feito através do método setNumRows que seta em zero o número de linhas. Isto é feito para que não se dupliquem dados cada vez que o método pesquisar é invocado. Um outro detalhe é que o método pesquisar está declarado como static. A declaração foi feita desta forma porque outras classes precisam acessar o método, veremos isso adiante.

No evento do botão inserir, como é possível observar no código, ao clicar no botão inserir, o formulário de inserção de um novo contato é exibido na tela. Para o construtor da classe InserirContato é necessário enviar o modelo da tabela para que a mesma seja reconstruída.

No botão atualizar, como necessita-se saber qual o contato deve ser atualizado, é indispensável que uma linha da tabela esteja selecionada. O método getSelectedRow retorna o id referente à linha selecionada. Com o método getValueAt é possível retornar o valor do dado preenchido na tabela, desde que passados os parâmetros linha e coluna correspondentes. Após a obtenção desses dados o formulário de edição é exibido na tela. Para o formulário da edição do contato é necessário enviar o modelo da tabela, para que sua visualização seja alterada, o id do contato a ser alterado e a linha selecionada.

No botão excluir,primeiramente é necessário identificar o id do contato a ser removido. Isso é feito com a junção dos métodos getSelectedRow, que retorna o id da linha selecionada e o método getValueAt que retorna o valor presente na linha e coluna enviados por parâmetro. Em posse do id do Contato chama-se o método remover implementado na classe ContatoDAO, para efetuar a remoção da base de dados. Em seguida, o método removeRow é invocado para excluir a linha da tabela.


import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

/**
 * @author Rosicléia Frasson
 */
public class ListarContato extends JFrame {

	private JPanel painelFundo;
	private JPanel painelBotoes;
	private JTable tabela;
	private JScrollPane barraRolagem;
	private JButton btInserir;
	private JButton btExcluir;
	private JButton btEditar;
	private DefaultTableModel modelo = new DefaultTableModel();

	public ListarContato() {
		super("Contatos");
		criaJTable();
		criaJanela();
	}

	public void criaJanela() {
		btInserir = new JButton("Inserir");
		btExcluir = new JButton("Excluir");
		btEditar = new JButton("Editar");
		painelBotoes = new JPanel();
		barraRolagem = new JScrollPane(tabela);
		painelFundo = new JPanel();
		painelFundo.setLayout(new BorderLayout());
		painelFundo.add(BorderLayout.CENTER, barraRolagem);
		painelBotoes.add(btInserir);
		painelBotoes.add(btEditar);
		painelBotoes.add(btExcluir);
		painelFundo.add(BorderLayout.SOUTH, painelBotoes);

		getContentPane().add(painelFundo);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setSize(500, 320);
		setVisible(true);
		btInserir.addActionListener(new BtInserirListener());
		btEditar.addActionListener(new BtEditarListener());
		btExcluir.addActionListener(new BtExcluirListener());
	}

	private void criaJTable() {
		tabela = new JTable(modelo);
		modelo.addColumn("Id");
		modelo.addColumn("Nome");
		modelo.addColumn("Telefone");
		modelo.addColumn("Email");
		tabela.getColumnModel().getColumn(0)
		.setPreferredWidth(10);
		tabela.getColumnModel().getColumn(1)
		.setPreferredWidth(120);
		tabela.getColumnModel().getColumn(1)
		.setPreferredWidth(80);
		tabela.getColumnModel().getColumn(1)
		.setPreferredWidth(120);
		pesquisar(modelo);
	}

	public static void pesquisar(DefaultTableModel modelo) {
		modelo.setNumRows(0);
		ContatoDao dao = new ContatoDao();

		for (Contato c : dao.getContatos()) {
			modelo.addRow(new Object[]{c.getId(), c.getNome(), 
			c.getTelefone(), c.getEmail()});
		}
	}

	private class BtInserirListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			InserirContato ic = new InserirContato(modelo);
			ic.setVisible(true);
		}
	}

	private class BtEditarListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			int linhaSelecionada = -1;
			linhaSelecionada = tabela.getSelectedRow();
			if (linhaSelecionada >= 0) {
				int idContato = (int) tabela
				.getValueAt(linhaSelecionada, 0);
				AtualizarContato ic = 
				new AtualizarContato(modelo, idContato, linhaSelecionada);
				ic.setVisible(true);
			} else {
				JOptionPane.showMessageDialog(null, 
				"É necesário selecionar uma linha.");
			}
		}
	}

	private class BtExcluirListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			int linhaSelecionada = -1;
			linhaSelecionada = tabela.getSelectedRow();
			if (linhaSelecionada >= 0) {
				int idContato = (int) 
				tabela.getValueAt(linhaSelecionada, 0);
				ContatoDao dao = new ContatoDao();
				dao.remover(idContato);
				modelo.removeRow(linhaSelecionada);
			} else {
				JOptionPane.showMessageDialog(null, 
				"É necesário selecionar uma linha.");
			}
		}
	}

	public static void main(String[] args) {
		ListarContato lc = new ListarContato();
		lc.setVisible(true);
	}
}
Listagem 9. Classe que é responsável por exibir a JTable

Na visualização, existe uma classe responsável por exibir o formulário de inserção de um novo contato.

Quando o botão salvar é clicado, todos os dados presentes nas caixas de texto são armazenados nos respectivos atributos de um contato. Em seguida, o método inserir da classe ContatoDAO é invocado, efetuando a inserção na base de dados. Como o id do contato é gerado apenas na base de dados, torna-se necessária a consulta na mesma para o preenchimento deste novo contato na tabela. Deste modo, o método pesquisar criado na classe ListarContato é invocado.


import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableModel;

/**
 *
 * @author Rosicléia Frasson
 */
public class InserirContato extends JFrame {
	private DefaultTableModel modelo = new DefaultTableModel();
	private JPanel painelFundo;
	private JButton btSalvar;
	private JButton btLimpar;
	private JLabel lbNome;
	private JLabel lbTelefone;
	private JLabel lbEmail;
	private JTextField txNome;
	private JTextField txTelefone;
	private JTextField txEmail;

	public InserirContato(DefaultTableModel md) {
		super("Contatos");
		criaJanela();
		modelo = md;
	}

	public void criaJanela() {
		btSalvar = new JButton("Salvar");
		btLimpar = new JButton("Limpar");
		lbNome = new JLabel("         Nome.:   ");
		lbTelefone = new JLabel("         Telefone.:   ");
		lbEmail = new JLabel("         Email.:   ");
		txNome = new JTextField(10);
		txTelefone = new JTextField();
		txEmail = new JTextField();

		painelFundo = new JPanel();
		painelFundo.setLayout(new GridLayout(4, 2, 2, 4));
		painelFundo.add(lbNome);
		painelFundo.add(txNome);
		painelFundo.add(lbTelefone);
		painelFundo.add(txTelefone);
		painelFundo.add(lbEmail);
		painelFundo.add(txEmail);
		painelFundo.add(btLimpar);
		painelFundo.add(btSalvar);

		getContentPane().add(painelFundo);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		setSize(300, 150);
		setVisible(true);
		btSalvar.addActionListener(new BtSalvarListener());
		btLimpar.addActionListener(new BtLimparListener());
	}

	private class BtSalvarListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			Contato c = new Contato();
			c.setNome(txNome.getText());
			c.setTelefone(txTelefone.getText());
			c.setEmail(txEmail.getText());

			ContatoDao dao = new ContatoDao();
			dao.inserir(c);
			ListarContato.pesquisar(modelo);

			setVisible(false);
		}
	}



	private class BtLimparListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			txNome.setText("");
			txTelefone.setText("");
			txEmail.setText("");
		}
	}
}
Listagem 10. Classe responsável por exibir um formulário para inserção de um novo contato

Para finalizar, falta a classe correspondente ao formulário de atualização.

O tratamento do evento do botão salvar ocorre de forma similar ao mesmo botão do formulário de inserção. Porém, como o id do contato não pode ser alterado, apenas os demais dados, não é necessário fazer uma consulta na base de dados para reconstruir a tabela. Para a atualização da linha referente ao contato editado, a mesma é excluída e adicionada com os novos dados. Isso é possível com a utilização dos métodos removeRow e addRow.


import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableModel;

/**
 *
 * @author Rosicléia Frasson
 */
public class AtualizarContato extends JFrame {

	private DefaultTableModel modelo = 
	new DefaultTableModel();
	private JPanel painelFundo;
	private JButton btSalvar;
	private JButton btLimpar;
	private JLabel lbNome;
	private JLabel lbTelefone;
	private JLabel lbEmail;
	private JLabel lbId;
	private JTextField txNome;
	private JTextField txId;
	private JTextField txTelefone;
	private JTextField txEmail;
	Contato contato;
	private int linhaSelecionada;

	public AtualizarContato(DefaultTableModel md, 
	int id, int linha) {
		super("Contatos");
		criaJanela();
		modelo = md;
		ContatoDao dao = new ContatoDao();
		contato = dao.getContatoById(id);
		txId.setText(Integer.toString(contato.getId()));
		txNome.setText(contato.getNome());
		txTelefone.setText(contato.getTelefone());
		txEmail.setText(contato.getEmail());
		linhaSelecionada = linha;  
	}

	public void criaJanela() {
		btSalvar = new JButton("Salvar");
		btLimpar = new JButton("Limpar");
		lbNome = new JLabel("         Nome.:   ");
		lbTelefone = new JLabel("         Telefone.:   ");
		lbEmail = new JLabel("         Email.:   ");
		lbId = new JLabel("         Id.:   ");
		txNome = new JTextField();
		txTelefone = new JTextField();
		txEmail = new JTextField();
		txId = new JTextField();
		txId.setEditable(false);

		painelFundo = new JPanel();
		painelFundo.setLayout(new GridLayout(5, 2, 2, 4));
		painelFundo.add(lbId);
		painelFundo.add(txId);
		painelFundo.add(lbNome);
		painelFundo.add(txNome);
		painelFundo.add(lbTelefone);
		painelFundo.add(txTelefone);
		painelFundo.add(lbEmail);
		painelFundo.add(txEmail);
		painelFundo.add(btLimpar);
		painelFundo.add(btSalvar);

		getContentPane().add(painelFundo);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		setSize(300, 150);
		setVisible(true);

		btSalvar.addActionListener(new 
		AtualizarContato.BtSalvarListener());
		btLimpar.addActionListener(new 
		AtualizarContato.BtLimparListener());
	}

	private class BtSalvarListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			Contato c = new Contato();
			c.setId(Integer.parseInt(txId.getText()));
			c.setNome(txNome.getText());
			c.setTelefone(txTelefone.getText());
			c.setEmail(txEmail.getText());

			ContatoDao dao = new ContatoDao();
			dao.atualizar(c);
			modelo.removeRow(linhaSelecionada);
			modelo.addRow(new Object[]{c.getId(), 
			c.getNome(), c.getTelefone(), c.getEmail()});
			setVisible(false);
		}
	}

	private class BtLimparListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			txNome.setText("");
			txTelefone.setText("");
			txEmail.setText("");
		}
	}
}
Listagem 11. Classe responsável por exibir o formulário de edição de um contato

Como visto, a classe DefaultTableModel funciona bem para alguns casos. Porém se existe a necessidade de algo mais personalizado na manipulação da JTable é necessário criar um modelo próprio de TableModel, assunto que será discutido adiante.

AbstractTableModel

No exemplo anterior, utilizamos como modelo de tabela uma instância de DefaultTableModel - uma classe já implementada na API do Java. Porém, com o uso da mesma possuímos algumas limitações, principalmente quando deseja-se algo diferente do que está implementado no DefaultTableModel. AbstractTableModel é uma classe abstrata que possui implementação de alguns métodos da interface TableModel. Desta forma, torna-se mais interessante implementar uma classe que estende de AbstractTableModel ao invés de implementar TableModel diretamente.Segue a seguir um exemplo de construção de um TableModel.

As classes FabricaConexao e Contato continuam exatamente como mostrado no tópico anterior do tutorial.

Na classe ContatoDao deve ser inserido um método que faça uma pesquisa nos contatos cadastrados informando o nome e o telefone do contato. Essa pesquisa será necessária para atualizar a tabela no momento da inserção de um novo contato.

Primeiramente deve ser declarada uma String com o comando necessário para selecionar da base de dados os contatos segundo o nome e o telefone desejado.


private final String LISTBYNOMEFONE = "SELECT * FROM CONTATO WHERE NOME=? AND TELEFONE=?"
Listagem 12. Código SQL para pesquisar um contato por nome e telefone

Em seguida, deve ser construído o método para executar o comando sql listado acima.


public Contato getContatoByNomeTel(Contato c) {
	Connection conn = null;
	PreparedStatement pstm = null;
	ResultSet rs = null;
	Contato contato = new Contato();
	try {
		conn = FabricaConexao.getConexao();
		pstm = conn.prepareStatement(LISTBYNOMEFONE);
		pstm.setString(1, c.getNome());
		pstm.setString(2, c.getTelefone());
		rs = pstm.executeQuery();
		while (rs.next()) {
			contato.setId(rs.getInt("id"));
			contato.setNome(rs.getString("nome"));
			contato.setTelefone(rs.getString("telefone"));
			contato.setEmail(rs.getString("email"));

		}
		FabricaConexao.fechaConexao(conn, pstm, rs);
	} catch (Exception e) {
		JOptionPane.showMessageDialog(null, 
		"Erro ao listar contatos" + e.getMessage());
	}
	return contato;
}
Listagem 13. Método que executa o SQL que pesquisa um contato por nome e telefone

A classe AbstractTableModel fornece implementação padrão para a maioria dos métodos da interface TableModel. Para criar uma classe TableModel concreta como uma subclasse de AbstractTableModel é necessário fornecer a implementação dos métodos getRowCount, getColumnCount e getValueAt. Outros métodos podem ser sobrescritos ou criados conforme a necessidade.

Inicialmente é necessário declarar as colunas que compõem a tabela, atribuindo os respectivos índices.

É imprescindível criar uma lista que guarda os dados referentes as linhas da tabela. Esta lista é preenchida com os dados enviados por parâmetro para o construtor.

Também é necessário um vetor onde será armazenado os dados que definem o texto do cabeçalho da tabela.

O método getRowCount retorna a quantidade total de colunas que a JTable deve usar para montar a tabela. Como os dados estão armazenados na List linhas, o método retorna o tamanho da mesma.

O método getColumnCount deve retornar a quantidade total de colunas que a JTable deve usar para montar a tabela. No nosso caso, o cabeçalho das colunas encontra-se armazenado no vetor colunas, desta forma, o método retorna o tamanho do vetor colunas.

O método getColumnName retorna o nome da coluna referente ao índice especificado por parâmetro. Como os nomes das colunas estão armazenados no vetor colunas, o método retorna a String armazenada na posição especificada no parâmetro.

O método getColumnClass retorna o tipo de dado referente a coluna especificada no parâmetro.

O método isCellEditable retorna um valor booleano indicando se a célula especificada no parâmetro pode ter seu valor alterado. Isto quer dizer que ao clicar em uma célula da tabela, a mesmo não pode ser editada.

O método getValueAt retorna o conteúdo da célula especificada no parâmetro.

O método setValueAt seta um novo valor para a célula especificada. Este método possui efeito apenas se o método isCellEditable retornar true.

O método getContato retorna o contato presente na linha especificada no parâmetro.

O método addContato adiciona uma nova linha à tabela com o contato especificado no parâmetro. Para que isso ocorra é necessário adicionar o contato a List linhas, após encontrar o último índice da tabela (quantidade de linhas - 1, pois o índice começa a ser contado a partir de zero) e notificar a mudança à JTable através do método fireTableRowsInserted que informa que as linhas na faixa especificada por parâmetro devem ser adicionadas.

O método updateContato atualiza a linha especificada por parâmetro na List linhas. Para a mudança ocorrer na JTable é usado o método fireTableRowsUpdated que informa que as linhas na faixa especificada por parâmetro tiveram seu valor alterado.

O método removeContato exclui da List de linhas o contato enviado por parâmetro ao método. Para a mudança ocorrer na JTable é necessário notificar que as linhas na faixa especificada devem ser removidas. Isto é feito através do método fireTableRowsDeleted.


import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;

/**
 *
 * @author Rosicléia Frasson
 */
public class ContatoTableModel extends AbstractTableModel {

	private static final int COL_ID = 0;
	private static final int COL_NOME = 1;
	private static final int COL_TELEFONE = 2;
	private static final int COL_EMAIL = 3;
	List<Contato> linhas;
	private String[] colunas = new String[]{"Id", "Nome", 
	"Telefone", "Email"};

	public ContatoTableModel(List<Contato> contatos) {
		this.linhas = new ArrayList<>(contatos);
	}

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

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

	public String getColumnName(int columnIndex) {
		return colunas[columnIndex];
	}

	public Class getColumnClass(int columnIndex) {
		if (columnIndex == COL_ID) {
			return Integer.class;
		}
		return String.class;
	}

	public boolean isCellEditable(int rowIndex, int 
	columnIndex) {
		return false;
	}

	public Object getValueAt(int row, int column) {

		Contato m = linhas.get(row);

		if (column == COL_ID) {
			return m.getId();
		} else if (column == COL_NOME) {
			return m.getNome();
		} else if (column == COL_TELEFONE) {
			return m.getTelefone();
		} else if (column == COL_EMAIL) {
			return m.getEmail();
		}
		return "";
	}

	public void setValueAt(Object aValue, int row, 
	int column) {
		Contato u = linhas.get(row);
		if (column == COL_ID) {
			u.setId((Integer) aValue);
		} else if (column == COL_NOME) {
			u.setNome(aValue.toString());
		} else if (column == COL_TELEFONE) {
			u.setTelefone(aValue.toString());
		} else if (column == COL_EMAIL) {
			u.setEmail(aValue.toString());
		}
	}

	public Contato getContato(int indiceLinha) {
		return linhas.get(indiceLinha);
	}

	public void addContato(Contato contato) {
		linhas.add(contato);
		int ultimoIndice = getRowCount() - 1;
		fireTableRowsInserted(ultimoIndice, 
		ultimoIndice);

	}

	public void updateContato(int indiceLinha, Contato marca) {
		linhas.set(indiceLinha, marca);
		fireTableRowsUpdated(indiceLinha, 
		indiceLinha);

	}

	public void removeContato(int indiceLinha) {
		linhas.remove(indiceLinha);
		fireTableRowsDeleted(indiceLinha, 
		indiceLinha);

	}
}
Listagem 14. Implementação de um modelo de JTable

Na classe ListarContato são necessárias duas variáveis para controlar o model da tabela. Uma variável do tipo ContatoTableModel e uma List de contatos.


private ContatoTableModel modelo
List<Contato> lista
Listagem 15. Declaração de variáveis que auxiliam o controle do modelo da tabela

Ainda na classe ListarContato são criados os métodos criaJTable e pesquisar. O método criaJTable é invocado no construtor da classe e instância a tabela criada utilizando uma instância da classe ContatoTableModel. Este método também invoca o método pesquisar que é responsável por preencher a lista de contatos.


private void criaJTable() {
	tabela = new JTable(modelo);
	pesquisar();

}

private void pesquisar() {
	ContatoDao dao = new ContatoDao();
	lista = dao.getContatos();
	modelo = new ContatoTableModel(lista);
	tabela.setModel(modelo);      
}
Listagem 16. Métodos responsáveis por preencher a tabela com dados advindos da base de dados

Os eventos dos botões de inserção, remoção e atualização de contatos também possuem algumas modificações:

  • No botão inserir a classe InserirContato deve ser instanciada enviando como parâmetro a instância de ContatoTableModel criada.
  • No botão editar a classe AtualizarContato deve ser instanciada enviando como parâmetro a instância de ContatoTableModel criada.
  • No botão excluir como não é chamada nenhuma outra tela, após o contato ser excluído da base de dados, o método removeContato da classe ContatoTableModel é invocado.

private class BtInserirListener implements ActionListener {		
	public void actionPerformed(ActionEvent e) {
		InserirContato ic = new InserirContato(modelo);
		ic.setVisible(true);
	}
}

private class BtEditarListener implements ActionListener {

	public void actionPerformed(ActionEvent e) {
		int linhaSelecionada = -1;
		linhaSelecionada = tabela.getSelectedRow();
		if (linhaSelecionada >= 0) {
			int idContato = (int) tabela.getValueAt(linhaSelecionada, 0);
			AtualizarContato ic = new AtualizarContato(modelo, idContato, linhaSelecionada);
			ic.setVisible(true);
		} else {
			JOptionPane.showMessageDialog(null, "É necessário selecionar uma linha.");
		}
	}
}

private class BtExcluirListener implements ActionListener {

	public void actionPerformed(ActionEvent e) {
		int linhaSelecionada = -1;
		linhaSelecionada = tabela.getSelectedRow();
		if (linhaSelecionada >= 0) {
			int idContato = (int) tabela.getValueAt(linhaSelecionada, 0);
			ContatoDao dao = new ContatoDao();
			dao.remover(idContato);

			modelo.removeContato(linhaSelecionada);
		} else {
			JOptionPane.showMessageDialog(null, "É necessário selecionar uma linha.");
		}
	}
} 
Listagem 17. Métodos responsáveis pelos eventos do botões inserir, atualizar e remover contato

Na classe InserirContato, como o id é gerado automaticamente pela base de dados não é possível saber qual o id do contato inserido sem efetuar uma pesquisa na base de dados. Por este motivo, após a inserção, um método que pesquisa um contato pelo nome e telefone é invocado para retornar o id do mesmo. Com este retorno é possível adicionar uma linha na tabela chamando o método addContato (linha 12).


private class BtSalvarListener implements ActionListener {

	public void actionPerformed(ActionEvent e) {
		Contato c = new Contato();
		c.setNome(txNome.getText());
		c.setTelefone(txTelefone.getText());
		c.setEmail(txEmail.getText());

		ContatoDao dao = new ContatoDao();
		dao.inserir(c);
		pesquisar(c);
		modelo.addContato(pesquisar(c));
		setVisible(false);
	}
}
public Contato pesquisar(Contato co) {
	ContatoDao dao = new ContatoDao();
	return dao.getContatoByNomeTel(co);
}
Listagem 18. Método responsável pela atualização da tabela quando um novo contato é inserido

Na classe AtualizarContato, após o contato ser atualizado na base de dados, o método updateContato da classe ContatoTableModel é invocado para efetuar a atualização na tabela.


private class BtSalvarListener implements ActionListener {

	public void actionPerformed(ActionEvent e) {
		Contato c = new Contato();
		c.setId(Integer.parseInt(txId.getText()));
		c.setNome(txNome.getText());
		c.setTelefone(txTelefone.getText());
		c.setEmail(txEmail.getText());

		ContatoDao dao = new ContatoDao();
		dao.atualizar(c);

		modelo.updateContato(linhaSelecionada, c);
		setVisible(false);
	}
}
Listagem 19. Método responsável pela atualização da tabela quando um contato é atualizado

Pelo que o artigo demonstrou existem diferentes maneiras de se popular uma JTable. Cabe ao programador escolher a melhor forma, analisando o problema a ser resolvido. Espero que tenham gostado e até a próxima.

Os códigos fontes dos exemplos podem ser baixados no link no topo desse artigo ou podem ser acessados no GitHub.