正确实现Scrollable接口

时间:2021-05-13 20:30:40

标签: java swing

我正在尝试在 Scrollable 上实现 Swing JPanel 接口。
但是,它显示了一种我不理解的奇怪行为。

enter image description here

正如您在 GIF 中看到的那样,面板会缩小到最小尺寸,并且只有在此时才显示滚动条,这没关系。

然而,面板并没有保持其最小尺寸,而是使用其首选尺寸(似乎)。
这种行为是预期的吗?我认为内面板应该保持收缩的最小尺寸,不要再次膨胀。

我需要改变什么才能达到那个结果?


示例代码:

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 个答案:

答案 0 :(得分:1)

只是我的想法,你的设计并不完全一致,因为 JScrollPane 的视口是基于首选大小的,而你跟踪视口的宽度和高度直到最小尺寸。

一种可能的解决方法是在打包框架后将可滚动的首选大小设置为最小尺寸,这将避免在激活滚动条时视口扩展到原始首选大小。

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