JTable#scrollRectToVisible 结合 JSplitPlane 显示错误的行
JTable#scrollRectToVisible in combination with JSplitPlane shows the wrong row
当我调用 JTable#scrollRectToVisible
时,在某些情况下,我要显示的行隐藏在 header 下方。
这个问题的其余部分只有在使用以下代码时才有意义。这是一个非常简单的程序,我用它来说明问题。它显示了一个 UI,其中包含一个 JSplitPane
,上半部分带有一些控制按钮,下半部分包含一个 JTable
,包裹在一个 JScrollPane
中(请参阅底部的屏幕截图这个 post).
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
public class DividerTest {
private final JSplitPane fSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
private final JTable fTable;
private final JScrollPane fScrollPane;
private boolean fHideTable = false;
public DividerTest() {
fTable = new JTable( createTableModel(50));
fScrollPane = new JScrollPane(fTable);
fSplitPane.setBottomComponent(fScrollPane);
fSplitPane.setTopComponent(createControlsPanel());
fSplitPane.setDividerLocation(0.5);
}
private JPanel createControlsPanel(){
JPanel result = new JPanel();
result.setLayout(new BoxLayout(result, BoxLayout.PAGE_AXIS));
final JCheckBox checkBox = new JCheckBox("Make table invisible before adjusting divider");
checkBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
fHideTable = checkBox.isSelected();
}
});
result.add(checkBox);
JButton upperRow = new JButton("Select row 10");
upperRow.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
selectRowInTableAndScroll(10);
}
});
result.add(upperRow);
JButton lowerRow = new JButton("Select row 45");
lowerRow.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
selectRowInTableAndScroll(45);
}
});
result.add(lowerRow);
JButton hideBottom = new JButton("Hide bottom");
hideBottom.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (fHideTable) {
fScrollPane.setVisible(false);
}
fSplitPane.setDividerLocation(1.0);
}
});
result.add(hideBottom);
JButton showBottom = new JButton("Show bottom");
showBottom.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fScrollPane.setVisible(true);
fSplitPane.setDividerLocation(0.5);
}
});
result.add(showBottom);
return result;
}
private void selectRowInTableAndScroll( int aRowIndex ){
fTable.clearSelection();
fTable.getSelectionModel().addSelectionInterval(aRowIndex, aRowIndex);
fTable.scrollRectToVisible(fTable.getCellRect(aRowIndex, 0, true));
}
public JComponent getUI(){
return fSplitPane;
}
private TableModel createTableModel(int aNumberOfRows){
Object[][] data = new Object[aNumberOfRows][1];
for( int i = 0; i < aNumberOfRows; i++ ){
data[i] = new String[]{"Row" + i};
}
return new DefaultTableModel(data, new String[]{"Column"});
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Test frame");
frame.getContentPane().add(new DividerTest().getUI());
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
});
}
}
不需要的行为
- 运行以上代码
- 按"Select row 10":第10行被选中可见
- 按"Select row 45":第45行被选中可见
- 单击 "Hide bottom" 按钮。这将调整
JSplitPane
的分隔线,以便只有上面板可见
- 单击 "Select row 10" 按钮。你当然什么也看不到,因为 table 还不可见
- 单击 "Show bottom" 按钮。分隔符已调整,但第 10 行隐藏在 header 下方。我希望它无需滚动即可显示。
想要的行为
重复上述步骤,但确保选中 "Make table invisible before adjusting divider" 复选框。这将在隐藏底部面板之前在 JTable
周围的 JScrollPane
上调用 setVisible(false)
。
通过这样做,在最后一步中,第 10 行将显示为最顶行,这正是我想要的。 我只是不想让滚动窗格不可见:在我的实际应用程序中,分隔线以动画方式调整,因此您希望在 table 期间保持可见动画。
截图
不需要:执行上述步骤后第 10 行不可见
需要:执行上述步骤后第 10 行可见
环境
我认为这无关紧要,但以防万一:我在 Linux 系统上使用 JDK7。
不太确定 scrollRectToVisible() 在做什么。
您或许可以使用 JViewport.setViewPosition(...)
方法。
Rectangle r = fTable.getCellRect(aRowIndex, 0, true);
Point p = new Point(r.x, r.y);
fScrollPane.getViewport().setViewPosition( p );
在这种情况下,selected 行将始终显示在视口的顶部(如果可能)。因此视口将始终滚动,除非 selected 行当前位于顶部。如果第一行位于视口的顶部并且您 select 第 10 行,视口将滚动以在顶部显示第 10 行,则使用此方法。
但是,此行为与使用 scrollRectToVisible() 方法略有不同。当使用 scrollRectToVisible() 方法时,仅当矩形不在视口的可见部分时才滚动视口。如果第一行位于视口的顶部并且您 select 第 10 行,则使用此方法视口将不会滚动,因为第 10 行已经在视口中可见。
不知道这种功能上的变化是否可以接受。
请注意,如果您不想让视口在 select 行时自动滚动,您可以尝试类似的操作:
JViewport viewport = fScrollPane.getViewport();
Rectangle viewRect = viewport.getViewRect();
Rectangle r = fTable.getCellRect(aRowIndex, 0, true);
Point p = new Point(r.x, r.y);
if (! viewRect.contains(p))
viewport.setViewPosition( p );
这似乎是由 JViewport
处理 scrollRectToVisible
调用的方式引起的,因为它的大小小于所需的矩形。它在 JavaDocs 中包含一条(有点模糊,但可能相关的)评论:
Note that this method will not scroll outside of the valid viewport; for example, if contentRect is larger than the viewport, scrolling will be confined to the viewport's bounds.
我没有仔细阅读完整的代码,也没有做所有的数学计算并检查所有的案例。所以警告:以下解释包含完全相同的挥手。但是在这个特殊情况下这对我意味着什么的简单描述:
当底部隐藏时(通过相应设置分隔符位置),则 JScrollPane
及其 JViewport
的高度为 0。现在,当请求 scrollRectToVisible
使用高度为 20 的矩形(例如,对于一个 table 行),然后它会注意到这不适合。根据 JViewport
的当前视图位置,这可能会导致视口滚动,以便此矩形的 底部 可见。
(您可以观察到这一点:手动拖动分隔线位置 ,这样 table 行的大约 一半 可见。单击 "Select row 45" 按钮时,行的 上半部分 将可见。单击 "Select row 10" 按钮时,行的 下半部分将可见 一半的行将可见)
这里似乎对我有用的一个实用解决方案是确保它始终滚动,以便矩形的 top 可见(即使矩形不可见)完全适合视口!)。像这样:
private void selectRowInTableAndScroll(int aRowIndex)
{
fTable.clearSelection();
fTable.getSelectionModel().addSelectionInterval(aRowIndex, aRowIndex);
Rectangle r = fTable.getCellRect(aRowIndex, 0, true);
r.height = Math.min(fScrollPane.getHeight(), r.height);
fTable.scrollRectToVisible(r);
}
但我不能保证当 动画 开始播放时,这会对您产生预期的效果...
当我调用 JTable#scrollRectToVisible
时,在某些情况下,我要显示的行隐藏在 header 下方。
这个问题的其余部分只有在使用以下代码时才有意义。这是一个非常简单的程序,我用它来说明问题。它显示了一个 UI,其中包含一个 JSplitPane
,上半部分带有一些控制按钮,下半部分包含一个 JTable
,包裹在一个 JScrollPane
中(请参阅底部的屏幕截图这个 post).
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
public class DividerTest {
private final JSplitPane fSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
private final JTable fTable;
private final JScrollPane fScrollPane;
private boolean fHideTable = false;
public DividerTest() {
fTable = new JTable( createTableModel(50));
fScrollPane = new JScrollPane(fTable);
fSplitPane.setBottomComponent(fScrollPane);
fSplitPane.setTopComponent(createControlsPanel());
fSplitPane.setDividerLocation(0.5);
}
private JPanel createControlsPanel(){
JPanel result = new JPanel();
result.setLayout(new BoxLayout(result, BoxLayout.PAGE_AXIS));
final JCheckBox checkBox = new JCheckBox("Make table invisible before adjusting divider");
checkBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
fHideTable = checkBox.isSelected();
}
});
result.add(checkBox);
JButton upperRow = new JButton("Select row 10");
upperRow.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
selectRowInTableAndScroll(10);
}
});
result.add(upperRow);
JButton lowerRow = new JButton("Select row 45");
lowerRow.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
selectRowInTableAndScroll(45);
}
});
result.add(lowerRow);
JButton hideBottom = new JButton("Hide bottom");
hideBottom.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (fHideTable) {
fScrollPane.setVisible(false);
}
fSplitPane.setDividerLocation(1.0);
}
});
result.add(hideBottom);
JButton showBottom = new JButton("Show bottom");
showBottom.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fScrollPane.setVisible(true);
fSplitPane.setDividerLocation(0.5);
}
});
result.add(showBottom);
return result;
}
private void selectRowInTableAndScroll( int aRowIndex ){
fTable.clearSelection();
fTable.getSelectionModel().addSelectionInterval(aRowIndex, aRowIndex);
fTable.scrollRectToVisible(fTable.getCellRect(aRowIndex, 0, true));
}
public JComponent getUI(){
return fSplitPane;
}
private TableModel createTableModel(int aNumberOfRows){
Object[][] data = new Object[aNumberOfRows][1];
for( int i = 0; i < aNumberOfRows; i++ ){
data[i] = new String[]{"Row" + i};
}
return new DefaultTableModel(data, new String[]{"Column"});
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Test frame");
frame.getContentPane().add(new DividerTest().getUI());
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
});
}
}
不需要的行为
- 运行以上代码
- 按"Select row 10":第10行被选中可见
- 按"Select row 45":第45行被选中可见
- 单击 "Hide bottom" 按钮。这将调整
JSplitPane
的分隔线,以便只有上面板可见 - 单击 "Select row 10" 按钮。你当然什么也看不到,因为 table 还不可见
- 单击 "Show bottom" 按钮。分隔符已调整,但第 10 行隐藏在 header 下方。我希望它无需滚动即可显示。
想要的行为
重复上述步骤,但确保选中 "Make table invisible before adjusting divider" 复选框。这将在隐藏底部面板之前在 JTable
周围的 JScrollPane
上调用 setVisible(false)
。
通过这样做,在最后一步中,第 10 行将显示为最顶行,这正是我想要的。 我只是不想让滚动窗格不可见:在我的实际应用程序中,分隔线以动画方式调整,因此您希望在 table 期间保持可见动画。
截图
不需要:执行上述步骤后第 10 行不可见
需要:执行上述步骤后第 10 行可见
环境
我认为这无关紧要,但以防万一:我在 Linux 系统上使用 JDK7。
不太确定 scrollRectToVisible() 在做什么。
您或许可以使用 JViewport.setViewPosition(...)
方法。
Rectangle r = fTable.getCellRect(aRowIndex, 0, true);
Point p = new Point(r.x, r.y);
fScrollPane.getViewport().setViewPosition( p );
在这种情况下,selected 行将始终显示在视口的顶部(如果可能)。因此视口将始终滚动,除非 selected 行当前位于顶部。如果第一行位于视口的顶部并且您 select 第 10 行,视口将滚动以在顶部显示第 10 行,则使用此方法。
但是,此行为与使用 scrollRectToVisible() 方法略有不同。当使用 scrollRectToVisible() 方法时,仅当矩形不在视口的可见部分时才滚动视口。如果第一行位于视口的顶部并且您 select 第 10 行,则使用此方法视口将不会滚动,因为第 10 行已经在视口中可见。
不知道这种功能上的变化是否可以接受。
请注意,如果您不想让视口在 select 行时自动滚动,您可以尝试类似的操作:
JViewport viewport = fScrollPane.getViewport();
Rectangle viewRect = viewport.getViewRect();
Rectangle r = fTable.getCellRect(aRowIndex, 0, true);
Point p = new Point(r.x, r.y);
if (! viewRect.contains(p))
viewport.setViewPosition( p );
这似乎是由 JViewport
处理 scrollRectToVisible
调用的方式引起的,因为它的大小小于所需的矩形。它在 JavaDocs 中包含一条(有点模糊,但可能相关的)评论:
Note that this method will not scroll outside of the valid viewport; for example, if contentRect is larger than the viewport, scrolling will be confined to the viewport's bounds.
我没有仔细阅读完整的代码,也没有做所有的数学计算并检查所有的案例。所以警告:以下解释包含完全相同的挥手。但是在这个特殊情况下这对我意味着什么的简单描述:
当底部隐藏时(通过相应设置分隔符位置),则 JScrollPane
及其 JViewport
的高度为 0。现在,当请求 scrollRectToVisible
使用高度为 20 的矩形(例如,对于一个 table 行),然后它会注意到这不适合。根据 JViewport
的当前视图位置,这可能会导致视口滚动,以便此矩形的 底部 可见。
(您可以观察到这一点:手动拖动分隔线位置 ,这样 table 行的大约 一半 可见。单击 "Select row 45" 按钮时,行的 上半部分 将可见。单击 "Select row 10" 按钮时,行的 下半部分将可见 一半的行将可见)
这里似乎对我有用的一个实用解决方案是确保它始终滚动,以便矩形的 top 可见(即使矩形不可见)完全适合视口!)。像这样:
private void selectRowInTableAndScroll(int aRowIndex)
{
fTable.clearSelection();
fTable.getSelectionModel().addSelectionInterval(aRowIndex, aRowIndex);
Rectangle r = fTable.getCellRect(aRowIndex, 0, true);
r.height = Math.min(fScrollPane.getHeight(), r.height);
fTable.scrollRectToVisible(r);
}
但我不能保证当 动画 开始播放时,这会对您产生预期的效果...