I'm trying to implement the Swing Scrollable interface on a JPanel.
However, it shows a strange behavior which I do not understand.
As you can see in the GIF, the panel shrinks until its minimum size, and only at that point the scrollbar is shown, and that's ok.
However, the panel does not keep its minimum size but instead goes to its preferred one (it seems).
Is this behavior expected? I think the inner panel should maintain the shrinked minimum dimension and not expand again.
What do I have to change to get to that result?
The example code:
public class Main {
public static void main(final String[] args) {
final var frame = new JFrame("Example");
final var gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.anchor = GridBagConstraints.LINE_START;
gbc.weightx = 1.0;
final var innerPanel = new JPanel(new GridBagLayout());
innerPanel.add(new JLabel("<html>Just an example label. Here for the show but I must be a bit long.</html>"), gbc);
gbc.gridy = 1;
innerPanel.add(new InnerPanel(), gbc);
innerPanel.setMinimumSize(new Dimension(200, 0));
final var panel = new ScrollablePanel(new BorderLayout());
panel.add(innerPanel, BorderLayout.CENTER);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
frame.getContentPane().add(new JScrollPane(panel));
frame.pack();
frame.setVisible(true);
}
static class InnerPanel extends JPanel {
InnerPanel() {
super(new GridBagLayout());
final var gb = new GridBagConstraints();
gb.fill = GridBagConstraints.HORIZONTAL;
gb.anchor = GridBagConstraints.LINE_END;
gb.weightx = 1.0;
gb.insets = new Insets(3, 5, 3, 5);
gb.gridy = 0;
gb.gridx = 0;
add(new JLabel("Example"), gb);
gb.gridx++;
add(new JComboBox<String>(), gb);
gb.gridy++;
gb.gridx = 0;
add(new JLabel("Example"), gb);
gb.gridx++;
add(new JFormattedTextField(), gb);
gb.gridy++;
gb.gridx = 0;
gb.gridwidth = GridBagConstraints.REMAINDER;
gb.fill = GridBagConstraints.NONE;
add(new JButton("Example"), gb);
}
}
static class ScrollablePanel extends JPanel implements Scrollable {
private static final int INCREMENT = 10;
ScrollablePanel(final LayoutManager layout) {
super(layout);
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override
public int getScrollableUnitIncrement(
final Rectangle visibleRect,
final int orientation,
final int direction) {
final var currentPosition = orientation == SwingConstants.HORIZONTAL ? visibleRect.x : visibleRect.y;
if (direction < 0) {
final var newPosition = currentPosition - currentPosition / INCREMENT * INCREMENT;
return newPosition == 0 ? INCREMENT : newPosition;
}
return (currentPosition / INCREMENT + 1) * INCREMENT - currentPosition;
}
@Override
public int getScrollableBlockIncrement(
final Rectangle visibleRect,
final int orientation,
final int direction) {
return orientation == SwingConstants.HORIZONTAL ? visibleRect.width - INCREMENT : visibleRect.height - INCREMENT;
}
@Override
public boolean getScrollableTracksViewportWidth() {
final var parent = getParent();
return parent instanceof JViewport && parent.getWidth() > getMinimumSize().width;
}
@Override
public boolean getScrollableTracksViewportHeight() {
final var parent = getParent();
return parent instanceof JViewport && parent.getHeight() > getMinimumSize().height;
}
}
}
