Atualização de tamanho da JTable interfere no JScroll automático

Estou desenvolvendo uma aplicação que vai adicionando linhas em uma JTable e fazendo um “scrolling” automático: Segue trecho do código:

tabelaDeItensModel.adicionaItem(item);
jScrollPaneItens.getVerticalScrollBar()
    .setValue(jScrollPaneItens.getVerticalScrollBar()
    .getMaximum());

Durante os testes verifiquei que a execução rolava a tabela apenas até o penúltimo item e não até o último (que acabou de ser inserido).
Então resolvi fazer alguns testes a mais para descobrir o porque e fiz o seguinte:

JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(), "Erro!", 
    JOptionPane.ERROR_MESSAGE);
tabelaDeItensModel.adicionaItem(item);
JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(), "Erro!", 
    JOptionPane.ERROR_MESSAGE);

Para minha surpresa a altura da tabela não alterou após a inserção de um item mesmo a aplicação tendo exibido a tabela com o novo item.

  • Valor do 1º JOptionPane: 0
  • Valor do 2º JOptionPane: 0

Então fiz mais um teste: adicionei um novo JOptionPane após o segundo:

JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(), "Erro!", 
    JOptionPane.ERROR_MESSAGE);
tabelaDeItensModel.adicionaItem(item);
JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(), "Erro!", 
    JOptionPane.ERROR_MESSAGE);
JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(), "Erro!", 
    JOptionPane.ERROR_MESSAGE);

Para minha surpresa maior ainda no terceiro JOptionPane a altura da tabela estava atualizada (diferente dos dois anteriores)

  • Valor do 1º JOptionPane: 0
  • Valor do 2º JOptionPane: 0
  • Valor do 3º JOptionPane: 16

Então deduzi que o sistema estava executando o “scrolling” antes da atualização de renderização da tabela e o tempo que eu demoro para clicar no “ok” do 2º JOptionPane é suficiente para atualizar o tamanho da JTable e mostrar corretamente no 3º JOptionPane.
Então fiz mais um teste:

JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(), "Erro!", 
    JOptionPane.ERROR_MESSAGE);
int temp = jTableItensDaVenda.getHeight();
tabelaDeItensModel.adicionaItem(item);                
while (jTableItensDaVenda.getHeight() <= temp) { }
JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(),"Erro!", 
    JOptionPane.ERROR_MESSAGE);

Nessa caso a aplicação exibiu o 1º JOptionPane e entrou em loop infinito. Então fiz ainda mais um teste: Acrescentei um JOptionPane dentro do WHILE:

JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(), "Erro!", 
  JOptionPane.ERROR_MESSAGE);                
int temp = jTableItensDaVenda.getHeight();                
tabelaDeItensModel.adicionaItem(item);                
while (jTableItensDaVenda.getHeight() <= temp) {
     JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(), "Erro!", 
        JOptionPane.ERROR_MESSAGE);
     }                
    JOptionPane.showMessageDialog(null, jTableItensDaVenda.getHeight(),"Erro!", 
       JOptionPane.ERROR_MESSAGE);

Aí fiquei mais surpreso ainda com o resultado:

  • Valor do 1º JOptionPane: 0
  • Valor do 2º JOptionPane (dentro do while e executado uma única vez): 0
  • Valor do 3º JOptionPane: 16

Por favor, alguém poderia me explicar para que eu possa pensar em uma solução para meu “scrolling” automático?

No seu método adicionaItem, após ter adicionado o item, invoque o fireTableDataChanged()

1 curtida

Obrigado pela atenção,
Já tinha pensado nisso, mas minha aplicação tem o fireTableRowsInserted, testei com o fireTableDataChanged, mas o problema continua:
Lembrando que a aplicação insere a linha antes de exibir o JOptionPane, eu visualizo. Se eu tiro a linha fireTableRowsInserted() o item nem aparece.

public void adicionaItem(ItemDaVenda item) {
     linhas.add(item);
     int ultimoIndice = getRowCount() - 1;
        
     fireTableRowsInserted(ultimoIndice, ultimoIndice);
    }

Pessoal, alguém tem alguma idéia?

Não é elegante, mas pelo menos é funcional, veja o método eventos, pois ele é o responsável pelo efeito:

package teste;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.HeadlessException;
import java.awt.event.AdjustmentEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.table.DefaultTableModel;

    public class Teste extends JFrame {

        private final JTable tabela = new JTable(new DefaultTableModel(null, new String[]{"Coluna 1", "Coluna2", "ColunaN"}));
        private DefaultTableModel modeloTabela;
        private final String aux = "Campo Texto";
        private final JTextField[] camposDeTexto = {new JTextField(aux), new JTextField(aux), new JTextField(aux)};
        private final JButton adicionar = new JButton("Adicionar Linha");

        public Teste() throws HeadlessException {
            setTitle("Teste");
            config();
        }

        private void config() {
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setSize(400, (int) (200 * 1.618));

            setLayout(new BorderLayout());

            JScrollPane painel = new JScrollPane();
            painel.setViewportView(tabela);

            JPanel campos = new JPanel(new GridLayout(1, 4));
            for (JTextField txtFild : camposDeTexto) {
                campos.add(txtFild);
            }
            campos.add(adicionar);
            add(campos, BorderLayout.NORTH);
            add(painel, BorderLayout.CENTER);

            modeloTabela = (DefaultTableModel) tabela.getModel();

            eventos(painel);
        }

        private void eventos(JScrollPane pane) {
            Timer atualize = new Timer(50, ae -> {
                pane.getVerticalScrollBar().setValue(pane.getVerticalScrollBar().getMaximum());
                System.out.println("Atualizando");
            });

            adicionar.addActionListener(actionEvent -> {
                modeloTabela.addRow(new String[]{camposDeTexto[0].getText(), camposDeTexto[1].getText(), camposDeTexto[2].getText()});
                modeloTabela.addRow(new String[]{camposDeTexto[0].getText(), camposDeTexto[1].getText(), camposDeTexto[2].getText()});
                modeloTabela.addRow(new String[]{camposDeTexto[0].getText(), camposDeTexto[1].getText(), camposDeTexto[2].getText()});
                modeloTabela.addRow(new String[]{camposDeTexto[0].getText(), camposDeTexto[1].getText(), camposDeTexto[2].getText()});
                modeloTabela.addRow(new String[]{camposDeTexto[0].getText(), camposDeTexto[1].getText(), camposDeTexto[2].getText()});
                modeloTabela.addRow(new String[]{camposDeTexto[0].getText(), camposDeTexto[1].getText(), camposDeTexto[2].getText()});
                atualize.start();
            }
            );
            pane.getVerticalScrollBar().addAdjustmentListener((AdjustmentEvent ae) -> {
                if (ae.getValueIsAdjusting()) {
                    atualize.stop();
                }
            });

        }

        public static void main(String[] args) {
            new Teste().setVisible(true);
        }
    }

Obrigado addller,
Realmente não é muito elegante, mas funcionou.
Fiz assim:

Timer atualiza = new Timer(50, ae -> {
     jScrollPaneItens.getVerticalScrollBar().setValue(jScrollPaneItens.getVerticalScrollBar().getMaximum());
     });

atualiza.start();
            
tabelaDeItensModel.adicionaItem(itemDaVendaSelecionado);

jScrollPaneItens.getVerticalScrollBar().addAdjustmentListener((AdjustmentEvent ae) -> {
     if (ae.getValueIsAdjusting()) {
          atualiza.stop();
     }
});

Valeu mesmo!