Correctly implement the Scrollable interface

Viewed 90

I'm trying to implement the Swing Scrollable interface on a JPanel.
However, it shows a strange behavior which I do not understand.

enter image description here

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;
    }
  }
}
1 Answers

Just my thought, your design is not fully consistent, since JScrollPane's viewport is based on preferredSize, while you track viewport width and height till the minimum size.

A possible workaround is to set the preferred size of the scrollable to the minimum size after packing the frame, this will avoid the viewport to expand to the original preferred size when the scrollbar is activated.

    frame.pack();
    panel.setPreferredSize(panel.getMinimumSize());
    frame.setVisible(true);
Related