【问题标题】:Multiple JScrollPane (Java) on different hierarchy layer: horizontal scroll bar issue不同层次结构层上的多个 JScrollPane (Java):水平滚动条问题
【发布时间】:2016-03-22 18:10:05
【问题描述】:

我正在努力解决以下问题:

我在布局的不同位置有多个 JScrollPanes。仅使用一个 JScrollPane 时一切正常。不幸的是,第二个让我陷入了深深的麻烦。

此外,还有以下要求:

-必须使用 JTextPane(需要富文本格式)

-没有第三方库(会让生活更轻松,但目前还不是 此处允许)

-JTextPane 的行为仍应与当前一样(解释:有 2 个 JTextPanes 由 JSplitter 分隔。由于 JScrollPanes 的数量不同,它们的行为不同。目标是在所有情况下的行为都相同(第一个))

下面是我写的代码:

public class ScrollExample extends JPanel {
    public ScrollExample() {
        super(new BorderLayout());
        JTextPane textPane1 = new JTextPane();
        textPane1.setEditorKit(new WrapEditorKit());

        JTextPane textPane2 = new JTextPane();
        textPane2.setEditorKit(new WrapEditorKit());

        JScrollPane scrollPaneText1 = new JScrollPane(textPane1);
        scrollPaneText1.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPaneText1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

        JScrollPane scrollPaneText2 = new JScrollPane(textPane2);
        scrollPaneText2.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPaneText2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(scrollPaneText2, BorderLayout.CENTER);
        panel.add(new JButton("Example"), BorderLayout.NORTH);

        JScrollPane secondScrollPane = new JScrollPane(panel);
        secondScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        secondScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

        JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPaneText1, secondScrollPane);
        splitPane.setDividerLocation(100);

        add(splitPane);

        textPane1.setText("ThisIsAVeryLongStringWhichRepeatsItselfThisIsAVeryLongStringWhichRepeatsItself ThisIsAVeryLongStringWhichRepeatsItself");
        textPane2.setText("ThisIsAVeryLongStringWhichRepeatsItselfThisIsAVeryLongStringWhichRepeatsItself ThisIsAVeryLongStringWhichRepeatsItself");
    }

    public static void main(String[] args) {
        ScrollExample example = new ScrollExample();
        JFrame frame = new JFrame("Example");
        frame.setLayout(new BorderLayout());
        frame.add(example, BorderLayout.CENTER);
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                frame.setBounds(100, 50, 600, 400);
                frame.setVisible(true);
            }
        });
    }

    public class WrapLabelView extends LabelView {
        public WrapLabelView(Element elem) {
            super(elem);
        }

        @Override
        public float getMinimumSpan(int axis) {
            switch(axis) {
                case View.X_AXIS:
                    return 0;
                case View.Y_AXIS:
                    return super.getMinimumSpan(axis);
                default:
                    throw new IllegalArgumentException("Invalid axis: " + axis);
            }
        }
    }

    public class WrapEditorKit extends StyledEditorKit {
        protected ViewFactory _factory = new WrapColumnFactory();

        @Override
        public ViewFactory getViewFactory() {
            return _factory;
        }
    }

    public class WrapColumnFactory implements ViewFactory {
        @Override
        public View create(Element elem) {
            switch(elem.getName()) {
                case AbstractDocument.ContentElementName:
                    return new WrapLabelView(elem);
                case AbstractDocument.ParagraphElementName:
                    return new ParagraphView(elem);
                case AbstractDocument.SectionElementName:
                    return new BoxView(elem, View.Y_AXIS);
                case StyleConstants.ComponentElementName:
                    return new ComponentView(elem);
                case StyleConstants.IconElementName:
                    return new IconView(elem);
            }
        return new LabelView(elem);
        }
    }
}

解释:

-JTextPane 的正确行为需要内部类(它会破坏“长”字而不是弄乱 UI)。

-JSplitPane 的上半部分显示了 JTextPane 如何正确运行(文本换行并在需要时添加滚动条(垂直))

-下部添加了一个随机按钮和 JTextPane(包括 JScrollPane)。该按钮仅用于说明,因为在该区域中,许多其他组件应成为 UI 的一部分。

现在问题出在 JSpitPane 的下部。 JTextPane 的 JScrollPane 的行为方式与没有第二个 JScrollPane 时的行为方式不同。

有谁知道或可以给我一个提示如何在两个 JTextPane 的 JScrollPanes 上获得相同的行为?

编辑#1:

@rdonuk 提出了一个可行的解决方案。我仍然更喜欢不依赖使用任何set(Preferred|Maximum|Minimum) 方法的解决方案。此外,我想出了一个适用于我的特定情况的解决方案,但可能不适用于其他 LayoutManager。此外,我也不喜欢这种方法,我仍在寻找更好的解决方案。

编辑 #2:

调整后的澄清要求。

编辑#3:

添加了第三个解决方案(由@MadProgrammer 提供)。

解决方案 1

See below for @rdonuk's solution

解决方案 2

我只是重写了 JScrollPanes 的 getPreferredSize 方法:

public class ScrollExample extends JPanel {
    public ScrollExample() {
        super(new BorderLayout());
        JTextPane textPane1 = new JTextPane();
        textPane1.setEditorKit(new WrapEditorKit());

        JTextPane textPane2 = new JTextPane();
        textPane2.setEditorKit(new WrapEditorKit());

        JScrollPane scrollPaneText1 = new JScrollPane(textPane1) {
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(1,1);
            }
        };
        scrollPaneText1.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPaneText1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

        JScrollPane scrollPaneText2 = new JScrollPane(textPane2) {
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(1,1);
            }
        };
        scrollPaneText2.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPaneText2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(scrollPaneText2, BorderLayout.CENTER);
        panel.add(new JButton("Example"), BorderLayout.NORTH);

        JScrollPane secondScrollPane = new JScrollPane(panel);
        secondScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        secondScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

        JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPaneText1, secondScrollPane);
        splitPane.setDividerLocation(100);

        add(splitPane);

        textPane1.setText("ThisIsAVeryLongStringWhichRepeatsItselfThisIsAVeryLongStringWhichRepeatsItself ThisIsAVeryLongStringWhichRepeatsItself");
        textPane2.setText("ThisIsAVeryLongStringWhichRepeatsItselfThisIsAVeryLongStringWhichRepeatsItself ThisIsAVeryLongStringWhichRepeatsItself");
    }

    public static void main(String[] args) {
        ScrollExample example = new ScrollExample();
        JFrame frame = new JFrame("Example");
        frame.setLayout(new BorderLayout());
        frame.add(example, BorderLayout.CENTER);
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                frame.setBounds(100, 50, 600, 400);
                frame.setVisible(true);
            }
        });
    }

    public class WrapLabelView extends LabelView {
        public WrapLabelView(Element elem) {
            super(elem);
        }

        @Override
        public float getMinimumSpan(int axis) {
            switch(axis) {
                case View.X_AXIS:
                    return 0;
                case View.Y_AXIS:
                    return super.getMinimumSpan(axis);
                default:
                    throw new IllegalArgumentException("Invalid axis: " + axis);
            }
        }
    }

    public class WrapEditorKit extends StyledEditorKit {
        protected ViewFactory _factory = new WrapColumnFactory();

        @Override
        public ViewFactory getViewFactory() {
            return _factory;
        }
    }

    public class WrapColumnFactory implements ViewFactory {
        @Override
        public View create(Element elem) {
            switch(elem.getName()) {
                case AbstractDocument.ContentElementName:
                    return new WrapLabelView(elem);
                case AbstractDocument.ParagraphElementName:
                    return new ParagraphView(elem);
                case AbstractDocument.SectionElementName:
                    return new BoxView(elem, View.Y_AXIS);
                case StyleConstants.ComponentElementName:
                    return new ComponentView(elem);
                case StyleConstants.IconElementName:
                    return new IconView(elem);
            }
        return new LabelView(elem);
        }
    }
}

解决方案 3

See below for @MadProgrammer's solution

编辑#4:

@MadProgrammer 和 @rdonuk 提供的两种解决方案都有效,总体上可能比我的要好,但由于最终的 UI 相当复杂,而且这两种解决方案要么需要大量工作,要么在特定情况下不起作用我会坚持我的解决方案(解决方案 #2)。

【问题讨论】:

  • 你的 GUI 应该是什么样子的?
  • 看看Scrollable 接口,它可以用来向JScrollPane 提供关于它的“首选”视口大小应该是什么的提示
  • @GilbertLeBlanc :上面的例子是一个简单的抽象。最后,用户界面会有很多组件,不适合屏幕(受限于屏幕宽度,最外面的 JScrollPane 上不会有水平滚动条)。因此,添加了外部 JScrollPane(JSplitter 的底部)。内容将是动态的并且取决于用户交互。但是 JTextPane(包括 JScrollPane)的行为应该与 JSplitPane 的上部一样。
  • @GilbertLeBlanc:第 2 部分:JTextPane 的内容也是动态的。一切都可能非常合适,或者可能一团糟。因此,它应该包含滚动条,因为稍后会限制组件的高度(并且宽度应该适合屏幕,这是目前的问题)。
  • @MadProgrammer :正如其他问题中所建议的那样,我已经查看了 Scrollable 并覆盖了 getScrollableTracksViewPortWidth() 以返回 false。那根本不起作用(破坏了组件的大小调整)。因为它是组件的一部分,并且 JTextPane 的内容也是动态的,所以我不知道如何提供“首选”视口大小。有什么建议吗?

标签: java swing user-interface jscrollpane jtextpane


【解决方案1】:

创建一个实现ScrollableJPanel 并为getPreferredScrollableViewportSize 提供解决方案

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Rectangle;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextPane;
import javax.swing.Scrollable;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;

public class ScrollExample extends JPanel {

    public ScrollExample() {
        super(new BorderLayout());
        JTextPane textPane1 = new JTextPane();
        textPane1.setEditorKit(new WrapEditorKit());

        JTextPane textPane2 = new JTextPane();
        textPane2.setEditorKit(new WrapEditorKit());

        JScrollPane scrollPaneText1 = new JScrollPane(textPane1);
        scrollPaneText1.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPaneText1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

        JScrollPane scrollPaneText2 = new JScrollPane(textPane2);
        scrollPaneText2.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPaneText2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

        JPanel panel = new RestrictedPanel();
        panel.setLayout(new BorderLayout());
        panel.add(scrollPaneText2, BorderLayout.CENTER);
        panel.add(new JButton("Example"), BorderLayout.NORTH);

        JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPaneText1, panel);
        splitPane.setDividerLocation(100);

        add(splitPane);

        textPane1.setText("ThisIsAVeryLongStringWhichRepeatsItselfThisIsAVeryLongStringWhichRepeatsItself ThisIsAVeryLongStringWhichRepeatsItself");
        textPane2.setText("ThisIsAVeryLongStringWhichRepeatsItselfThisIsAVeryLongStringWhichRepeatsItself ThisIsAVeryLongStringWhichRepeatsItself");
    }

    public class RestrictedPanel extends JPanel implements Scrollable {

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return new Dimension(600, 200);
        }

        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 128;
        }

        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 128;
        }

        @Override
        public boolean getScrollableTracksViewportWidth() {
            return true;
        }

        @Override
        public boolean getScrollableTracksViewportHeight() {
            return true;
        }

    }

    public static void main(String[] args) {
        ScrollExample example = new ScrollExample();
        JFrame frame = new JFrame("Example");
        frame.setLayout(new BorderLayout());
        frame.add(example, BorderLayout.CENTER);
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                frame.setBounds(100, 50, 600, 400);
                frame.setVisible(true);
            }
        });
    }

    public class WrapLabelView extends LabelView {

        public WrapLabelView(Element elem) {
            super(elem);
        }

        @Override
        public float getMinimumSpan(int axis) {
            switch (axis) {
                case View.X_AXIS:
                    return 0;
                case View.Y_AXIS:
                    return super.getMinimumSpan(axis);
                default:
                    throw new IllegalArgumentException("Invalid axis: " + axis);
            }
        }
    }

    public class WrapEditorKit extends StyledEditorKit {

        protected ViewFactory _factory = new WrapColumnFactory();

        @Override
        public ViewFactory getViewFactory() {
            return _factory;
        }
    }

    public class WrapColumnFactory implements ViewFactory {

        @Override
        public View create(Element elem) {
            switch (elem.getName()) {
                case AbstractDocument.ContentElementName:
                    return new WrapLabelView(elem);
                case AbstractDocument.ParagraphElementName:
                    return new ParagraphView(elem);
                case AbstractDocument.SectionElementName:
                    return new BoxView(elem, View.Y_AXIS);
                case StyleConstants.ComponentElementName:
                    return new ComponentView(elem);
                case StyleConstants.IconElementName:
                    return new IconView(elem);
            }
            return new LabelView(elem);
        }
    }
}

【讨论】:

  • 有效,这绝对是一个解决方案。尽管如此,这对我来说感觉有点“错误”。解释一下: (TextWrap) JTextPane 是自定义组件的一部分。那个自定义组件不应该照顾它的布局(JTextPane 是其中的一部分),而不是必须在任何地方添加一个 RestrictedPane,在哪里使用自定义组件?在解决方案#2中,我覆盖了(我的(子)自定义组件的)getPreferredSize方法,这导致了相同的结果。作为一个摇摆新手,如果您能解释为什么您的解决方案是可行的方法,我将不胜感激。
  • 您之前发布的链接涉及 setXSize 方法,其中一些 cmets 建议自定义组件可能会覆盖 getXSize 方法(并且 cmets 也不清楚)。正如第一条评论中提到的(达到字符限制),我非常感谢一个简短的解释。
  • 不幸的是 - 经过一些测试 - 它的行为并不完全符合我的要求。您给出的示例效果很好,并且在我给出的示例中也可以。在实际应用程序中,不会有所有项目都正确排列的 BorderLayout。例如。如果您使用 BoxLayout (Y_AXIS) 并添加越来越多的组件,您最终会得到一个不起作用的外部滚动条。我试图调整返回的 Dimension 但没有成功。有什么建议吗?
  • 问题在于外部JScrollPane,它允许组件不受限制地扩展到其首选大小,BoxLayoutBorderLayout 更糟糕,因为它使用了maximumSize决策组件
【解决方案2】:

请尝试下面的代码。简而言之;我刚刚修复了 scrollPaneText2 的大小,并在您的主面板中添加了一个调整大小的侦听器。因此,如果用户调整窗口大小,scrollPaneText2 的大小将根据新大小再次固定。

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextPane;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;

public class ScrollExample extends JPanel {

    JScrollPane secondScrollPane;
    JScrollPane scrollPaneText2;
    JPanel panel;

    public ScrollExample() {
        super(new BorderLayout());

        addComponentListener(new ResizeListener());

        JTextPane textPane1 = new JTextPane();
        textPane1.setEditorKit(new WrapEditorKit());

        JTextPane textPane2 = new JTextPane();
        textPane2.setEditorKit(new WrapEditorKit());

        JScrollPane scrollPaneText1 = new JScrollPane(textPane1);
        scrollPaneText1.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPaneText1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

        scrollPaneText2 = new JScrollPane(textPane2);
        scrollPaneText2.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPaneText2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

        panel = new JPanel(new BorderLayout());
        panel.add(scrollPaneText2, BorderLayout.WEST);
        panel.add(new JButton("Example"), BorderLayout.NORTH);

        secondScrollPane = new JScrollPane(panel);
        secondScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        secondScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

        JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPaneText1, secondScrollPane);
        splitPane.setDividerLocation(100);

        add(splitPane);

        textPane1.setText("ThisIsAVeryLongStringWhichRepeatsItselfThisIsAVeryLongStringWhichRepeatsItself ThisIsAVeryLongStringWhichRepeatsItself");
        textPane2.setText("ThisIsAVeryLongStringWhichRepeatsItselfThisIsAVeryLongStringWhichRepeatsItself ThisIsAVeryLongStringWhichRepeatsItself");
    }

    public void adjustComponents() {
        panel.remove(scrollPaneText2);
        Dimension dimension = new Dimension();
        int nScrollWidth = secondScrollPane.getVerticalScrollBar().getWidth();
        dimension.setSize(secondScrollPane.getVisibleRect().getWidth()-nScrollWidth, scrollPaneText2.getHeight());
        scrollPaneText2.setPreferredSize(dimension);
        scrollPaneText2.setMaximumSize(dimension);

        panel.add(scrollPaneText2);
        repaint();
    }

    public static void main(String[] args) {
        final ScrollExample example = new ScrollExample();
        final JFrame frame = new JFrame("Example");
        frame.setLayout(new BorderLayout());
        frame.add(example, BorderLayout.CENTER);
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                frame.setBounds(100, 50, 600, 400);
                frame.setVisible(true);
            }
        });
    }

    public class WrapLabelView extends LabelView {
        public WrapLabelView(Element elem) {
            super(elem);
        }

        @Override
        public float getMinimumSpan(int axis) {
            switch(axis) {
                case View.X_AXIS:
                    return 0;
                case View.Y_AXIS:
                    return super.getMinimumSpan(axis);
                default:
                    throw new IllegalArgumentException("Invalid axis: " + axis);
            }
        }
    }

    public class WrapEditorKit extends StyledEditorKit {
        protected ViewFactory _factory = new WrapColumnFactory();

        @Override
        public ViewFactory getViewFactory() {
            return _factory;
        }
    }

    public class WrapColumnFactory implements ViewFactory {
        @Override
        public View create(Element elem) {
            switch(elem.getName()) {
                case AbstractDocument.ContentElementName:
                    return new WrapLabelView(elem);
                case AbstractDocument.ParagraphElementName:
                    return new ParagraphView(elem);
                case AbstractDocument.SectionElementName:
                    return new BoxView(elem, View.Y_AXIS);
                case StyleConstants.ComponentElementName:
                    return new ComponentView(elem);
                case StyleConstants.IconElementName:
                    return new IconView(elem);
            }
        return new LabelView(elem);
        }
    }

    public class ResizeListener extends ComponentAdapter {
        @Override
        public void componentResized(ComponentEvent e) {
            ScrollExample scrollExample = (ScrollExample) e.getSource();
            scrollExample.adjustComponents();
        }
    }
}

【讨论】:

  • @rdonuk :该解决方案似乎有效,但正如@MadProgrammer 提到的,如果可能的话,我更喜欢不依赖set(Preferred|Maximum|Minimum)Size 的解决方案。我会赞成您的回答,但不幸的是,由于我的声誉积分(新帐户),这是不可能的。如果没有set(Preferred|Maximum|Minimum)Size 找不到解决方案,我会将其标记为解决我问题的答案。
  • 好吧。如果您找到其他解决方案,请在此处发布。
  • @rdonuk :将您的方法添加到可能的解决方案中。此外,我添加了另一种适用于我的情况的解决方案。但我离满意还差得很远。
  • @CodeMonkey 请检查我的第二个解决方案。
【解决方案3】:

请参阅我最初的帖子。示例代码中有 3 种解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-19
    • 1970-01-01
    • 2021-11-27
    • 2018-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多