[英]JScrollPane Not Wide Enough When Vertical Scrollbar Appears
我在同一个窗口中有两个JScrollPanes。 左侧的那个足够大,可以显示所包含面板的内容。 右边的那个不够大,无法显示其内容,因此需要创建一个垂直滚动条。
但正如您所看到的,问题是当出现垂直滚动条时,滚动条会出现在JScrollPane的内部 。 它覆盖了内部包含的内容,因此需要水平滚动条来显示所有内容。 我想要那个固定的。
我意识到我可以一直打开垂直滚动条,但出于美观原因,我只希望它在必要时出现,而不需要出现水平滚动条。
编辑:我的启动代码很简单:
JScrollPane groupPanelScroller = new JScrollPane(groupPanel);
this.add(groupPanelScroller, "align center");
我正在使用MigLayout(MigLayout.com),但无论我使用什么版面管理器,这个问题似乎都会出现。 此外,如果我缩小窗口以使左面板不再大到足以显示所有内容,则会出现与右面板相同的行为。
第一: 永远不要调整组件级别的大小提示 。 特别是当你有一个强大的LayoutManager,比如MigLayout,它支持在管理器级别进行调整时。
在代码中,调整任何内容的pref大小:
// 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;
}
}
}
这是罪魁祸首,因为
结果就是你所看到的:滚动条与视图重叠(在切断一些宽度的意义上)。
黑客攻击是一个自定义的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;
}
}
编辑
嗯...看到跳跃(在显示与不显示垂直滚动条之间,如评论中所述):当我将示例中的文本字段替换为另一个scrollPane时,然后调整“靠近”它的pref宽度显示问题。 所以hack不够好,可能是因为询问视口的范围是不正确的(在布局过程中)。 目前不知道,如何做得更好。
编辑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);
我想到了。 问题是,当滚动条变为可见时,JScrollPane符合内容的大小并与其重叠。 所以我决定让内容更加宽大,以使其适应滚动条,如果它变得可见。 这也可以通过使insets成为Scrollbar的宽度来完成,这可能是更正统的方式。
int scrollBarWidth = new JScrollPane().getVerticalScrollBar().getPreferredSize();
groupPanel.setLayout(new MigLayout("insets 1 " + scrollBarWidth + " 1 " + scrollBarWidth*1.5); //Adjust multipliers and widths to suit
//...
//Add everything in
//...
JScrollPane groupPanelScroller = new JScrollPane(groupPanel);
groupPanelScroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
this.add(groupPanelScroller, "align center");
编辑:上面的答案可能是更好的答案,但这是我对奇怪的原始答案,实际上调整了面板的大小。
JScrollPane groupPanelScroller = new JScrollPane(groupPanel);
groupPanel.setPreferredSize(new Dimension(groupPanel.getPreferredSize().width + groupPanelScroller.getVerticalScrollBar().getVisibleAmount()*2,
groupPanel.getPreferredSize().height));
groupPanelScroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
this.add(groupPanelScroller, "align center");
这样做会使内容大小相同,加上滚动条宽度的两倍。 由于所有内容都是居中的,所以它们看起来都是一样的,然后如果出现滚动条,则一侧的一些额外空间会被填充。
我遇到了同样的问题,经过几个小时的努力找到原因后来到这里。我最终实现了自己的ViewportLayout,并希望分享该解决方案。
根本问题是,ScrollPaneLayout不知道一个维度(在您的情况下是视图的高度)将被约束为最大值,因此它无法预先确定是否需要滚动条。 因此它无法调整其他灵活尺寸(在您的情况下是宽度)。
自定义ViewportLayout可以考虑这一点,向其父级询问允许的高度,从而允许ScrollPaneLayout相应地调整首选宽度:
public class ConstrainedViewPortLayout extends ViewportLayout {
@Override
public Dimension preferredLayoutSize(Container parent) {
Dimension preferredViewSize = super.preferredLayoutSize(parent);
Container viewportContainer = parent.getParent();
if (viewportContainer != null) {
Dimension parentSize = viewportContainer.getSize();
preferredViewSize.height = parentSize.height;
}
return preferredViewSize;
}
}
ViewportLayout设置如下:
scrollPane.getViewport().setLayout(new ConstrainedViewPortLayout());
这个bug现在好几年了,但仍然没有修复,所以我会添加我的贡献。
您还可以通过在JScrollPane的视图组件上实现javax.swing.Scrollable来解决该问题。 ScrollPaneLayout检查此接口的方法以确定是否首先显示水平滚动条,然后再回到其错误计算。
您可以实现Scrollable接口,以便视图组件的处理方式与实现Scrollable之前完全相同, 但确定是否显示水平滚动条的方法除外 。
一个具体的例子来证明这一点,我没有单独测试,而是从我们的代码库中提取。 更改:
JPanel panel = new JPanel();
JScrollPane pane = new JScrollPane(panel);
至
class ScrollableJPanel extends JPanel implements Scrollable {
@Override public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
JScrollPane scrollPane = (JScrollPane)getParent();
return orientation == SwingConstants.VERTICAL ? scrollPane.getVerticalScrollBar().getUnitIncrement() : scrollPane.getHorizontalScrollBar().getUnitIncrement();
}
@Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
JScrollPane scrollPane = (JScrollPane)getParent();
return orientation == SwingConstants.VERTICAL ? scrollPane.getVerticalScrollBar().getBlockIncrement() : scrollPane.getHorizontalScrollBar().getBlockIncrement();
}
@Override public boolean getScrollableTracksViewportHeight() {
return false;
}
@Override public boolean getScrollableTracksViewportWidth() {
Dimension sz = getSize();
Dimension vsz = getViewportSize();
return sz.width - 1 <= vsz.width; // This is the bodge
}
}
JPanel panel = new ScrollableJPanel();
JScrollPane pane = new JScrollPane();
在针对Java 1.8 rt.jar测试我们的应用程序以修改打印大量调试输出时,我们发现该值只有一个像素 - 所以这是我们在上面提到的那个常量。
顺便提一下,这已经提交给了Oracle https://bugs.openjdk.java.net/browse/JDK-8042020 ,但它被标记为“无法修复”。
这个图像谈论GridLayout
,两个部分都有相同的大小
使用适当的LayoutManager
接受不同的PreferredSize, BoxLayout
或GridBagLayout
好吧我有完全相同的问题:
我试图在没有真正解决方案的情况下跟踪互联网(和stackoverflow)上发现的大量变通方法!
这是我的理解,以及我的工作解决方案 :
似乎scrollpanelayout(jscrollpane使用的布局管理器)中存在一个错误,它无法正确计算其中组件的大小。
如果你把mainpanel.setPreferedSize(新的Dimension(1,1)),没有更多的重叠错误,但垂直滚动条(如果作为VERTICAL_SCROLLBAR_ALWAYS放置的事件)将不会滚动! 这是因为滚动窗格认为您的内容是1像素高度。
因此,只需遍历您的子面板并计算适当的高度:
//into your constructor or somewhere else JPanel mainpanel = new JPanel(); mainpanel.setLayout(new BoxLayout(mainpanel, BoxLayout.Y_AXIS)); JScrollPane scrollPane = new JScrollPane(mainpanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); //into another function to create dynamic content int height = 0; for (int i = 0; i < nb_subpanel_to_create; ++i) { JPanel subpanel = create_your_sub_panel(); height += subpanel.getPreferedSize().height; mainpanel.add(subpanel); } mainpanel.setPreferedSize(new Dimension(1, height);
奖金:
如果您希望将“list”中的内容叠加到顶部:
List<JComponent> cmps = new LinkedList<JComponent>(); int height = 0; for (int i = 0; i < nb_subpanel_to_create; ++i) { JPanel subpanel = create_your_sub_panel(); height += subpanel.getPreferedSize().height; cmps.add(subpanel); } mainpanel.add(stackNorth(subpanel)); mainpanel.setPreferedSize(new Dimension(1, height);
和stackNorth函数:
public static JPanel stackNorth(List<JComponent> components) { JPanel last = new JPanel(new BorderLayout()); JPanel first = last; for (JComponent jComponent : components) { last.add(jComponent, BorderLayout.NORTH); JPanel tmp = new JPanel(new BorderLayout()); last.add(tmp, BorderLayout.CENTER); last = tmp; } return first; }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.