[英]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.