我在同一窗口中有两个JScrollPanes。左边的一个足够大,可以显示所包含面板的内容。右边的那个不够大,无法显示其内容,因此需要创建一个垂直滚动条。

但是正如您所看到的,问题在于当出现垂直滚动条时,滚动条出现在JScrollPane的内部。它掩盖了内部的内容,因此需要水平滚动条来显示所有内容。我要解决这个问题。

我意识到我可以一直打开垂直滚动条,但是出于美学原因,我只希望它在必要时出现,而不必使水平滚动 Pane 出现。

编辑:我的代码开始如此简单:

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

我正在使用MigLayout(MigLayout.com),但是无论我使用什么布局管理器,似乎都会出现此问题。另外,如果我缩小窗口以使左侧面板不再足够大以显示所有内容,则会发生与右侧面板相同的行为。

最佳答案

第一:组件级别的never-ever tweak the sizing hints。尤其是当您拥有功能强大的LayoutManager(例如MigLayout)时,则不是这样,它支持在管理器级别进行调整。

在代码中,调整以下各项的首选项大小:

// calculate some width to add to pref, f.i. to take the scrollbar width into account
final JScrollPane pane = new JScrollPane(comp);
int prefBarWidth = pane.getVerticalScrollBar().getPreferredSize().width;
// **do not**
comp.setPreferredSize(new Dimension(comp.getPreferredSize().width + prefBarWidth, ...);
// **do**
String pref = "(pref+" + prefBarWidth + "px)";
content.add(pane, "width " + pref);

也就是说:基本上,您在ScrollPaneLayout中遇到了一个(可争论的)错误。尽管看起来好像考虑了滚动条的宽度,但实际上并非在所有情况下都如此。 preferredLayoutSize中的相关代码段
// filling the sizes used for calculating the pref
Dimension extentSize = null;
Dimension viewSize = null;
Component view = null;

if (viewport != null) {
    extentSize = viewport.getPreferredSize();
    view = viewport.getView();
    if (view != null) {
        viewSize = view.getPreferredSize();
    } else {
        viewSize = new Dimension(0, 0);
    }
}

....

// the part trying to take the scrollbar width into account

if ((vsb != null) && (vsbPolicy != VERTICAL_SCROLLBAR_NEVER)) {
    if (vsbPolicy == VERTICAL_SCROLLBAR_ALWAYS) {
        prefWidth += vsb.getPreferredSize().width;
    }
    else if ((viewSize != null) && (extentSize != null)) {
        boolean canScroll = true;
        if (view instanceof Scrollable) {
            canScroll = !((Scrollable)view).getScrollableTracksViewportHeight();
        }
        if (canScroll &&
            // following condition is the **culprit**
            (viewSize.height > extentSize.height)) {
            prefWidth += vsb.getPreferredSize().width;
        }
    }
}

这是元凶,因为
  • 它将 View 偏好与视口(viewport)偏好进行比较
  • 在大多数情况下它们都是相同的

  • 结果就是您所看到的:滚动条与 View 重叠(在某种程度上切掉了宽度)。

    值得一提的是自定义ScrollPaneLayout,如果 View 的高度小于实际视口(viewport)的高度,则会添加滚动条的宽度,这是一个粗略的示例(请注意:不是生产质量)
    public static class MyScrollPaneLayout extends ScrollPaneLayout {
    
        @Override
        public Dimension preferredLayoutSize(Container parent) {
            Dimension dim =  super.preferredLayoutSize(parent);
            JScrollPane pane = (JScrollPane) parent;
            Component comp = pane.getViewport().getView();
            Dimension viewPref = comp.getPreferredSize();
            Dimension port = pane.getViewport().getExtentSize();
            // **Edit 2** changed condition to <= to prevent jumping
            if (port.height < viewPref.height) {
                dim.width += pane.getVerticalScrollBar().getPreferredSize().width;
            }
            return dim;
        }
    
    }
    

    编辑

    嗯...看到跳跃(如注释所述,在显示与未显示垂直滚动条之间):当我用另一个scrollPane替换示例中的文本字段,然后在“近”处调整大小时,其首选项宽度出现了问题。因此,这种技巧还不够好,可能是因为要求视口(viewport)的范围不正确(在布局过程的中间)。目前不知道如何做得更好。

    编辑2

    暂定跟踪:逐像素进行宽度更改时,感觉就像一次过的错误。将条件从<更改为<=似乎可以解决跳转问题-以始终增加滚动条宽度为代价。因此,总的来说,这将导致第一步具有更大的尾随插入;-)同时认为scollLlayout的整体逻辑需要改进...

    总结您的选择:
  • 在(MigLayout)componentConstraint中调整首选宽度。这是最简单的,缺点是在滚动条未显示
  • 的情况下增加了尾随空格
  • 修复scrollPaneLayout。需要一些努力和测试(请参见ScrollPaneLayout核心代码需要做什么),优点是在滚动条
  • 之外添加了一致的填充
  • 不是选项手动在组件
  • 上设置首选宽度

    以下是可使用的代码示例:
    // adjust the pref width in the component constraint
    MigLayout layout = new MigLayout("wrap 2", "[][]");
    final JComponent comp = new JPanel(layout);
    for (int i = 0; i < 10; i++) {
        comp.add(new JLabel("some item: "));
        comp.add(new JTextField(i + 5));
    }
    
    MigLayout outer = new MigLayout("wrap 2",
            "[][grow, fill]");
    JComponent content = new JPanel(outer);
    final JScrollPane pane = new JScrollPane(comp);
    int prefBarWidth = pane.getVerticalScrollBar().getPreferredSize().width;
    String pref = "(pref+" + prefBarWidth + "px)";
    content.add(pane, "width " + pref);
    content.add(new JTextField("some dummy") );
    Action action = new AbstractAction("add row") {
    
        @Override
        public void actionPerformed(ActionEvent e) {
            int count = (comp.getComponentCount() +1)/ 2;
            comp.add(new JLabel("some Item: "));
            comp.add(new JTextField(count + 5));
            pane.getParent().revalidate();
        }
    };
    frame.add(new JButton(action), BorderLayout.SOUTH);
    frame.add(content);
    frame.pack();
    frame.setSize(frame.getWidth()*2, frame.getHeight());
    frame.setVisible(true);
    
    // use a custom ScrollPaneLayout
    MigLayout layout = new MigLayout("wrap 2", "[][]");
    final JComponent comp = new JPanel(layout);
    for (int i = 0; i < 10; i++) {
        comp.add(new JLabel("some item: "));
        comp.add(new JTextField(i + 5));
    }
    
    MigLayout outer = new MigLayout("wrap 2",
            "[][grow, fill]");
    JComponent content = new JPanel(outer);
    final JScrollPane pane = new JScrollPane(comp);
    pane.setLayout(new MyScrollPaneLayout());
    content.add(pane);
    content.add(new JTextField("some dummy") );
    Action action = new AbstractAction("add row") {
    
        @Override
        public void actionPerformed(ActionEvent e) {
            int count = (comp.getComponentCount() +1)/ 2;
            comp.add(new JLabel("some Item: "));
            comp.add(new JTextField(count + 5));
            pane.getParent().revalidate();
        }
    };
    frame.add(new JButton(action), BorderLayout.SOUTH);
    frame.add(content);
    frame.pack();
    frame.setSize(frame.getWidth()*2, frame.getHeight());
    frame.setVisible(true);
    

    07-24 19:19
    查看更多