When I call JTable#scrollRectToVisible
, the row I want to show is hidden underneath the header in certain situations.
The rest of this question only makes sense when using the following code. This is a very simply program which I use to illustrate the problem. It shows a UI containing a JSplitPane
with in the upper part some control buttons, and the lower part contains a JTable
wrapped in a JScrollPane
(see screenshots at the bottom of this 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);
}
});
}
}
Unwanted behavior
JSplitPane
so that only the upper panel is visibleWanted behavior
Repeat the steps from above, but make sure the "Make table invisible before adjusting divider" checkbox is selected. This will call setVisible(false)
on the JScrollPane
around the JTable
before hiding the bottom panel.
By doing this, in the last step row 10 will be visible as the top most row, which is what I want. I just do not want to turn the scrollpane invisible: in my real application, the divider is adjusted in an animated way and as such you want to keep the table visible during the animation.
Screenshots
Unwanted: row 10 is invisible after performing the aforementioned steps
Wanted: row 10 is visible after performing the aforementioned steps
Environment
I do not think it will matter, but just in case: I am using JDK7 on a Linux system.
The JTable is used to display and edit regular two-dimensional tables of cells. See How to Use Tables in The Java Tutorial for task-oriented documentation and examples of using JTable .
The TableModel interface specifies the methods the JTable will use to interrogate a tabular data model. The JTable can be set up to display any data model which implements the TableModel interface with a couple of lines of code: TableModel myData = new MyTableModel(); JTable table = new JTable(myData);
The JTable class is used to display data in tabular form. It is composed of rows and columns.
This seems to be caused by the way how the JViewport
handles the scrollRectToVisible
calls for the cases that its size is smaller than the desired rectangle. It contains a (somewhat fuzzy, but probably related) comment in the 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.
I did not go though the complete code and do all the maths and check all the cases. So a warning: The following explainations contain quite same hand-waving. But a simplified description of what this means for me in this particular case:
When the bottom part is hidden (by setting the divider location accordingly), then this height of the JScrollPane
and its JViewport
is 0. Now, when requesting to scrollRectToVisible
with a rectangle that has a height of 20 (for one table row, as an example), then it will notice that this does not fit. Depending on the current view position of the JViewport
, this may cause to viewport to be scrolled so that the bottom of this rectangle is visible.
(You can observe this: Drag the divider location manually, so that approximately half of one table row is visible. When clicking the "Select row 45" button, the upper half of the row will be visible. When clicking the "Select row 10" button, then the lower half of the row will be visible)
One pragmatic solution here that seemed to work for me was to make sure that it will always scroll so that the top of the rectangle is visible (even when the rectangle does not at all fit into the viewport!). Like this:
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);
}
But I can't promise that this will have the desired effect for you, when an animation comes into play...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With