簡體   English   中英

當垂直滾動條出現時,JScrollPane不夠寬

[英]JScrollPane Not Wide Enough When Vertical Scrollbar Appears

我在同一個窗口中有兩個JScrollPanes。 左側的那個足夠大,可以顯示所包含面板的內容。 右邊的那個不夠大,無法顯示其內容,因此需要創建一個垂直滾動條。

JScrollPane問題

但正如您所看到的,問題是當出現垂直滾動條時,滾動條會出現在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;
        }
    }
}

這是罪魁禍首,因為

  • 它將視圖pref與視口pref進行比較
  • 他們大部分時間都是一樣的

結果就是你所看到的:滾動條與視圖重疊(在切斷一些寬度的意義上)。

黑客攻擊是一個自定義的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的整個邏輯需要改進......

總結您的選擇:

  • 調整(MigLayout)componentConstraint中的pref寬度。 這是最簡單的,缺點是在滾動條未顯示的情況下附加尾隨空格
  • 修復scrollPaneLayout。 需要一些努力和測試(參見核心ScrollPaneLayout的代碼需要做什么),優點是滾動條帶有一致的填充
  • 不是手動設置組件上的pref寬度的選項

下面是代碼示例:

// 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, BoxLayoutGridBagLayout

好吧我有完全相同的問題:

  • 動態添加(子)面板到(主)面板,放入jscrollpane(我想創建一個垂直的面板列表)
  • jscrollpane的垂直滾動條與我的內容重疊(即我的子面板)。

我試圖在沒有真正解決方案的情況下跟蹤互聯網(和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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM