Search code examples
javaswingjscrollpanejsplitpane

JScrollPane minimum width inside JSplitPane


I'm trying out the JSplitPane with a couple of scrollable side-by-side JTables.
However I'm experiment a behavior where the JScrollPane gets shrinked too much, as per gif.
Notice the left component, that besides having a minimum width of 250px, continues shrinking.

enter image description here

The relevant code is

final var objetsTable = new JTable();
final var objectsScrollPane = new JScrollPane(objetsTable);
objectsScrollPane.setMinimumSize(new Dimension(250, 0));
objectsScrollPane.setPreferredSize(new Dimension(400, 300));

final var stepsTable = new JTable();
final var stepsScrollPane = new JScrollPane(stepsTable);
stepsScrollPane.setMinimumSize(new Dimension(150, 0));
stepsScrollPane.setPreferredSize(new Dimension(200, 300));

final var splitPane = new JSplitPane();
splitPane.setLeftComponent(objectsScrollPane);
splitPane.setRightComponent(stepsScrollPane);
splitPane.setResizeWeight(1.0);

How can I avoid the JScrollPanes getting shrinked too much in this case?


Solution

  • The getMinimumSize called on a JSplitPane returns a size which is actually taking into account the minimum size of its left and right Components, plus the divider size. So one way to maybe solve your problem would be to make your JSplitPane implement Scrollable (in order to make it respect the minimum size of itself) and add it to a JScrollPane. This way you can ensure that the minimum size is respected and when the user continues shrinking the Scrollable JSplitPane past its minimum size, then the scroll bars will show up.

    Here is some working code:

    import java.awt.Color;
    import java.awt.Container;
    import java.awt.Dimension;
    import java.awt.Rectangle;
    import javax.swing.BorderFactory;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JSplitPane;
    import javax.swing.JViewport;
    import javax.swing.Scrollable;
    import javax.swing.SwingConstants;
    import javax.swing.SwingUtilities;
    
    public class Main {
        
        private static class MyScrollableSplitPane extends JSplitPane implements Scrollable {
            
            private int maxUnitIncrement = 10;
            
            public void setMaxUnitIncrement(final int pixels) {
                maxUnitIncrement = pixels;
            }
            
            public int getMaxUnitIncrement() {
                return maxUnitIncrement;
            }
            
            /**
             * This is being constantly checked by the scroll pane instead of the
             * getPreferredScrollableViewportSize...
             */
            @Override
            public Dimension getPreferredSize() {
                final Dimension minSz = getMinimumSize(),
                                curSz = getSize();
                curSz.width = Math.max(curSz.width, minSz.width);
                curSz.height = Math.max(curSz.height, minSz.height);
                return curSz;
            }
            
            /**
             * This is only checked once (at the beginning).
             */
            @Override
            public Dimension getPreferredScrollableViewportSize() {
                return super.getPreferredSize();
            }
    
            /**
             * Source: https://docs.oracle.com/javase/tutorial/uiswing/components/scrollpane.html .
             */
            @Override
            public int getScrollableUnitIncrement(Rectangle visibleRect,
                                                  int orientation,
                                                  int direction) {
                //Get the current position.
                int currentPosition;
                if (orientation == SwingConstants.HORIZONTAL) {
                    currentPosition = visibleRect.x;
                } else {
                    currentPosition = visibleRect.y;
                }
    
                //Return the number of pixels between currentPosition
                //and the nearest tick mark in the indicated direction.
                if (direction < 0) {
                    int newPosition = currentPosition -
                                     (currentPosition / maxUnitIncrement)
                                      * maxUnitIncrement;
                    return (newPosition == 0) ? maxUnitIncrement : newPosition;
                } else {
                    return ((currentPosition / maxUnitIncrement) + 1)
                           * maxUnitIncrement
                           - currentPosition;
                }
            }
    
            /**
             * Source: https://docs.oracle.com/javase/tutorial/uiswing/components/scrollpane.html .
             */
            @Override
            public int getScrollableBlockIncrement(Rectangle visibleRect,
                                                   int orientation,
                                                   int direction) {
                if (orientation == SwingConstants.HORIZONTAL) {
                    return visibleRect.width - maxUnitIncrement;
                } else {
                    return visibleRect.height - maxUnitIncrement;
                }
            }
    
            @Override
            public boolean getScrollableTracksViewportWidth() {
                final Container parent = getParent();
                return (parent instanceof JViewport) && (getMinimumSize().width < ((JViewport) parent).getWidth());
            }
    
            @Override
            public boolean getScrollableTracksViewportHeight() {
                final Container parent = getParent();
                return (parent instanceof JViewport) && (getMinimumSize().height < ((JViewport) parent).getHeight());
            }
        }
        
        private static void createAndShowGUI() {
            
            /*Since I don't add any Components to the 'left' and 'right' panels, I am going to set the
            preferred size of them. This is only for demonstrating the concept. Setting the minimum size
            though is somewhat required by the JSplitPane itself.*/
            
            final JPanel left = new JPanel();
            left.setMinimumSize(new Dimension(150, 100));
            left.setPreferredSize(new Dimension(200, 200));
            
            final JPanel right = new JPanel();
            right.setMinimumSize(new Dimension(300, 100));
            right.setPreferredSize(new Dimension(400, 200));
            
            final JSplitPane split = new MyScrollableSplitPane();
            split.setBorder(BorderFactory.createLineBorder(Color.CYAN.darker(), 3));
            split.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
            split.setLeftComponent(left);
            split.setRightComponent(right);
            
            final JFrame frame = new JFrame("MyScrollableSplitPane demo");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(new JScrollPane(split));
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
        
        public static void main(final String[] args) {
            SwingUtilities.invokeLater(Main::createAndShowGUI);
        }
    }