JSplitPane 内的 JScrollPane 最小宽度

JScrollPane minimum width inside JSplitPane

我正在尝试 JSplitPane 和几个可并排滚动的 JTables。
但是,我正在试验一种行为,其中 JScrollPane 被缩小得太多,如 gif 所示。
请注意 左侧分量 ,它除了具有 250px 的最小值 width 之外,还在继续缩小。

相关代码为

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

在这种情况下,如何避免 JScrollPanes 缩小太多?

getMinimumSize调用了一个JSplitPanereturns一个size其实是考虑了它左右Component的最小尺寸,再加上分隔符尺寸。因此,解决您的问题的一种方法是让您的 JSplitPane 实现 Scrollable(以使其遵守自身的最小大小)并将其添加到 JScrollPane。通过这种方式,您可以确保遵守最小尺寸,并且当用户继续将 Scrollable JSplitPane 缩小到超过其最小尺寸时,滚动条就会出现。

这是一些工作代码:

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