Adjusting a JScrollPane with nested child JComponents
In Robot Overlord have a JScrollPane in Java Swing and inside it is a JPanel that contains other JPanels and so on and way WAY down at the bottom were the selectable elements like JTextField and JButton. When I hit the TAB key (or SHIFT+TAB) to move focus between components I want the newly focussed component to stay on screen – in other words, make the computer move the scroll bars just enough to bring the focus component into view in the Viewport.
The top answer I could find on Stack Exchange said to do either
// either call on child with zero offset
child.scrollRectToVisible(new Rectangle(child.getSize());
// or call on child's parent with child bounds
child.getParent().scrollRectToVisible(child.getBounds());
Which sounds great, but doesn’t work with nested items. JComponent.getSize() won’t return the absolute position of the component relative to the top-most JPanel in the Viewport. JComponent.getBounds() is slightly better, with an X and Y value relative to its parent.
The solution
Recursion to the rescue!
public void focusGained(FocusEvent e) {
// the thing that got focus
Component c = e.getComponent();
// the bounds of the element
Rectangle rec = c.getBounds();
// add up all the parent offsets until we get to the JScrollPane.
Container c0 = null;
Container c1 = c.getParent();
while( (c1!=null) && !(c1 instanceof JScrollPane) ) {
Rectangle r2 = c1.getBounds();
rec.x += r2.x;
rec.y += r2.y;
// don't forget the last component before the JScrollPane.
// we'll need it later.
c0 = c1;
c1 = c1.getParent();
}
((JComponent)c0).scrollRectToVisible(rec);
}
It would have been really nice if calling scrollRectToVisible() on the first component would work its way up the parent chain automatically, but until that beautiful day this is your workaround.