CardLayout计算的最小尺寸不正确

编程语言 2026-07-12

CardLayout 无论显示的是哪个组件,都会计算最小宽度。请查看 java.awt.CardLayout#minimumLayoutSize

复现步骤:

  1. 将窗口最大化。
  2. 将左侧组件切换为粉色组件(它更窄)。
  3. 还原窗口。

砰——分隔条被放置在一个怪异的位置,好像显示的是更宽的组件。

这个GUI模拟了我们应用中的实际布局。它包含一个拆分窗格,左侧的组件会在 CardLayout 的帮助下动态切换(在本场景中似乎最合适的布局)。容器有两个组件,它们的大小差异很大。分隔条的位置应该能够反映这种差异(同时仍然可用)。

如何规避这个问题?

最小可复现示例(MRE):

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.util.Arrays;

public class SplitPaneDemo {

    static JSplitPane splitPane;
    static JPanel leftPanel;
    static CardLayout leftLayout;

    public static void main(String[] args) {
        Container mainPanel = createMainPanel();
        JFrame frame = new JFrame("Split Pane Demo");
        frame.setContentPane(mainPanel);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    private static Container createMainPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(createButtonPane(), BorderLayout.NORTH);
        panel.add(createSplitPane());
        return panel;
    }

    private static Component createButtonPane() {
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));
        panel.add(createButton());
        return panel;
    }

    private static Component createButton() {
        JButton button = new JButton("Swap left component");
        button.addActionListener(SplitPaneDemo::swapLeftComponent);
        return button;
    }

    private static void swapLeftComponent(ActionEvent e) {
        leftLayout.next(leftPanel);
        splitPane.setDividerLocation(getShowingPanel().getPreferredSize().width);
    }

    private static Component getShowingPanel() {
        return Arrays.stream(leftPanel.getComponents())
                .filter(Component::isShowing)
                .findFirst()
                .orElseThrow(null);
    }

    private static Container createSplitPane() {
        splitPane = new JSplitPane();
        splitPane.setLeftComponent(createLeftPanel());
        splitPane.setRightComponent(createGreenPanel());
        return splitPane;
    }

    private static Component createLeftPanel() {
        leftPanel = new JPanel(leftLayout = new CardLayout());
        leftPanel.setName("leftPanel");
        leftPanel.add(createRedPanel(), "red");
        leftPanel.add(createPinkPanel(), "pink");
        return leftPanel;
    }

    private static Component createRedPanel() { // wider
        Component panel = new JPanel();
        Dimension size = new Dimension(500, 500);
        panel.setPreferredSize(size);
        panel.setMinimumSize(size); // crucial
        panel.setBackground(Color.RED);
        return panel;
    }

    private static Component createPinkPanel() { // narrower
        Component panel = new JPanel();
        panel.setPreferredSize(new Dimension(200, 500));
        panel.setBackground(Color.PINK);
        return panel;
    }

    private static Component createGreenPanel() {
        Component panel = new JPanel();
        panel.setPreferredSize(new Dimension(500, 500));
        panel.setBackground(Color.GREEN);
        return panel;
    }
}

全屏

退出全屏模式,分隔条位置异常

覆盖方法可行,但感觉不对劲。

此外,在实际应用中并不容易实现,因为我们使用Swing UI Designer GUI表单(本不应该这样做,但我们确实在用)。

        leftPanel = new JPanel(leftLayout = new CardLayout() {
            @Override // everything's the same...
            public Dimension minimumLayoutSize(Container parent) {
                synchronized (parent.getTreeLock()) {
                    Insets insets = parent.getInsets();
                    int ncomponents = parent.getComponentCount();
                    int w = 0;
                    int h = 0;

                    for (int i = 0 ; i < ncomponents ; i++) {
                        Component comp = parent.getComponent(i);
                        if (!comp.isVisible()) continue; // ...except for this line
                        Dimension d = comp.getMinimumSize();
                        if (d.width > w) {
                            w = d.width;
                        }
                        if (d.height > h) {
                            h = d.height;
                        }
                    }
                    return new Dimension(insets.left + insets.right + w + getHgap()*2,
                            insets.top + insets.bottom + h + getVgap()*2);
                }
            }
        });

Java 8。

解决方案

这不是我所期望的行为,但在调整大小时,似乎会丢失对 getPreferredSize() 的调用,可以通过向 frame 添加监听器来解决,具体如下:

frame.addComponentListener( new ComponentAdapter() {
   @Override
   public void componentResized( ComponentEvent e ) {
      splitPane.setDividerLocation( getShowingPanel().getPreferredSize().width );
   }
});
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章