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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4890282/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 08:30:15  来源:igfitidea点击:

How to scroll to last row in a JTable

javaswingscrolljtable

提问by gd7

I am trying to use JTablein the way where new data record are added to the end. The strange thing is the scroll bar does not go to the end of the table; instead, it always shows the second from the last. Any way to tell the scroll bar to always go to the end of the table?

我试图以将JTable新数据记录添加到末尾的方式使用。奇怪的是滚动条没有走到表格的末尾;相反,它总是显示倒数第二个。有什么方法可以告诉滚动条总是走到表格的末尾?

Here is part of my code:

这是我的代码的一部分:

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

回答by Sam

I just ran into this problem - there is actually nothing wrong with that line of code; the problem lies in when you execute it.

我刚刚遇到了这个问题——那行代码实际上没有任何问题;问题在于你何时执行它。

If you're, like me, trying to execute it immediately after manipulating the TableModel (even via invokeLater) or by using a TableModelListener, you'll get the problem you're describing. The problem is that while the model has been updated with the new data (table.getRowCount()is simply a pass-through to the getRowCount()method on your TableModel), the JTablecomponent visually has not.

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

When you execute that line of code in the previously described places, you're actually trying to tell the JScrollPane(JTable.scrollRectToVisibledefers any action to a parent that can provide scrolling behaviour, e.g. JScrollPane) to scroll beyond the end of the enclosed JTablecomponent. It refuses to do that, and instead scrolls to the current end of the JTablecomponent instead.

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

At some point later, the JTablecomponent updates itself visually, and adds the newly added row underneath the row scrolled to earlier. You can verify that that line of code works by adding a button that executes it independently of the code that adds new rows, e.g.

在稍后的某个时间点,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);

The solution to this problem is a little indirect, but does work reliably in my testing. Because the issue lies in the visual side of things, I decided to hook into the ComponentListenerinstead, which provides, among other things, a componentResizedmethod. Whenever a row is added or removed, the JTableresizes, even if it cannot be seen visually due to the JScrollPane'sviewport. Therefore just run that line of code in that listener method, and things will work as expected.

这个问题的解决方案有点间接,但在我的测试中确实可靠。因为问题出在视觉方面,所以我决定转而使用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));
    }
});

回答by Hasun Perera

call this method whenever you want to scroll down to the bot of the table. And above problem is solved by using this method.

每当您想向下滚动到表格的机器人时调用此方法。而上述问题就是用这种方法解决的。

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

回答by bigleftie

Thanks to Sam's answer and another page I found elsewhere, I was able to solve this problem.

感谢 Sam 的回答和我在别处找到的另一个页面,我能够解决这个问题。

I figured I'd share my solution so the next guy doesn't have to piece it all together.

我想我会分享我的解决方案,这样下一个人就不必把它拼凑起来。

Enjoy!

享受!

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

}

回答by nugget

Why not call fireTableRowsInsertedupon updating in your TableModelimplementation?

为什么不在fireTableRowsInserted您的TableModel实施中呼吁更新?

I usally have something like below in my TableModel implementation:

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

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