第一:永远不要在组件级别调整大小提示。尤其是当您确实拥有强大的 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;
}
}
}
这是罪魁祸首,因为
- 它将视图首选项与视口首选项进行比较
- 他们大部分时间都是一样的
结果就是您所看到的:滚动条与视图重叠(在切断一些宽度的意义上)。
一个hack是一个自定义的ScrollPaneLayout,如果视图的高度小于实际的视口高度,它会添加滚动条宽度,这是一个粗略的例子(注意:不是生产质量)
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;
}
}
编辑
嗯...看到跳跃(在显示与不显示垂直滚动条之间,如评论中所述):当我用另一个滚动窗格替换示例中的文本字段时,然后调整“接近”其首选项宽度的大小会出现问题。所以hack不够好,可能是询问视口范围的时间不正确(在布局过程的中间)。目前不知道如何做得更好。
编辑 2
试探性跟踪:当逐像素改变宽度时,感觉就像一次性错误。将条件从 更改<
为<=
似乎可以解决跳跃问题 - 以始终添加滚动条宽度为代价。所以总的来说,这导致第一步有更广泛的尾随插图;-) 同时相信 scollLlayout 的整个逻辑需要改进......
总结您的选择:
- 调整 (MigLayout) 组件约束中的首选项宽度。这是最简单的,缺点是一个额外的尾随空格,以防滚动条不显示
- 修复 scrollPaneLayout。需要一些努力和测试(参见核心 ScrollPaneLayout 的代码需要做什么),优点是一致的填充 w/out 滚动条
- 不是手动设置组件上的首选项宽度的选项
以下是可以使用的代码示例:
// 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);