Tutorials

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.