如何滚动到JTable中的最后一行

我试图以新数据记录添加到最后的方式使用JTable 。 奇怪的是滚动条不会到达表的末尾; 相反,它总是显示从最后一秒开始的第二个。 有什么办法告诉滚动条总是到表的末尾?

这是我的代码的一部分:

 table.scrollRectToVisible(table.getCellRect(table.getRowCount()-1, 0, true)); 

我刚遇到这个问题 – 这行代码实际上并没有错; 问题在于你执行它。

如果你像我一样,在操作TableModel之后立即执行它(甚至通过invokeLater )或者使用TableModelListener ,你将会遇到你正在描述的问题。 问题是虽然模型已经使用新数据更新( table.getRowCount()只是对TableModel上的getRowCount()方法的传递),但JTable组件在视觉上却没有。

当您在前面描述的位置执行该行代码时,您实际上是在尝试告诉JScrollPaneJTable.scrollRectToVisible将任何操作推迟到可以提供滚动行为的父级,例如JScrollPane ),以滚动到封闭的JTable的末尾零件。 它拒绝这样做,而是滚动到JTable组件的当前端。

稍后, JTable组件会在视觉上更新自身,并将新添加的行添加到之前滚动到的行的下方。 您可以通过添加一个独立于添加新行的代码执行它的按钮来validation该行代码是否有效,例如

 private JTable _table = new JTable(); ... JButton b = new JButton("Force scroll to bottom"); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { _table.scrollRectToVisible(_table.getCellRect(_table.getRowCount()-1, 0, true)); } }); this.add(b); 

这个问题的解决方案有点间接,但在我的测试中确实可靠。 因为问题在于事物的视觉方面,所以我决定挂钩到ComponentListener ,它提供了一个componentResized方法。 无论何时添加或删除行, JTable都会resize,即使由于JScrollPane的视口而无法直观地看到它。 因此,只需在该侦听器方法中运行该行代码,事情就会按预期工作。

 private JTable _table = new JTable(); ... _table.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { _table.scrollRectToVisible(_table.getCellRect(_table.getRowCount()-1, 0, true)); } }); 

每当你想向下滚动到表的机器人时调用这个方法。 并且通过使用该方法解决了上述问题。

 public void scrolltable() { table.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { int lastIndex =table.getCellRect(table.getRowCount()-1; table.changeSelection(lastIndex, 0,false,false); } }); } 

感谢Sam的回答以及我在其他地方找到的另一个页面,我能够解决这个问题。

我想我会分享我的解决方案,所以下一个人不必将它们拼凑在一起。

请享用!

 import java.awt.Rectangle; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.text.SimpleDateFormat; import java.util.Date; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JViewport; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableModel; /** * Demonstrate displaying a specific cell in a JTable when a row is added. * 

* The Table Row Index is displayed in one of the table's columns. *

* The cell containing the Value will be selected for displaying. *

* The specified cell will be made visible and, if possible, positioned in the center of the Viewport. *

* The code works regardless of: *

    *
  • Whether or not the table data is sorted
  • *
  • The position/visibility of the "Value" column
  • *
*/ public class JTableScrollToRow { static SecureRandom random; private DefaultTableModel dtm; static { try { random = SecureRandom.getInstance("SHA1PRNG"); int seed = Integer.parseInt((new SimpleDateFormat("SSS")).format(new Date())); random.setSeed(random.generateSeed(seed)); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } } public void buildGUI() { Object[][] data = {}; Object colNames[] = { "Value", "TableRowIx", "Column A", "Column B", "Column C", "Column D", "Column E", "Column F" }; dtm = new DefaultTableModel(data, colNames); final JTable sampleTable = new JTable(dtm); sampleTable.setDragEnabled(false); sampleTable.setAutoCreateRowSorter(true); // Turn off auto-resizing to allow for columns moved out of the Viewport sampleTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); // Populate the table with some data for (int x = 0; x < 200; x++) { addRow(x); } // Create a ScrollPane JScrollPane sp = new JScrollPane(sampleTable); // Provide a horizontal scroll bar so that columns can be scrolled out of the Viewport sp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); final JFrame f = new JFrame(); f.getContentPane().add(sp); f.setTitle("JTable cell display example"); f.pack(); f.setLocationRelativeTo(null); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); // Create a thread that periodically adds a row to the table Thread rowAdder = new Thread(new Runnable() { @Override public void run() { do { try { int secs = 5; Thread.sleep(secs * 1000); } catch (InterruptedException e) { e.printStackTrace(); } // Add a row addRow(dtm.getRowCount()); } while (true); } }); rowAdder.start(); // Add the custom ComponentListener sampleTable.addComponentListener(new JTableCellDisplayer(sampleTable)); } /** * Display a table row when it is added to a JTable.
* Details available at StackOverflow. *

* Key information: Whenever a row is added or removed the JTable resizes. This occurs even if the row is * outside of the JScrollPane's Viewport (ie, the row is not visible). */ class JTableCellDisplayer extends ComponentAdapter { boolean selRow = false; boolean selCol = false; boolean firstTime = true; boolean selectData = false; JTable table; public JTableCellDisplayer(JTable jTable) { table = jTable; } @Override public void componentResized(ComponentEvent e) { if (firstTime) { firstTime = false; return; } int viewIx = table.convertRowIndexToView(table.getRowCount() - 1); if (!selRow && !selCol) { System.out.println(" - Select nothing - selectData=" + selectData); } else if (selRow && !selCol) { System.out.println(" - Select row only - selectData=" + selectData); } else if (!selRow && selCol) { System.out.println(" - Select column only - selectData=" + selectData); } else { System.out.println(" - Select cell - selectData=" + selectData); } // If data should be selected, set the selection policies on the table. if (selectData) { table.setRowSelectionAllowed(selRow); table.setColumnSelectionAllowed(selCol); } // Scroll to the VALUE cell (columnIndex=0) that was added displayTableCell(table, viewIx, table.convertColumnIndexToView(0), selectData); // Cycle through all possibilities if (!selRow && !selCol) { selRow = true; } else if (selRow && !selCol) { selRow = false; selCol = true; } else if (!selRow && selCol) { selRow = true; selCol = true; } else { selRow = false; selCol = false; selectData = !selectData; } } } /** * Assuming the table is contained in a JScrollPane, scroll to the cell (vRowIndex, vColIndex).
* The specified cell is guaranteed to be made visible.
* Every attempt will be made to position the cell in the center of the Viewport. Note: This may not be * possible if the row is too close to the top or bottom of the Viewport. *

* It is possible to select the specified cell. The amount of data selected (none, entire row, entire column or a * single cell) is dependent on the settings specified by {@link JTable#setColumnSelectionAllowed(boolean)} and * {@link JTable#setRowSelectionAllowed(boolean)}. *

* Original code found here. *

* * @param table * - The table * @param vRowIndex * - The view row index * @param vColIndex * - The view column index * @param selectCell * - If true, the cell will be selected in accordance with the table's selection policy; * otherwise the selected data will not be changed. * @see JTable#convertRowIndexToView(int) * @see JTable#convertColumnIndexToView(int) */ public static void displayTableCell(JTable table, int vRowIndex, int vColIndex, boolean selectCell) { if (!(table.getParent() instanceof JViewport)) { return; } JViewport viewport = (JViewport) table.getParent(); /* This rectangle is relative to the table where the * northwest corner of cell (0,0) is always (0,0). */ Rectangle rect = table.getCellRect(vRowIndex, vColIndex, true); // The location of the view relative to the table Rectangle viewRect = viewport.getViewRect(); /* * Translate the cell location so that it is relative * to the view, assuming the northwest corner of the * view is (0,0). */ rect.setLocation(rect.x - viewRect.x, rect.y - viewRect.y); // Calculate location of rectangle if it were at the center of view int centerX = (viewRect.width - rect.width) / 2; int centerY = (viewRect.height - rect.height) / 2; /* * Fake the location of the cell so that scrollRectToVisible * will move the cell to the center */ if (rect.x < centerX) { centerX = -centerX; } if (rect.y < centerY) { centerY = -centerY; } rect.translate(centerX, centerY); // If desired and allowed, select the appropriate cell if (selectCell && (table.getRowSelectionAllowed() || table.getColumnSelectionAllowed())) { // Clear any previous selection table.clearSelection(); table.setRowSelectionInterval(vRowIndex, vRowIndex); table.setColumnSelectionInterval(vColIndex, vColIndex); } // Scroll the area into view. viewport.scrollRectToVisible(rect); } private String addRow(int tableRowIndex) { String retVal; int value = random.nextInt(99999999); dtm.addRow(new Object[] { value, tableRowIndex, random.nextInt(99999999), random.nextInt(99999999), random.nextInt(99999999), random.nextInt(99999999), random.nextInt(99999999), random.nextInt(99999999), }); retVal = "Row added - value=" + value + " & tableRowIx=" + tableRowIndex; System.out.println(retVal); return retVal; } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new JTableScrollToRow().buildGUI(); } }); } }

为什么不在TableModel实现中更新时调用fireTableRowsInserted

我通常在TableModel实现中有类似下面的内容:

 public void addRow (MyDataType valToAdd){ rows.add(valToAdd); fireTableRowsInserted(rows.size()-1,rows.size()-1); }