我在同一窗口中有两个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 重叠(在某种程度上切掉了宽度)。
值得一提的是自定义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的整体逻辑需要改进...总结您的选择:
以下是可使用的代码示例:
// 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);