简体   繁体   English

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

[英]How to scroll to last row in a JTable

I am trying to use JTable in the way where new data record are added to the end. 我试图以新数据记录添加到最后的方式使用JTable 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? 有什么办法告诉滚动条总是到表的末尾?

Here is part of my code: 这是我的代码的一部分:

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

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

When you execute that line of code in the previously described places, you're actually trying to tell the JScrollPane ( JTable.scrollRectToVisible defers any action to a parent that can provide scrolling behaviour, eg JScrollPane ) to scroll beyond the end of the enclosed JTable component. 当您在前面描述的位置执行该行代码时,您实际上是在尝试告诉JScrollPaneJTable.scrollRectToVisible将任何操作推迟到可以提供滚动行为的父级,例如JScrollPane ),以滚动到封闭的JTable的末尾零件。 It refuses to do that, and instead scrolls to the current end of the JTable component instead. 它拒绝这样做,而是滚动到JTable组件的当前端。

At some point later, the JTable component updates itself visually, and adds the newly added row underneath the row scrolled to earlier. 稍后, JTable组件会以可视方式更新自身,并将新添加的行添加到之前滚动到的行的下方。 You can verify that that line of code works by adding a button that executes it independently of the code that adds new rows, eg 您可以通过添加一个独立于添加新行的代码执行它的按钮来验证该行代码的工作原理,例如

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 ComponentListener instead, which provides, among other things, a componentResized method. 因为问题在于事物的视觉方面,所以我决定挂钩到ComponentListener ,它提供了一个componentResized方法。 Whenever a row is added or removed, the JTable resizes, even if it cannot be seen visually due to the JScrollPane's viewport. 无论何时添加或删除行, JTable都会调整大小,即使由于JScrollPane的视口而无法直观地看到它。 Therefore just run that line of code in that listener method, and things will work as expected. 因此,只需在该侦听器方法中运行该行代码,事情就会按预期工作。

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

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

}

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

Why not call fireTableRowsInserted upon updating in your TableModel implementation? 为什么不在TableModel实现中更新时调用fireTableRowsInserted

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM