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

时间:2011-02-03 18:42:01

标签: java swing scroll jtable

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

以下是我的代码的一部分:

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

4 个答案:

答案 0 :(得分:17)

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

如果您像我一样,在操作TableModel(甚至通过 invokeLater )或使用 TableModelListener 后立即尝试执行它,您将获得你正在描述的问题。问题是虽然模型已经使用新数据进行了更新( table.getRowCount()只是传递给 getRowCount()方法的 > TableModel ), JTable 组件在视觉上没有。

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

稍后, JTable 组件会以可视方式更新自身,并将新添加的行添加到之前滚动到的行的下方。您可以通过添加一个独立于添加新行的代码执行它的按钮来验证该行代码的工作原理,例如。

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 都会调整大小,即使由于 JScrollPane的视口而无法直观地看到它。因此,只需在该侦听器方法中运行该行代码,事情就会按预期运行。

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

答案 1 :(得分:2)

感谢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.
 * <p>
 * The Table Row Index is displayed in one of the table's columns.
 * <p>
 * The cell containing the Value will be selected for displaying.
 * <p>
 * The specified cell will be made visible and, if possible, positioned in the center of the Viewport.
 * <p>
 * The code works regardless of:
 * <ul>
 * <li>Whether or not the table data is sorted</li>
 * <li>The position/visibility of the "Value" column</li>
 * </ul>
 */
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.<br>
     * Details available at <a
     * href="http://stackoverflow.com/questions/4890282/howto-to-scroll-to-last-row-on-jtable">StackOverflow</a>.
     * <p>
     * <b>Key information:</b> Whenever a row is added or removed the JTable resizes. This occurs even if the row is
     * outside of the JScrollPane's Viewport (i.e., 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). <br>
     * The specified cell is guaranteed to be made visible.<br>
     * Every attempt will be made to position the cell in the center of the Viewport. <b>Note:</b> This may not be
     * possible if the row is too close to the top or bottom of the Viewport.
     * <p>
     * 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)}.
     * <p>
     * Original code found <a href="http://www.exampledepot.com/egs/javax.swing.table/VisCenter.html">here</a>.
     * <p>
     * 
     * @param table
     *            - The table
     * @param vRowIndex
     *            - The view row index
     * @param vColIndex
     *            - The view column index
     * @param selectCell
     *            - If <code>true</code>, 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();
            }
        });
    }

}

答案 2 :(得分:2)

每当你想向下滚动到表的机器人时,都会调用此方法。以上问题通过使用此方法解决。

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);
        }
    });
}

答案 3 :(得分:1)

为什么不在fireTableRowsInserted实施中更新时致电TableModel

我的TableModel实现中通常有以下内容:

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