Finalizando implementação JTable [RESOLVIDO]

Fala, pessoal.
Tava quebrando a cabeça aqui pra ajeitar o JTable e agora consegui fazer o básico funcionar.

Mas ainda tem uns erros:

1. Quando já existe um objeto na tabela e eu adiciono outro, os dados na tabela do objeto que estava lá são substituídos pelos dados do novo objeto.[Resolvido]

2. Quando eu clico remover sem selecionar nenhum objeto na tabela o último objeto é removido. Eu quero que só possa ser removido quando for selecionado com clique e os dados do objeto (vindos da tabela, que vêm do banco) já estejam nos campos.[Resolvido]

3. Quero que a JTable seja só pra visualização rápida e pra seleção de clientes cadastrados. E ao clicar numa linha da JTable os JTextFields serão preenchidos com os dados completos do objeto. [Não implementado, ainda. É só usar o get do ClientesTableModel][Resolvido]

ClientesTableModel (baseado nesse exemplo: http://www.guj.com.br/java/132698-jtable-removendo-colunas-em-tempo-de-execucao#714736):

[code]package visao;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import modelo.Cliente;

public class ClientesTableModel<T> extends AbstractTableModel {  
    //Nome das colunas
    private static final int COL_NOME = 0;  
    private static final int COL_CIDADE = 1;  
    private static final int COL_EMAIL = 2;  
    private static final int COL_TELEFONE = 3;  
  
    //Lista dos clientes que serão exibidos.
    private List<Cliente> clientes;         
  
    public ClientesTableModel(){
        this.clientes = new ArrayList<Cliente>();
    }
    
    //Construtor que recebe a lista de clientes  
    public ClientesTableModel(List<Cliente> lista) {  
        this();
        this.clientes.addAll(lista); 
    }  
  
    public int getRowCount() {  
        //Retorna o número de linhas da tabela. Uma linha para cada item na lista de clientes.
        return clientes.size();  
    }  
  
    public int getColumnCount() {  
        //Retorna o números de colunas da tabela. Nesse caso, 4.
        return 4;  
    }  
  
    public String getColumnName(int columnIndex) {  
        //Qual é o nome das nossas colunas?  
        //String colunas[] = {"Nome", "Cidade", "E-mail", "Telefone"};
        if (columnIndex == COL_NOME) return "Nome";  
        if (columnIndex == COL_CIDADE) return "Cidade";
        if (columnIndex == COL_EMAIL) return "E-mail";
        if (columnIndex == COL_TELEFONE) return "Telefone";
        return ""; //Nunca deve ocorrer
        //return colunas[columnIndex];
    }  
    
    public Class<?> getColumnClass(int columnIndex) {  
        //Qual a classe das nossas colunas?
        /*Como nesse caso só serão exibidas colunas String, não é necessário fazer a checagem.
        if (columnIndex == COL_NOME) return String.class;  
        else if (columnIndex == COL_CIDADE) return String.class;
        else if (columnIndex == COL_EMAIL) return String.class;
        else if (columnIndex == COL_TELEFONE) return String.class;*/
        //return null; //Nunca deve acontecer
        return String.class;
    } 
  
    public Object getValueAt(int row, int column) {  
        //Precisamos retornar o valor da coluna column e da linha row.  
        Cliente cliente = clientes.get(row);  
        if (column == COL_NOME) return cliente.getNome();  
        else if (column == COL_CIDADE) return cliente.getCEP();  
        else if (column == COL_EMAIL) return cliente.getEmail();
        else if (column == COL_TELEFONE) return cliente.getTel();
        return ""; //Nunca deve ocorrer  
    }  
  
@Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {  
        //Vamos alterar o valor da coluna columnIndex na linha rowIndex com o valor aValue passado no parâmetro.
        Cliente cliente = clientes.get(rowIndex);
        if (columnIndex== COL_NOME) cliente.setNome(aValue.toString());  
        else if (columnIndex== COL_CIDADE) cliente.setCidade(aValue.toString());  
        else if (columnIndex== COL_EMAIL) cliente.setEmail(aValue.toString());  
        else if (columnIndex== COL_TELEFONE) cliente.setTel(aValue.toString());
        
        //Avisa que os dados mudaram
        fireTableDataChanged();
    } 
  

      
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {  
    //Indicamos se a célula da rowIndex e da columnIndex é editável.
    return false;
}
    
    public List<Cliente> getClientes() {  
        return Collections.unmodifiableList(clientes);  
    } 
    
    //Insere um novo objeto na tabela.
    public void inserir(Cliente cliente) {  
        clientes.add(cliente);   //Adicionamos na lista  
        fireTableDataChanged(); //Redesenha o modelo.
    }  
    
    //Remove um objeto da tabela.
    public void remover(Cliente cliente) {  
        int index = clientes.indexOf(cliente);
        if (index == -1)  //Se o cliente não estiver na tabela  
            return;           //Saimos sem fazer nada  
        clientes.remove(index);   //Caso contrário, removemos da lista  
        fireTableDataChanged(); //Repintamos a linha  
}  
    
    //Já que esse tableModel é de clientes, vamos fazer um get que retorne um cliente inteiro.  
    //Isso elimina a necessidade de chamar o getValueAt() nas telas.   
    public Cliente get(int row) {  
        return clientes.get(row);  
    }  
    
    public Cliente getCliente(int pos){
        if (pos < 0 || pos >= clientes.size())
            return null;
        
        return clientes.get(pos);
    }
}  [/code]

TelaCliente:

[code]/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */

/*

  • TelaCliente.java
  • Created on 01/07/2011, 14:39:40
    */
    package visao;

import java.awt.Component;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
import modelo.Cliente;
import visao.ClientesTableModel;

public class TelaCliente extends javax.swing.JDialog {

/** Creates new form TelaCliente */
public TelaCliente(java.awt.Frame parent, boolean modal) {
    super(parent, modal);
    
    initComponents();
    this.jTableClientes.setModel(new ClientesTableModel());
}


private ClientesTableModel tableModelCliente(List<Cliente> clientes){
    return new ClientesTableModel(clientes);  
}

public void adicionaItemTabela(Cliente c){
    pegaModelo().inserir(c);
}

public void removeItemTabela(Cliente c){
    pegaModelo().remover(c);
}

public ClientesTableModel pegaModelo(){
    return (ClientesTableModel)this.jTableClientes.getModel();
}

private Cliente getClienteDaLinhaSelecionada() {  
    if (this.jTableClientes.getSelectedRow() == -1) {  
        return null;  
    }  
    Cliente u = (Cliente) pegaModelo().getClientes().get(this.jTableClientes.getSelectedRow());
} 

public void clearFields() {  
    Component[] componentes = this.getContentPane().getComponents();  
      
    for (int i = 0; i < componentes.length; i++) {  
        if (componentes[i] instanceof JTextField) {  
            JTextField field = (JTextField)componentes[i];  
            field.setText("");  
        }  
    }  
}  
 
public void escuta(ActionListener e){
    this.jButtonAlterar.addActionListener(e);
    this.jButtonCadastrar.addActionListener(e);
    this.jButtonExcluir.addActionListener(e);
}

//...

[/code]

2. Quando eu clico remover sem selecionar nenhum objeto na tabela o último objeto é removido. Eu quero que só possa ser removido quando for selecionado com clique e os dados do objeto (vindos da tabela, que vêm do banco) já estejam nos campos.
Resolvido.

Na classe ControleCliente pus isso:

else if(e.getActionCommand().equals("Remover")){
    //...
    c = tc.getClienteDaLinhaSelecionada();
    if(c != null)
        tc.removeItemTabela(c);
    else
        tc.mensagem("Selecione um cliente na tabela");

Poste o código do botão onde vc faz a inserção. Você está criando lá um cliente com “new”?

Basta usar um listener de seleção:

seuTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; c = tc.getClienteDaLinhaSelecionada(); //Preencha aqui seus JTextField com os dados de C. } });

Poste o código do botão onde vc faz a inserção. Você está criando lá um cliente com “new”?
[/quote]
Não. Eu instancio um cliente no construtor da classe ControleCliente e também passo ela mesma como o ActionListener pra TelaCliente(escuta(this); )

[code]/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */
    package controle;

//…

public class ControleCliente implements ActionListener {

private TelaCliente tc;
private Cliente c;
private DAOGenerico<Cliente> daog;

public ControleCliente (TelaCliente tc){
    this.c=new Cliente();
    this.tc=tc;
    tc.escuta(this);
    tc.setVisible(true);
}
//...

}

[/code]

Aí no actionPerformed, se o comando é cadastrar, eu seto os dados do cliente, retirados dos textfields, e chamo o método adicionarItemTabela passando o cliente.

    //...
    public void actionPerformed(ActionEvent e){
        
        if(e.getActionCommand().equals("Cadastrar")){
            
            
            //daog = new DAOGenerico<Cliente>();
            c.setBairro(tc.getjTextFieldBairro().getText());
            c.setCEP(tc.getjTextFieldCEP().getText());
            c.setCidade(tc.getjTextFieldCidade().getText());
            c.setEmail(tc.getjTextFieldEMail().getText());
            c.setEnd(tc.getjTextFieldEndereco().getText());
            c.setEstado(tc.getjTextFieldEstado().getText());
            c.setFax(tc.getjTextFieldFax().getText());
            c.setNome(tc.getjTextFieldNome().getText());
            c.setTel(tc.getjTextFieldTelefone().getText());
            c.setContato(tc.getjTextFieldContato().getText());
            tc.adicionaItemTabela(c);
            //try{
            //    daog.save(c);
            //}catch(NullPointerException exc){
            //    System.out.println(exc);
            //}
            
            
            
        }
    //...
}

PS: Perdão, vou parar de dar quote porque isso fica muito grande e ruim de enxergar.

O que faz o método adicionaItemTabela?

3. Quero que a JTable seja só pra visualização rápida e pra seleção de clientes cadastrados. E ao clicar numa linha da JTable os JTextFields serão preenchidos com os dados completos do objeto. [Não implementado, ainda. É só usar o get do ClientesTableModel]Resolvido.

Resolvido da forma como o Vinny sugeriu (eu já tinha imaginado isso, mas não conhecia esse ListSelecionListener. muito bom!).

Método escuta (TelaCliente):

public void escuta(ActionListener e, ListSelecionListener e2){ this.jButtonAlterar.addActionListener(e); this.jButtonCadastrar.addActionListener(e); this.jButtonExcluir.addActionListener(e); this.jTableCLientes.getSelectionModel().addListSelectionListener(e2); }

Classe ControleCliente implementa os dois listeners e chama o método escuta passando a si mesmo duas vezes.

[code]/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */
    package controle;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.event.ListSelectionListener
import modelo.Cliente;
import visao.TelaCliente;
import dao.DAOGenerico;

public class ControleCliente implements ActionListener, ListSelectionListener {

private TelaCliente tc;
private Cliente c;
private DAOGenerico<Cliente> daog;

public ControleCliente (TelaCliente tc){
    this.c=new Cliente();
    this.tc=tc;
    tc.escuta(this, this);
    tc.setVisible(true);
}

[/code]

Pega o modelo (não sei pra que eu fiz esse método pegaModelo) ClientesTableModel

[code]public void adicionaItemTabela(Cliente c){
pegaModelo().inserir©;
}

public ClientesTableModel pegaModelo(){
return (ClientesTableModel)this.jTableClientes.getModel();
} [/code]

e usa o método inserir da ClientesTableModel:

//Insere um novo objeto na tabela. public void inserir(Cliente cliente) { clientes.add(cliente); //Adicionamos na lista fireTableDataChanged(); //Redesenha o modelo. }

Outra dúvida estranha: Às vezes eu do run no programa e ele dá NullPointerException no ControleCliente, nessa linha:

c.setBairro(tc.getjTextFieldBairro().getText());

Ou seja, ou o cliente © ou o tc (TelaCliente) estão nulos, mas eu já tinha inicializado os dois no construtor.
Aí se eu fecho e abro novamente, ele funciona. Mas não pode ser aleatório, alguém sugere algum provável erro?

Exception occurred during event dispatching: java.lang.NullPointerException at controle.ControleCliente.actionPerformed(ControleCliente.java:35) at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995) at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318) at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387) at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242) at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236) at java.awt.Component.processMouseEvent(Component.java:6288) at javax.swing.JComponent.processMouseEvent(JComponent.java:3267) at java.awt.Component.processEvent(Component.java:6053) at java.awt.Container.processEvent(Container.java:2041) at java.awt.Component.dispatchEventImpl(Component.java:4651) at java.awt.Container.dispatchEventImpl(Container.java:2099) at java.awt.Component.dispatchEvent(Component.java:4481) at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4577) at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4238) at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4168) at java.awt.Container.dispatchEventImpl(Container.java:2085) at java.awt.Window.dispatchEventImpl(Window.java:2478) at java.awt.Component.dispatchEvent(Component.java:4481) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:643) at java.awt.EventQueue.access$000(EventQueue.java:84) at java.awt.EventQueue$1.run(EventQueue.java:602) at java.awt.EventQueue$1.run(EventQueue.java:600) at java.security.AccessController.doPrivileged(Native Method) at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87) at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:98) at java.awt.EventQueue$2.run(EventQueue.java:616) at java.awt.EventQueue$2.run(EventQueue.java:614) at java.security.AccessController.doPrivileged(Native Method) at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87) at java.awt.EventQueue.dispatchEvent(EventQueue.java:613) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:178) at java.awt.Dialog$1.run(Dialog.java:1046) at java.awt.Dialog$3.run(Dialog.java:1098) at java.security.AccessController.doPrivileged(Native Method) at java.awt.Dialog.show(Dialog.java:1096) at java.awt.Component.show(Component.java:1584) at java.awt.Component.setVisible(Component.java:1536) at java.awt.Window.setVisible(Window.java:842) at java.awt.Dialog.setVisible(Dialog.java:986) at controle.ControleCliente.<init>(ControleCliente.java:26) at controle.ControlePrincipal.actionPerformed(ControlePrincipal.java:29) at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995) at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318) at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387) at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242) at javax.swing.AbstractButton.doClick(AbstractButton.java:357) at javax.swing.plaf.basic.BasicMenuItemUI.doClick(BasicMenuItemUI.java:809) at javax.swing.plaf.basic.BasicMenuItemUI$Handler.mouseReleased(BasicMenuItemUI.java:850) at java.awt.Component.processMouseEvent(Component.java:6288) at javax.swing.JComponent.processMouseEvent(JComponent.java:3267) at java.awt.Component.processEvent(Component.java:6053) at java.awt.Container.processEvent(Container.java:2041) at java.awt.Component.dispatchEventImpl(Component.java:4651) at java.awt.Container.dispatchEventImpl(Container.java:2099) at java.awt.Component.dispatchEvent(Component.java:4481) at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4577) at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4238) at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4168) at java.awt.Container.dispatchEventImpl(Container.java:2085) at java.awt.Window.dispatchEventImpl(Window.java:2478) at java.awt.Component.dispatchEvent(Component.java:4481) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:643) at java.awt.EventQueue.access$000(EventQueue.java:84) at java.awt.EventQueue$1.run(EventQueue.java:602) at java.awt.EventQueue$1.run(EventQueue.java:600) at java.security.AccessController.doPrivileged(Native Method) at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87) at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:98) at java.awt.EventQueue$2.run(EventQueue.java:616) at java.awt.EventQueue$2.run(EventQueue.java:614) at java.security.AccessController.doPrivileged(Native Method) at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87) at java.awt.EventQueue.dispatchEvent(EventQueue.java:613) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161) at java.awt.EventDispatchThread.run(EventDispatchThread.java:122) CONSTRUÍDO COM SUCESSO (tempo total: 19 segundos)

Aparentemente isso ocorre quando o controle é criado:

    at controle.ControleCliente.<init>(ControleCliente.java:26) 

E pelas linhas acima, parece que há alguns Runnable em sua JDialog.
Vc roda alguma thread?

at java.awt.Dialog$1.run(Dialog.java:1046) at java.awt.Dialog$3.run(Dialog.java:1098)

Lembre-se que a ordem que threads executam é completamente imprevisível. Além disso, threads não podem compartilhar variáveis, a menos que estas sejam completamente sincronizadas ou declaradas como volatile.

Quanto ao seu inserir, ao invés de chamar o fireTableDataChanged(), chame apenas fireTableRowsInserted passando como os dois parâmetros o tamanho da sua coleção-1.

O fireTableDataChanged exige que a tabela seja inteiramente redesenhada e tem péssima performance. Você deve evitar fazer isso ao máximo possível.

OK. Mudei os fireTable, mas o erro permanece. Os objetos anteriores continuam pegando os valores do novo objeto, isso quando não dá a exception.

Essa linha 26 que você deu quote é a linha

tc.setVisible(true);

Na TelaCliente:

[code]
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {

        public void run() {
            TelaCliente dialog = new TelaCliente(new javax.swing.JFrame(), true);
            dialog.addWindowListener(new java.awt.event.WindowAdapter() {

                public void windowClosing(java.awt.event.WindowEvent e) {
                    System.exit(0);
                }
            });
            dialog.setVisible(true);
        }
    });
}[/code]

PS: Eu reparei que ele só da essa exceção quando o a linha selecionada é a do topo (primeiro objeto adicionado).

Tive que comentar o método valueChanged porque é ele que está causando a NullPointerException.

De qualquer forma, está assim (na classe ControleCliente, como escrito em um post anterior):

[code]@Override
public void valueChanged(ListSelectionEvent e) {
/if(e.getValueIsAdjusting())
return ;
else{
c = tc.getClienteDaLinhaSelecionada();
tc.setBairro(c.getBairro());
tc.setCep(c.getCEP());
tc.setCidade(c.getCidade());
tc.setEmail(c.getEmail());
tc.setEnd(c.getEnd());
tc.setEstado(c.getEstado());
tc.setFax(c.getFax());
tc.setNome(c.getNome());
tc.setTelefone(c.getTel());
tc.setContato(c.getContato());
}
/

}[/code]

Pra finalizar só falta descobrir:

  1. Por que a cada objeto inserido na tabela todos os objetos que já existiam nela ficam com os dados do objeto inserido.
  2. Por que esse método valueChanged() está dando essa exceção.

Se puderem me ajudar vai ser de grande valia. Amanhã vou levar esse projeto pra quem estou desenvolvendo, e quero chegar lá com as conexões todas funcionando (assim que resolver esses 2 pontos o resto é mole).

  1. Porque a cada objeto inserido na tabela todos os objetos que já existiam nela ficam com os dados do objeto inserido.

Isso aqui, em 99% das vezes, é porque você não está dando “new Cliente” antes de inserir na tabela.
Toda sua lista contém a mesma referência, o que leva a esse tipo de erro.

De qualquer forma, eu rodaria com um depurador o código desde o pressionar do botão inserir, até o momento que o dado é inserido na tabela, vendo a cada passo o valor das variáveis e se não fiz nenhuma besteira no caminho.

Quanto ao erro do valueChanged. Você deve tratar o caso para quando nenhuma linha é selecionada. Provavelmente seu “c” fica nulo nesse caso.

1. Quando já existe um objeto na tabela e eu adiciono outro, os dados na tabela do objeto que estava lá são substituídos pelos dados do novo objeto.[Resolvido]

1. Por que a cada objeto inserido na tabela todos os objetos que já existiam nela ficam com os dados do objeto inserido.[Resolvido]
Resolvido da maneira que o Vini falou.

2. Por que esse método valueChanged() está dando essa exceção.[Resolvido]
Realmente, faltou a checagem de nulo, como o ViniGodoy disse (pra variar, né… :smiley: ).
O getClienteDaLinhaSelecionada() retorna um null em caso da linha ser “-1”, então ele estava dando nulo toda hora.

Obrigado, ViniGodoy! E morte ao DefaultTableMode!