Слушатель модели JTable слишком быстро обнаруживает вставленные строки (до их рисования)

У меня есть JTable который может содержать строки, динамически добавленные пользователем. Он находится в JScrollPane , так как количество строк становится достаточно большим, скроллер становится активным. Мое желание состоит в том, что когда пользователь добавляет новую строку, скроллер перемещается полностью вниз, так что новая строка видна в scrollpane. Я в настоящее время (SSCCE ниже) пытается использовать прослушиватель модели таблицы, чтобы определить, когда строка вставлена, и заставлять полосу прокрутки полностью удаляться при обнаружении. Тем не менее, похоже, что это обнаружение «слишком рано», поскольку модель обновилась, но новая строка еще не была нарисована, так что происходит, когда скроллер движется полностью вниз до того, как вставлена ​​новая строка, и то новая строка вставлена ​​чуть ниже конца панели (вне видимости).

Очевидно, что такой подход как-то не так. Каков правильный подход?

 import java.awt.Dimension; import java.awt.EventQueue; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableModel; public class TableListenerTest { private JFrame frame; private JScrollPane scrollPane; private JTable table; private DefaultTableModel tableModel; public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { try { TableListenerTest window = new TableListenerTest(); window.frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } public TableListenerTest() { initialize(); } private void initialize() { frame = new JFrame(); frame.setBounds(100, 100, 450, 200); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS)); JSplitPane splitPane = new JSplitPane(); frame.getContentPane().add(splitPane); scrollPane = new JScrollPane(); scrollPane.setPreferredSize(new Dimension(100, 2)); splitPane.setLeftComponent(scrollPane); tableModel = new DefaultTableModel(new Object[]{"Stuff"},0); table = new JTable(tableModel); scrollPane.setViewportView(table); table.getModel().addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { if (e.getType() == TableModelEvent.INSERT) { JScrollBar scrollBar = scrollPane.getVerticalScrollBar(); scrollBar.setValue(scrollBar.getMaximum()); } } }); JButton btnAddRow = new JButton("Add Row"); btnAddRow.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { tableModel.addRow(new Object[]{"new row"}); } }); splitPane.setRightComponent(btnAddRow); } } 

EDIT: Обновлено SSCCE ниже, основываясь на запросе trashgod. Однако эта версия все еще не работает, если я перемещаю логику прокрутки из прослушивателя модели таблицы на прослушиватель кнопок, как он это делал, тогда она работает!

 import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableModel; public class TableListenerTest { private JFrame frame; private JScrollPane scrollPane; private JTable table; private DefaultTableModel tableModel; public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { try { TableListenerTest window = new TableListenerTest(); window.frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } public TableListenerTest() { initialize(); } private void initialize() { frame = new JFrame(); frame.setBounds(100, 100, 450, 200); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS)); JSplitPane splitPane = new JSplitPane(); frame.getContentPane().add(splitPane); scrollPane = new JScrollPane(); scrollPane.setPreferredSize(new Dimension(100, 2)); splitPane.setLeftComponent(scrollPane); tableModel = new DefaultTableModel(new Object[]{"Stuff"},0); table = new JTable(tableModel); scrollPane.setViewportView(table); table.getModel().addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { if (e.getType() == TableModelEvent.INSERT) { int last = table.getModel().getRowCount() - 1; Rectangle r = table.getCellRect(last, 0, true); table.scrollRectToVisible(r); } } }); JButton btnAddRow = new JButton("Add Row"); btnAddRow.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { tableModel.addRow(new Object[]{"new row"}); } }); splitPane.setRightComponent(btnAddRow); } } 

Этот пример использует scrollRectToVisible() для (условно) прокрутки до последнего прямоугольника ячейки. В качестве функции вы можете нажать на большой палец, чтобы приостановить прокрутку и отпустить, чтобы возобновить.

 private void scrollToLast() { if (isAutoScroll) { int last = table.getModel().getRowCount() - 1; Rectangle r = table.getCellRect(last, 0, true); table.scrollRectToVisible(r); } } 

Приложение: Я попробовал scrollRectToVisible в своем SSCCE, и он по-прежнему демонстрирует ту же проблему.

Это Action обеспечивает управление мышью и клавиатурой:

 JButton btnAddRow = new JButton(new AbstractAction("Add Row") { @Override public void actionPerformed(ActionEvent e) { tableModel.addRow(new Object[]{"new row"}); int last = table.getModel().getRowCount() - 1; Rectangle r = table.getCellRect(last, 0, true); table.scrollRectToVisible(r); } }); 

введите описание изображения здесь

Добавление. Ниже приведен пример вашего примера, иллюстрирующий пересмотренную страtagsю компоновки.

 /** @see https://stackoverflow.com/a/14429388/230513 */ public class TableListenerTest { private static final int N = 8; private JFrame frame; private JScrollPane scrollPane; private JTable table; private DefaultTableModel tableModel; public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { TableListenerTest window = new TableListenerTest(); window.frame.setVisible(true); } }); } public TableListenerTest() { initialize(); } private void initialize() { frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.X_AXIS)); tableModel = new DefaultTableModel(new Object[]{"Stuff"}, 0); for (int i = 0; i < N; i++) { tableModel.addRow(new Object[]{"new row"}); } table = new JTable(tableModel) { @Override public Dimension getPreferredScrollableViewportSize() { return new Dimension(200, table.getRowHeight() * N); } }; scrollPane = new JScrollPane(); scrollPane.setViewportView(table); JButton btnAddRow = new JButton(new AbstractAction("Add Row") { @Override public void actionPerformed(ActionEvent e) { tableModel.addRow(new Object[]{"new row"}); int last = table.getModel().getRowCount() - 1; Rectangle r = table.getCellRect(last, 0, true); table.scrollRectToVisible(r); } }); frame.add(scrollPane); frame.add(btnAddRow); frame.pack(); } } 

Однако, похоже, это обнаружение «слишком рано»,

Для акцента (@trashgod уже упомянул об этом в комментарии): это правда – и ожидалось, и довольно общая проблема в Swing 🙂

Таблица – и любое другое представление с любой моделью, а не только данные, но также выбор, настройка, … – прослушивает модель для обновления самой. Таким образом, пользовательский прослушиватель является еще одним слушателем в строке (с порядковой последовательностью показывается неопределенный). Если он хочет сделать что-то, зависящее от состояния представления, он должен сделать это после завершения внутреннего обновления (в этом конкретном контексте внутреннее обновление включает обновление настройкиModel вертикального scrollBar). Откладывание пользовательской обработки пока внутренности не будут выполнены, достигается путем упаковки SwingUtilities.invokeLater:

 TableModelListener l = new TableModelListener() { @Override public void tableChanged(TableModelEvent e) { if (e.getType() == TableModelEvent.INSERT) { invokeScroll(); } } protected void invokeScroll() { SwingUtilities.invokeLater(new Runnable() { public void run() { int last = table.getModel().getRowCount() - 1; Rectangle r = table.getCellRect(last, 0, true); table.scrollRectToVisible(r); } }); } }; table.getModel().addTableModelListener(l); 
Давайте будем гением компьютера.