7

JTableユーザーが動的に行を追加できるaがあります。にJScrollPane配置されるため、行数が十分に大きくなると、スクローラーがアクティブになります。私の望みは、ユーザーが新しい行を追加すると、スクローラーが一番下まで移動して、新しい行がスクロールペインに表示されるようにすることです。私は現在(以下のSSCCE)、テーブルモデルリスナーを使用して行が挿入されたことを検出し、検出が行われたときにスクロールバーを完全に押し下げようとしています。ただし、モデルは更新されているが、新しい行は実際にはまだペイントされていないため、この検出は「早すぎる」ようです。したがって、新しい行が挿入される直前に、スクローラーが一番下まで移動し、次に、新しい行がペインの端のすぐ下に挿入されます(非表示)。

明らかに、このアプローチはどういうわけか間違っています。正しいアプローチは何ですか?

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;

public class TableListenerTest {

    private JFrame frame;
    private JScrollPane scrollPane;
    private JTable table;
    private DefaultTableModel tableModel;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TableListenerTest window = new TableListenerTest();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public TableListenerTest() {
        initialize();
    }

    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 450, 200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));

        JSplitPane splitPane = new JSplitPane();
        frame.getContentPane().add(splitPane);

        scrollPane = new JScrollPane();
        scrollPane.setPreferredSize(new Dimension(100, 2));
        splitPane.setLeftComponent(scrollPane);

        tableModel = new DefaultTableModel(new Object[]{"Stuff"},0);
        table = new JTable(tableModel);
        scrollPane.setViewportView(table);
        table.getModel().addTableModelListener(new TableModelListener() {
            public void tableChanged(TableModelEvent e) {
                if (e.getType() == TableModelEvent.INSERT) {
                    JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
                    scrollBar.setValue(scrollBar.getMaximum());
                }
            }
        });

        JButton btnAddRow = new JButton("Add Row");
        btnAddRow.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                tableModel.addRow(new Object[]{"new row"});
            }
        });
        splitPane.setRightComponent(btnAddRow);
    }
}

編集:trashgodのリクエストに基づいて以下のSSCCEを更新しました。このバージョンはまだ機能しませんが、スクロールロジックをテーブルモデルリスナーからボタンリスナーに移動すると、機能します。

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;

    public class TableListenerTest {

        private JFrame frame;
        private JScrollPane scrollPane;
        private JTable table;
        private DefaultTableModel tableModel;

        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    try {
                        TableListenerTest window = new TableListenerTest();
                        window.frame.setVisible(true);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        public TableListenerTest() {
            initialize();
        }

        private void initialize() {
            frame = new JFrame();
            frame.setBounds(100, 100, 450, 200);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));

            JSplitPane splitPane = new JSplitPane();
            frame.getContentPane().add(splitPane);

            scrollPane = new JScrollPane();
            scrollPane.setPreferredSize(new Dimension(100, 2));
            splitPane.setLeftComponent(scrollPane);

            tableModel = new DefaultTableModel(new Object[]{"Stuff"},0);
            table = new JTable(tableModel);
            scrollPane.setViewportView(table);
            table.getModel().addTableModelListener(new TableModelListener() {
                public void tableChanged(TableModelEvent e) {
                    if (e.getType() == TableModelEvent.INSERT) {
                        int last = table.getModel().getRowCount() - 1;
                        Rectangle r = table.getCellRect(last, 0, true);
                        table.scrollRectToVisible(r);
                    }
                }
            });

            JButton btnAddRow = new JButton("Add Row");
            btnAddRow.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    tableModel.addRow(new Object[]{"new row"});
                }
            });
            splitPane.setRightComponent(btnAddRow);
        }
    }
4

2 に答える 2

7

このではscrollRectToVisible()、(条件付きで) 最後のセル四角形にスクロールするために使用します。機能として、親指をクリックしてスクロールを一時停止し、放して再開することができます。

private void scrollToLast() {
    if (isAutoScroll) {
        int last = table.getModel().getRowCount() - 1;
        Rectangle r = table.getCellRect(last, 0, true);
        table.scrollRectToVisible(r);
    }
}

補遺: SSCCE で試してみましたが、それでも同じ問題が発生します。 scrollRectToVisible

これActionにより、マウスとキーボードの両方の制御が提供されます。

JButton btnAddRow = new JButton(new AbstractAction("Add Row") {

    @Override
    public void actionPerformed(ActionEvent e) {
        tableModel.addRow(new Object[]{"new row"});
        int last = table.getModel().getRowCount() - 1;
        Rectangle r = table.getCellRect(last, 0, true);
        table.scrollRectToVisible(r);
    }
});

ここに画像の説明を入力

補遺:これは、改訂されたレイアウト戦略を示す例のバリエーションです。

/** @see https://stackoverflow.com/a/14429388/230513 */
public class TableListenerTest {

    private static final int N = 8;
    private JFrame frame;
    private JScrollPane scrollPane;
    private JTable table;
    private DefaultTableModel tableModel;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                TableListenerTest window = new TableListenerTest();
                window.frame.setVisible(true);
            }
        });
    }

    public TableListenerTest() {
        initialize();
    }

    private void initialize() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.X_AXIS));
        tableModel = new DefaultTableModel(new Object[]{"Stuff"}, 0);
        for (int i = 0; i < N; i++) {
            tableModel.addRow(new Object[]{"new row"});
        }
        table = new JTable(tableModel) {

            @Override
            public Dimension getPreferredScrollableViewportSize() {
                return new Dimension(200, table.getRowHeight() * N);
            }
        };
        scrollPane = new JScrollPane();
        scrollPane.setViewportView(table);
        JButton btnAddRow = new JButton(new AbstractAction("Add Row") {

            @Override
            public void actionPerformed(ActionEvent e) {
                tableModel.addRow(new Object[]{"new row"});
                int last = table.getModel().getRowCount() - 1;
                Rectangle r = table.getCellRect(last, 0, true);
                table.scrollRectToVisible(r);
            }
        });
        frame.add(scrollPane);
        frame.add(btnAddRow);
        frame.pack();
    }
}
于 2013-01-20T21:12:28.523 に答える
1

ただ、この発見は「早すぎる」ようで、

強調のために(@trashgodはすでにコメントで言及しています):それは本当です-そして予想され、Swingのかなり一般的な問題です:-)

テーブルと、データだけでなく、選択、調整など、任意のモデルを含むその他のビューは、モデルをリッスンして自分自身を更新します。したがって、カスタム リスナーは、一連のリスナーの 1 つにすぎません (サービス シーケンスは未定義です)。ビューステートに依存する何かを実行したい場合は、すべての内部更新の準備が整ったに実行する必要があります(この具体的なコンテキストでは、内部更新には垂直スクロールバーの調整モデルの更新が含まれます) カスタム処理の延期内部処理が完了するまでは、SwingUtilities.invokeLater をラップすることで達成されます。

TableModelListener l = new TableModelListener() {
    @Override
    public void tableChanged(TableModelEvent e) {
        if (e.getType() == TableModelEvent.INSERT) {
            invokeScroll();
        }
    }

    protected void invokeScroll() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                int last = table.getModel().getRowCount() - 1;
                Rectangle r = table.getCellRect(last, 0, true);
                table.scrollRectToVisible(r);
            }
        });
    }
};
table.getModel().addTableModelListener(l);
于 2013-01-27T13:35:34.193 に答える