JScrollPane Not Wide Enough When Vertical Scrollbar Appears

Viewed 8273

I have two JScrollPanes in the same window. The one on the left is large enough to display the contents of the contained panel. The one on the right is not large enough to display its contents and thus it needs to create a vertical scrollbar.

JScrollPane Issue

But as you can see, the problem is that when the vertical scrollbar appears, the scrollbar appears on the inside of the JScrollPane. It covers up content contained inside and thus a horizontal scrollbar is necessary to show everything. I want that fixed.

I realize that I can turn the vertical scrollbar on all the time, but for aesthetic reasons, I only want for it to appear when necessary, without making it necessary for a horizontal scrollpane to appear.

EDIT: My code for starting this is as simple as can be:

JScrollPane groupPanelScroller = new JScrollPane(groupPanel);
this.add(groupPanelScroller, "align center");

I am using MigLayout (MigLayout.com), but this problem seems to appear no matter what layout manager I am using. Also, if I shrink the window so that the left panel is no longer large enough to display everything, the same behavior as the right panel occurs.

6 Answers

This bug is now several years old but still unfixed, so I'll add my contribution.

You can also work around the bug by implementing javax.swing.Scrollable on the view compoment of your JScrollPane. The ScrollPaneLayout checks the methods of this interface to determine whether to display a horizontal scrollbar first, before falling back to its buggy calculation.

You can implement the Scrollable interface so that the view component is treated exactly as it was before implementing Scrollable, except for the method determining whether a horizontal scrollbar is displayed.

A concrete example to demonstrate this which I haven't tested in isolation, but have extracted from our codebase. Change:

JPanel panel = new JPanel();
JScrollPane pane = new JScrollPane(panel);

to

class ScrollableJPanel extends JPanel implements Scrollable {
    @Override public Dimension getPreferredScrollableViewportSize() {
        return getPreferredSize();
    }

    @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        JScrollPane scrollPane = (JScrollPane)getParent();
        return orientation == SwingConstants.VERTICAL ? scrollPane.getVerticalScrollBar().getUnitIncrement() : scrollPane.getHorizontalScrollBar().getUnitIncrement();
    }

    @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        JScrollPane scrollPane = (JScrollPane)getParent();
        return orientation == SwingConstants.VERTICAL ? scrollPane.getVerticalScrollBar().getBlockIncrement() : scrollPane.getHorizontalScrollBar().getBlockIncrement();
    }

    @Override public boolean getScrollableTracksViewportHeight() {
        return false;
    }

    @Override public boolean getScrollableTracksViewportWidth() {
        Dimension sz = getSize();
        Dimension vsz = getViewportSize();
        return sz.width - 1 <= vsz.width; // This is the bodge
    }
}

JPanel panel = new ScrollableJPanel();
JScrollPane pane = new JScrollPane();

Whlie testing our application against a Java 1.8 rt.jar modified to print lots of debug output, we found that the value was only out by one pixel - so that's the constant we're using in the above bodge.

Incidentally this has been filed with Oracle https://bugs.openjdk.java.net/browse/JDK-8042020, but it's helpfully marked as "Won't Fix".

Related