Как прокрутить до последней строки в JTable - PullRequest
11 голосов
/ 03 февраля 2011

Я пытаюсь использовать JTable для добавления новой записи данных в конец.Странно то, что полоса прокрутки не доходит до конца таблицы;вместо этого он всегда показывает второе из последнего.Есть ли способ указать полосе прокрутки всегда идти до конца таблицы?

Вот часть моего кода:

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

Ответы [ 4 ]

17 голосов
/ 21 апреля 2011

Я только что столкнулся с этой проблемой - на самом деле нет ничего плохого в этой строке кода; проблема заключается в том, когда вы его выполняете.

Если вы, как и я, пытаетесь выполнить его сразу после манипулирования 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 изменяется, даже если он не виден визуально из-за области просмотра *1040* JScrollPane. Поэтому просто запустите эту строку кода в этом методе слушателя, и все будет работать как положено.

private JTable _table = new JTable();
...
_table.addComponentListener(new ComponentAdapter() {
    public void componentResized(ComponentEvent e) {
        _table.scrollRectToVisible(_table.getCellRect(_table.getRowCount()-1, 0, true));
    }
});
2 голосов
/ 30 августа 2014

вызывайте этот метод всякий раз, когда вы хотите прокрутить вниз до бота таблицы.И вышеуказанная проблема решается с помощью этого метода.

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);
        }
    });
}
2 голосов
/ 27 сентября 2012

Благодаря ответу Сэма и другой странице, которую я нашел в другом месте, я смог решить эту проблему.

Я решила, что поделюсь своим решением, чтобы следующему парню не пришлось собирать все вместе.

Наслаждайтесь!

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="/3929047/kak-prokrutit-do-poslednei-stroki-v-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();
            }
        });
    }

}
1 голос
/ 07 октября 2013

Почему бы не вызвать fireTableRowsInserted после обновления в вашей реализации TableModel?

Обычно в моей реализации TableModel есть что-то похожее на приведенное ниже:

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