Pintando linhas duma JTable [RESOLVIDO]

11 respostas
erico_kl

Olá Pessoal…
Tenho um método que pinta linhas de uma JTable através de um CellRenderer… até aí tudo blz, o problema é que antes de pintá-la eu preencho-a com dados do DB. Esta pesquisa no banco é feita em uma Thread separada da main, mas se eu coloco o método de pintura junto nesta Thread NPEs exporádicos acontecem, devido à este método ser chamado por outra Thread (não diretamente a main). Como posso resolver esse problema?

Obs.:
1 - As linhas são pintadas mesmo estourando os NPEs…
2 - Preciso colocar a pintura da tabela na Thread main para as linhas serem pintadas…
3 - Já fizemos uma thread esperar a outra e mesmo assim não adiantou (justamente pelo fato da pintura estar em uma Thread separada da main)

Obrigado pela atenção

11 Respostas

E

Pergunta boba número 1. Você precisa mostrar nessa JTable coisas que não são strings, é isso?

erico_kl

Sim e Não :slight_smile:
Na verdade mostro Strings, Booleans (como CheckBox), Doubles…

E

Mas não é que precisa mostrar cores, ícones ou até gráficos (como eu já vi acontecendo). É só aquelas coisas costumeiras, não?

E

No seu caso, em que você precisa mostrar rapidamente atualizações em uma JTable, eu aconselharia você usar o GlazedLists.

Eu passei a usar o GlazedLists para mostrar atualizações de cotações da Bolsa em tempo real (sendo que o meu programa tem uma tonelada de threads diferentes), e não tive problemas com esse tipo de coisa que você está reportando.

http://publicobject.com/glazedlists

erico_kl

O problema em si não está em carregar os dados rapidamente ou em pintar as linhas (é só pintar as linhas mesmo, não tem gráficos não…), mas sim em fazer essa pintura atualizar a table de dentro de uma Thread… ou fazer com que a main atualize a table depois da execução da outra Thread…

E

E é por isso que saber usar corretamente o GlazedLists é uma boa. Basicamente as coisas funcionam assim: você cria uma classe que representa 1 linha de sua JTable. Digamos que essa classe se chame “LinhaTabela”. Então crie uma lista desses objetos (que pode ser ordenada, se quiser) chamada BasicEventList, e então para:
a) Inserir / remover / modificar dados nessa tabela - pegue um WriteLock que está associado a essa lista, chame o método lock(), faça a operação desejada, e então chame o método unlock().
b) Simplesmente ler alguma coisa dessa tabela - pegue um ReadLock que está associado a essa lista, chame o método lock(), faça a operação desejada, e então chame o método unlock().
O conceito é que 1 “writer” pode estar ativo em um determinado tempo, mas N “readers” podem estar ativos nesse mesmo tempo.
Os métodos de inserção e modificação na lista disparam aqueles métodos “updateXXX” que servem para redesenhar a tabela, mas são disparados na thread do Swing, não na sua própria thread, o que torna isso mais seguro.

erico_kl

hmm… certo
vou estudar aqui o GlazedLists e tentar implementar algo em cima desse problema…

Detalhe: dessa maneira eu não conseguiria mais trabalhar com TableModels? (Corrija-me se eu estiver errado)

E

Ahá! O GlazedLists implementa uma TableModel para você. De qualquer maneira, pegue este programa e compile com a versão mais nova do GlazedLists. Você vai ver (clicando o botão “Adicionar Multithread”) que duas threads estão atualizando a tabela simultaneamente, sem dar problemas. Você pode até continuar a usar os outros botões.

package guj;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.EventTableModel;
import ca.odell.glazedlists.util.concurrent.Lock;
import java.awt.Dimension;

/**
 * Este exemplo requer o uso da seguinte biblioteca: Glazed Lists - http://publicobject.com/glazedlists/
 */
public class ExemploGlazedListsMultithread extends JFrame {
    public static class Cliente implements Comparable<Cliente> {
        public Cliente(final String nome, final String telefone, final int codigo) {
            this.nome = nome;
            this.telefone = telefone;
            this.codigo = codigo;
        }

        @Override
        public String toString() {
            return getNome();
        }

        public String getNome() {
            return nome;
        }

        public String getTelefone() {
            return telefone;
        }

        public int getCodigo() {
            return codigo;
        }

        @Override
        public int compareTo(Cliente that) {
            return Integer.valueOf(this.codigo).compareTo(that.codigo);
        }

        private String nome;
        private String telefone;
        private int codigo;
    }

    private static final long serialVersionUID = 1L;
    private JPanel jContentPane = null;
    private JScrollPane scpClientes = null;
    private JButton btnAdicionar = null;
    private JButton btnRemover = null;
    private JButton btnLimpar = null;
    private JPanel pnlBotoes = null;

    private EventList<Cliente> clientes = new BasicEventList<Cliente>();
    private EventTableModel<Cliente> modelClientes;

    private int codigo;
    private Random rand = new Random();
    private JTable tblClientes = null;
    private JButton btnAdicionarMultithread = null;

    private JScrollPane getScpClientes() {
        if (scpClientes == null) {
            scpClientes = new JScrollPane();
            scpClientes.setViewportView(getTblClientes());
        }
        return scpClientes;
    }

    private JButton getBtnAdicionar() {
        if (btnAdicionar == null) {
            btnAdicionar = new JButton();
            btnAdicionar.setText("Adicionar");
            btnAdicionar.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    Lock lock = clientes.getReadWriteLock().writeLock();
                    try {
                        lock.lock();
                        clientes.add(new Cliente("Cliente " + codigo, "888-8889", codigo));
                        codigo++;
                    } finally {
                        lock.unlock();
                    }
                }
            });
        }
        return btnAdicionar;
    }

    private JButton getBtnRemover() {
        if (btnRemover == null) {
            btnRemover = new JButton();
            btnRemover.setText("Remover");
            btnRemover.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    Lock lock = clientes.getReadWriteLock().writeLock();
                    try {
                        lock.lock();
                        // Removendo os clientes selecionados
                        ListSelectionModel lsm = tblClientes.getSelectionModel();
                        while (!lsm.isSelectionEmpty()) {
                            clientes.remove(lsm.getMinSelectionIndex());
                        }
                    } finally {
                        lock.unlock();
                    }
                }
            });
        }
        return btnRemover;
    }

    private JButton getBtnLimpar() {
        if (btnLimpar == null) {
            btnLimpar = new JButton();
            btnLimpar.setText("Limpar");
            btnLimpar.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    Lock lock = clientes.getReadWriteLock().writeLock();
                    try {
                        lock.lock();
                        clientes.clear();
                    } finally {
                        lock.unlock();
                    }
                }
            });
        }
        return btnLimpar;
    }

    private JPanel getPnlBotoes() {
        if (pnlBotoes == null) {
            pnlBotoes = new JPanel();
            pnlBotoes.setLayout(new FlowLayout());
            pnlBotoes.add(getBtnAdicionar());
            pnlBotoes.add(getBtnRemover());
            pnlBotoes.add(getBtnLimpar());
            pnlBotoes.add(getBtnAdicionarMultithread());
        }
        return pnlBotoes;
    }

    private JTable getTblClientes() {
        if (tblClientes == null) {
            tblClientes = new JTable();
            modelClientes = new EventTableModel<Cliente>(clientes, new String[] {
                "nome", "telefone", "codigo"
            }, new String[] {
                "Nome", "Telefone", "Código"
            }, new boolean[] {
                false, false, false
            });
            tblClientes.setModel(modelClientes);
        }
        return tblClientes;
    }

    private volatile boolean flagTerminou;

    private JButton getBtnAdicionarMultithread() {
        if (btnAdicionarMultithread == null) {
            btnAdicionarMultithread = new JButton();
            btnAdicionarMultithread.setText("Adicionar Multithread");
            btnAdicionarMultithread.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    btnAdicionarMultithread.setEnabled(false);
                    Thread thr1 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            for (int i = 1; i <= 1000; ++i) {
                                Lock lock = clientes.getReadWriteLock().writeLock();
                                try {
                                    lock.lock();
                                    clientes.add(new Cliente("Thread 1 " + codigo, "7777-7777", codigo));
                                    codigo++;
                                } finally {
                                    lock.unlock();
                                }
                                try {
                                    Thread.sleep(20);
                                } catch (InterruptedException ex) {
                                }
                            }
                            if (flagTerminou)
                                btnAdicionarMultithread.setEnabled(true);
                            flagTerminou = true;
                        }
                    });
                    Thread thr2 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            for (int i = 1; i <= 1000; ++i) {
                                Lock lock = clientes.getReadWriteLock().writeLock();
                                try {
                                    lock.lock();
                                    clientes.add(new Cliente("Thread 2 " + codigo, "5555-5555", codigo));
                                    codigo++;
                                } finally {
                                    lock.unlock();
                                }
                                try {
                                    Thread.sleep(10);
                                } catch (InterruptedException ex) {
                                }
                            }
                            if (flagTerminou)
                                btnAdicionarMultithread.setEnabled(true);
                            flagTerminou = true;
                        }
                    });
                    thr1.start();
                    thr2.start();
                }
            });
        }
        return btnAdicionarMultithread;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                ExemploGlazedListsMultithread thisClass = new ExemploGlazedListsMultithread();
                thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                thisClass.setVisible(true);
            }
        });
    }

    public ExemploGlazedListsMultithread() {
        super();
        initialize();
    }

    private void initialize() {
        this.setSize(525, 257);
        this.setContentPane(getJContentPane());
        this.setTitle("Exemplo Glazed Lists - JList");
    }

    private JPanel getJContentPane() {
        if (jContentPane == null) {
            jContentPane = new JPanel();
            jContentPane.setLayout(new BorderLayout());
            jContentPane.add(getScpClientes(), BorderLayout.CENTER);
            jContentPane.add(getPnlBotoes(), BorderLayout.SOUTH);
        }
        return jContentPane;
    }

} // @jve:decl-index=0:visual-constraint="10,10"
erico_kl

Bah… valeu pela ajuda, vou estudar com mais calma à noite e aí retorno aqui qualquer coisa…
Implementando dessa maneira eu continuo colorindo a tabela com TableCellRenderer, certo?

erico_kl

peguei o seu exemplo mas ele gera vários erros…
inclusive o próprio exemplo em web start do site está rodando com erros na console…

o meu problema é somente os estouros de NPE, pois tudo funciona perfeitamente… acho que vou fazer simplesmente um:

try {
   ...
}catch (NullPointerException npe) {}

pois mesmo estourando o erro a tabela é carregada, pintada e atualizada na Thread sem problemas…

esse é o erro listado:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException at javax.swing.plaf.synth.SynthTableUI.paintCell(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.paintCells(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.paint(Unknown Source) at javax.swing.plaf.synth.SynthTableUI.update(Unknown Source) at javax.swing.JComponent.paintComponent(Unknown Source) at javax.swing.JComponent.paint(Unknown Source) at javax.swing.JComponent.paintToOffscreen(Unknown Source) at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(Unknown Source) at javax.swing.RepaintManager$PaintManager.paint(Unknown Source) at javax.swing.RepaintManager.paint(Unknown Source) at javax.swing.JComponent._paintImmediately(Unknown Source) at javax.swing.JComponent.paintImmediately(Unknown Source) at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source) at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source) at javax.swing.RepaintManager.seqPaintDirtyRegions(Unknown Source) at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(Unknown Source) at java.awt.event.InvocationEvent.dispatch(Unknown Source) at java.awt.EventQueue.dispatchEvent(Unknown Source) at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.run(Unknown Source)

erico_kl

Olá pessoal…

Resolvi o problema adicionando um ComponentListener para componentResized que cada vez que muda o tamanho da tabela (toda a pesquisa vai mudar pois o tableModel é zerado antes de cada pesquisa), as linhas são pintadas…

Assim a pintura roda na Thread main e sempre depois de cada filtragem.
Resultado: não deu mais erros e não precisei fazer gambiarras do tipo “catch (Exception) {}” somente para não dar o stack trace do erro…

Obrigado pela ajuda…

Criado 3 de dezembro de 2010
Ultima resposta 4 de dez. de 2010
Respostas 11
Participantes 2