Grains
Grains

Reputation: 950

JScrollPane inside a JScrollPane

I am trying to add a JScrollPane inside another JScrollPane. The inner JScrollPane are only going to scroll HORIZONTAL and the outer are only going to scroll VERTICAL.

In this picture you can see that the horizontal scroller works when adding mainPanel(but have not added horizontal scroller here yet)

enter image description here

Here is how it looks when adding outerVerticalScroller to the JFrame (the horizontal bar is gone):

enter image description here

Any idea how this could be done?

If I skip the mainPanel and instead type:

JScrollPane outerVerticalScroller = new JScrollPane(panelScroller, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

Then it looks perfect with both scrollers:

enter image description here

But I need that extra "mainPanel". Any ideas?

public class ScrollersTest extends JFrame {

    public ScrollersTest() {
        super("A JScrollPane inside a JScrollPane");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        init();
        setVisible(true);
        pack();
    }

    public void init() {
        ScrollablePanel panelScroller = new ScrollablePanel(new GridBagLayout());
        panelScroller.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);

        GridBagConstraints c = new GridBagConstraints();

        // INNER SCROLLPANE: ONLY SCROLL HORIZONTAL
        JTextArea innerText = new JTextArea("This text is inside a JScrollPane of its own. But no scroller is shown when the frames size is decreased. Wrong viewPort?....................................");
        JScrollPane innerHorizantalScroller = new JScrollPane(innerText, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        c.fill = GridBagConstraints.HORIZONTAL;
        c.weightx = 1;
        panelScroller.add(innerHorizantalScroller, c);

        // SET SIZE OF SCROLLER HEIGHT
        Dimension d = innerHorizantalScroller.getPreferredSize();
        innerHorizantalScroller.setPreferredSize(new Dimension(d.width, d.height * 2));
        innerHorizantalScroller.setMinimumSize(new Dimension(0, d.height * 2));

        // EXTRA PANEL NEEDED (BUT MAKES HORIZANTAL SCROLLING DISAPEAR)
        JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.add(panelScroller, BorderLayout.CENTER);

        // THE FRAME SCROLLPANE: ONLY SCROLL VERTICAL
        JScrollPane outerVerticalScroller = new JScrollPane(mainPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        // WORKS BUT NO MAINPANEL
        // JScrollPane outerVerticalScroller = new JScrollPane(panelScroller, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        getContentPane().add(outerVerticalScroller, BorderLayout.CENTER);
        // getContentPane().add(mainPanel, BorderLayout.CENTER); // WORKS_BUT_NO_VERTICAL_SCROLL
    }

    public static void main(String args[]) {
        new ScrollersTest();
    }

    static class ScrollablePanel extends JPanel implements Scrollable, SwingConstants {
        public enum ScrollableSizeHint {
            NONE, FIT, STRETCH;
        }

        public enum IncrementType {
            PERCENT, PIXELS;
        }

        private ScrollableSizeHint scrollableHeight = ScrollableSizeHint.NONE;
        private ScrollableSizeHint scrollableWidth = ScrollableSizeHint.NONE;

        private IncrementInfo horizontalBlock;
        private IncrementInfo horizontalUnit;
        private IncrementInfo verticalBlock;
        private IncrementInfo verticalUnit;

        /**
         * Default constructor that uses a FlowLayout
         */
        public ScrollablePanel() {
            this(new FlowLayout());
        }

        /**
         * Constuctor for specifying the LayoutManager of the panel.
         * 
         * @param layout
         *            the LayountManger for the panel
         */
        public ScrollablePanel(LayoutManager layout) {
            super(layout);

            IncrementInfo block = new IncrementInfo(IncrementType.PERCENT, 100);
            IncrementInfo unit = new IncrementInfo(IncrementType.PERCENT, 10);

            setScrollableBlockIncrement(HORIZONTAL, block);
            setScrollableBlockIncrement(VERTICAL, block);
            setScrollableUnitIncrement(HORIZONTAL, unit);
            setScrollableUnitIncrement(VERTICAL, unit);
        }

        /**
         * Get the height ScrollableSizeHint enum
         * 
         * @return the ScrollableSizeHint enum for the height
         */
        public ScrollableSizeHint getScrollableHeight() {
            return scrollableHeight;
        }

        /**
         * Set the ScrollableSizeHint enum for the height. The enum is used to
         * determine the boolean value that is returned by the
         * getScrollableTracksViewportHeight() method. The valid values are:
         * 
         * ScrollableSizeHint.NONE - return "false", which causes the height of
         * the panel to be used when laying out the children
         * ScrollableSizeHint.FIT - return "true", which causes the height of
         * the viewport to be used when laying out the children
         * ScrollableSizeHint.STRETCH - return "true" when the viewport height
         * is greater than the height of the panel, "false" otherwise.
         * 
         * @param scrollableHeight
         *            as represented by the ScrollableSizeHint enum.
         */
        public void setScrollableHeight(ScrollableSizeHint scrollableHeight) {
            this.scrollableHeight = scrollableHeight;
            revalidate();
        }

        /**
         * Get the width ScrollableSizeHint enum
         * 
         * @return the ScrollableSizeHint enum for the width
         */
        public ScrollableSizeHint getScrollableWidth() {
            return scrollableWidth;
        }

        /**
         * Set the ScrollableSizeHint enum for the width. The enum is used to
         * determine the boolean value that is returned by the
         * getScrollableTracksViewportWidth() method. The valid values are:
         * 
         * ScrollableSizeHint.NONE - return "false", which causes the width of
         * the panel to be used when laying out the children
         * ScrollableSizeHint.FIT - return "true", which causes the width of the
         * viewport to be used when laying out the children
         * ScrollableSizeHint.STRETCH - return "true" when the viewport width is
         * greater than the width of the panel, "false" otherwise.
         * 
         * @param scrollableWidth
         *            as represented by the ScrollableSizeHint enum.
         */
        public void setScrollableWidth(ScrollableSizeHint scrollableWidth) {
            this.scrollableWidth = scrollableWidth;
            revalidate();
        }

        /**
         * Get the block IncrementInfo for the specified orientation
         * 
         * @return the block IncrementInfo for the specified orientation
         */
        public IncrementInfo getScrollableBlockIncrement(int orientation) {
            return orientation == SwingConstants.HORIZONTAL ? horizontalBlock : verticalBlock;
        }

        /**
         * Specify the information needed to do block scrolling.
         * 
         * @param orientation
         *            specify the scrolling orientation. Must be either:
         *            SwingContants.HORIZONTAL or SwingContants.VERTICAL.
         * @paran type specify how the amount parameter in the calculation of
         *        the scrollable amount. Valid values are: IncrementType.PERCENT
         *        - treat the amount as a % of the viewport size
         *        IncrementType.PIXEL - treat the amount as the scrollable
         *        amount
         * @param amount
         *            a value used with the IncrementType to determine the
         *            scrollable amount
         */
        public void setScrollableBlockIncrement(int orientation, IncrementType type, int amount) {
            IncrementInfo info = new IncrementInfo(type, amount);
            setScrollableBlockIncrement(orientation, info);
        }

        /**
         * Specify the information needed to do block scrolling.
         * 
         * @param orientation
         *            specify the scrolling orientation. Must be either:
         *            SwingContants.HORIZONTAL or SwingContants.VERTICAL.
         * @param info
         *            An IncrementInfo object containing information of how to
         *            calculate the scrollable amount.
         */
        public void setScrollableBlockIncrement(int orientation, IncrementInfo info) {
            switch (orientation) {
            case SwingConstants.HORIZONTAL:
                horizontalBlock = info;
                break;
            case SwingConstants.VERTICAL:
                verticalBlock = info;
                break;
            default:
                throw new IllegalArgumentException("Invalid orientation: " + orientation);
            }
        }

        /**
         * Get the unit IncrementInfo for the specified orientation
         * 
         * @return the unit IncrementInfo for the specified orientation
         */
        public IncrementInfo getScrollableUnitIncrement(int orientation) {
            return orientation == SwingConstants.HORIZONTAL ? horizontalUnit : verticalUnit;
        }

        /**
         * Specify the information needed to do unit scrolling.
         * 
         * @param orientation
         *            specify the scrolling orientation. Must be either:
         *            SwingContants.HORIZONTAL or SwingContants.VERTICAL.
         * @paran type specify how the amount parameter in the calculation of
         *        the scrollable amount. Valid values are: IncrementType.PERCENT
         *        - treat the amount as a % of the viewport size
         *        IncrementType.PIXEL - treat the amount as the scrollable
         *        amount
         * @param amount
         *            a value used with the IncrementType to determine the
         *            scrollable amount
         */
        public void setScrollableUnitIncrement(int orientation, IncrementType type, int amount) {
            IncrementInfo info = new IncrementInfo(type, amount);
            setScrollableUnitIncrement(orientation, info);
        }

        /**
         * Specify the information needed to do unit scrolling.
         * 
         * @param orientation
         *            specify the scrolling orientation. Must be either:
         *            SwingContants.HORIZONTAL or SwingContants.VERTICAL.
         * @param info
         *            An IncrementInfo object containing information of how to
         *            calculate the scrollable amount.
         */
        public void setScrollableUnitIncrement(int orientation, IncrementInfo info) {
            switch (orientation) {
            case SwingConstants.HORIZONTAL:
                horizontalUnit = info;
                break;
            case SwingConstants.VERTICAL:
                verticalUnit = info;
                break;
            default:
                throw new IllegalArgumentException("Invalid orientation: " + orientation);
            }
        }

        // Implement Scrollable interface

        public Dimension getPreferredScrollableViewportSize() {
            return getPreferredSize();
        }

        public int getScrollableUnitIncrement(Rectangle visible, int orientation, int direction) {
            switch (orientation) {
            case SwingConstants.HORIZONTAL:
                return getScrollableIncrement(horizontalUnit, visible.width);
            case SwingConstants.VERTICAL:
                return getScrollableIncrement(verticalUnit, visible.height);
            default:
                throw new IllegalArgumentException("Invalid orientation: " + orientation);
            }
        }

        public int getScrollableBlockIncrement(Rectangle visible, int orientation, int direction) {
            switch (orientation) {
            case SwingConstants.HORIZONTAL:
                return getScrollableIncrement(horizontalBlock, visible.width);
            case SwingConstants.VERTICAL:
                return getScrollableIncrement(verticalBlock, visible.height);
            default:
                throw new IllegalArgumentException("Invalid orientation: " + orientation);
            }
        }

        protected int getScrollableIncrement(IncrementInfo info, int distance) {
            if (info.getIncrement() == IncrementType.PIXELS)
                return info.getAmount();
            else
                return distance * info.getAmount() / 100;
        }

        public boolean getScrollableTracksViewportWidth() {
            if (scrollableWidth == ScrollableSizeHint.NONE)
                return false;

            if (scrollableWidth == ScrollableSizeHint.FIT)
                return true;

            // STRETCH sizing, use the greater of the panel or viewport width

            if (getParent() instanceof JViewport) {
                return (((JViewport) getParent()).getWidth() > getPreferredSize().width);
            }

            return false;
        }

        public boolean getScrollableTracksViewportHeight() {
            if (scrollableHeight == ScrollableSizeHint.NONE)
                return false;

            if (scrollableHeight == ScrollableSizeHint.FIT)
                return true;

            // STRETCH sizing, use the greater of the panel or viewport height

            if (getParent() instanceof JViewport) {
                return (((JViewport) getParent()).getHeight() > getPreferredSize().height);
            }

            return false;
        }

        /**
         * Helper class to hold the information required to calculate the scroll
         * amount.
         */
        static class IncrementInfo {
            private IncrementType type;
            private int amount;

            public IncrementInfo(IncrementType type, int amount) {
                this.type = type;
                this.amount = amount;
            }

            public IncrementType getIncrement() {
                return type;
            }

            public int getAmount() {
                return amount;
            }

            public String toString() {
                return "ScrollablePanel[" + type + ", " + amount + "]";
            }
        }
    }
}

Upvotes: 1

Views: 2391

Answers (3)

camickr
camickr

Reputation: 324078

JScrollPane. The inner JScrollPane are only going to scroll HORIZONTAL

Then I think you need to force the components to fit in the viewport. You can do this by implementing the Scrollable interface for your panel. Or, an easier approach is to use the Scrollable Panel which does all the work for you.

import java.awt.*;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class ScrollersTest extends JFrame {

    public ScrollersTest() {
        super("A JScrollPane inside a JScrollPane");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        init();
        setVisible(true);
        pack();
    }

    public void init() {
//        JPanel p = new JPanel(new GridBagLayout());
        ScrollablePanel p = new ScrollablePanel( new BorderLayout() );
        p.setScrollableWidth( ScrollablePanel.ScrollableSizeHint.FIT );

        GridBagConstraints c = new GridBagConstraints();

        // INNER SCROLLPANE: ONLY SCROLL HORIZONTAL: DOSE NOT WORK...
        JLabel innerText = new JLabel("This text is inside a JScrollPane of its own. But no scroller is shown when the frames size is decreased. Wrong viewPort?....................................");
        JScrollPane innerScroller = new JScrollPane(innerText, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        Dimension d = innerScroller.getPreferredSize();
        innerScroller.setPreferredSize( new Dimension(d.width, d.height * 2) );
//        p.add(innerScroller, c);
        p.add(innerScroller, BorderLayout.NORTH);

        // FRAMES SCROLLPANE: ONLY SCROLL VERTICAL
        JScrollPane frameScrollpane = new JScrollPane(p, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        getContentPane().add(frameScrollpane, BorderLayout.CENTER);
    }

    public static void main(String args[]) {
        new ScrollersTest();
    }
}

I used a BorderLayout in this example because it is simpler to use. If you want to use a GridBagLayout then you need to configure the constraints properly so that the component grows/shrinks in a reasonable manner. When you use the defaults the component will just default to its minimum size which is (0, 0) so you just see a small rectangle on the frame.

Upvotes: 2

splungebob
splungebob

Reputation: 5415

Based on your answer to ItachiUchiha's comment in the OP, it simply sounds like you want scrollable text, that's on a scrollable JPanel.

Instead of using a JLabel, how about using a JTextArea (decorated like a label) which is better suited for scrolling text?

With very little modifications to your existing code:

import java.awt.*;
import javax.swing.*;

public class ScrollersTest extends JFrame {
  public static void main(String args[]) {
    new ScrollersTest();
  }

  public ScrollersTest() {
    super("A JScrollPane inside a JScrollPane");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    init();
    pack();
    setVisible(true);
  }

  public void init() {
    JPanel p = new JPanel(new GridBagLayout());
    GridBagConstraints c = new GridBagConstraints();

    String text = "This text is inside a JScrollPane of its own. "
        + "But no scroller is shown when the frames size is decreased. "
        + "Wrong viewPort?....................................";

    // INNER SCROLLPANE:
    JTextArea innerText = new JTextArea(1, 20);
    innerText.setText(text);
    innerText.setEditable(false);
    innerText.setOpaque(false);

    JScrollPane innerScroller = new JScrollPane(innerText,
        JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    p.add(innerScroller);

    // FRAMES SCROLLPANE: ONLY SCROLL VERTICAL
    JScrollPane frameScrollpane = new JScrollPane(p,
        JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    getContentPane().add(frameScrollpane, BorderLayout.CENTER);
  }
}

Upvotes: 2

Benjamin
Benjamin

Reputation: 2286

   import java.awt.BorderLayout;
   import java.awt.GridBagConstraints;
   import java.awt.GridBagLayout;
   import javax.swing.JFrame;
   import javax.swing.JLabel;
   import javax.swing.JPanel;
   import javax.swing.*;
   import java.awt.*;

 class ScrollersTest {
 public static void main(String args[]) {
 JFrame frame = new JFrame("Tabbed Pane Sample");
 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 JTextArea innerText = new JTextArea("This text is inside a JScrollPane of its own."
        +" But no scroller is shown when the frames size is decreased. Wrong viewPort?....................................\n"
        +" But no scroller is shown when the frames size is decreased. Wrong viewPort?....................................\n"
        +" But no scroller is shown when the frames size is decreased. Wrong viewPort?....................................\n");
innerText.setBounds(100,100,500,500);
JScrollPane innerScroller = new JScrollPane(innerText);
innerScroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
innerScroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
JPanel p = new JPanel(new GridBagLayout());
p.add(innerScroller);
JScrollPane jScrollPane = new JScrollPane(p);
jScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
jScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
frame.add(jScrollPane, BorderLayout.CENTER);
frame.setSize(400, 150);
frame.setVisible(true);
  }
}

enter image description here

Upvotes: 1

Related Questions