Search code examples
javaswinguser-interfacejscrollpanejtextpane

Multiple JScrollPane (Java) on different hierarchy layer: horizontal scroll bar issue


I'm struggling with the following issue:

I have multiple JScrollPanes at different positions in the layout. Everything works when while using only one JScrollPane. Unfortunately the second one is getting me into deep troubles.

Furthermore, there are the following requirements:

-A JTextPane has to be used (richt text formating is required)

-No third party libraries (would make life easier, but it isn't currently allowed here)

-The JTextPane should still behave as it currently does (Explanation: There are 2 JTextPanes separeted by a JSplitter. They behave differntly due to a different count of JScrollPanes. The goal is that the behave in all situations the same (first one))

Below is the code I've wrote:

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);
        }
    }
}

Explanation:

-The inner classes are needed for the right behaviour of the JTextPane (it will break "long" words instead of messing up the UI).

-The upper portion of the JSplitPane shows how the JTextPane behaves correctly (text wrapping and adding scrollbar (vertical) when needed)

-The lower portion adds a random button and the JTextPane (incl. the JScrollPane). The button is just for illustration as in this region many other components should take part of the UI.

The issue now is in the lower part of the JSpitPane. The JScrollPane of the JTextPane doesn't behave the same way as it does when there isn't a second JScrollPane.

Does anyone know or could give me a hint how to get the same behaviour on both JTextPane's JScrollPanes?

EDIT #1:

@rdonuk came up with a solution that works. I'd still prefer a solution without relying on using any of the set(Preferred|Maximum|Minimum) methods. Furthermore, i came up with a solution that works in my specific case, but might not work with other LayoutManager. Furthermore, I don't like that approach either and I'm still looking for a better solution.

EDIT #2:

Adjusted requirements for clarification.

EDIT #3:

Third solution added (provided by @MadProgrammer).

Solution 1

See below for @rdonuk's solution

Solution 2

I simply overwrote the getPreferredSize methods of the 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) {
            @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);
        }
    }
}

Solution 3

See below for @MadProgrammer's solution

EDIT #4:

Both of the solutions given by @MadProgrammer and @rdonuk work and might be better than mine in general but as the final UI is quite complex and both of the solutions either require a lot of work or won't work in that specific environment I'll stick to my solution (Solution #2).


Solution

  • See my initial post. There are 3 solutions that would work in the example code.