我有一个JTable
,可以让用户动态添加行。它位于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);
}
}
答案 0 :(得分:7)
此example使用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();
}
}
答案 1 :(得分:1)
然而,这种检测似乎“太早了”,
强调(@trashgod已经在评论中提到过):这是真的 - 并且预期和Swing中相当普遍的问题:-)
表格 - 以及任何模型的任何其他视图,不仅包括数据,还包括选择,调整,...... - 正在监听模型以更新自身。因此,自定义侦听器只是该行中的另一个侦听器(服务序列未定义)。如果它想要做一些取决于视图状态的事情,它必须确保在之后所有内部更新都准备就绪(在这个具体的上下文中,内部更新包括更新的adjustModel vertical scrollBar)通过包装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);