如何使组件在调整大小时与 GridBagLayout 布局均匀共享 JPanel 容器垂直区域?

How to make components evenly share JPanel container vertical area with GridBagLayout layout on resizing?

问题:

JPanelGridBagLayout 包含两个 JScrollPane 组件,顶部的一个包含 JTextArea,底部的一个包含 JTable。我希望这个设置能让组件 流畅地填充 容器 JPanel 均匀share 调整大小时的垂直区域。实际发生的是,顶部 JScrollPane 组件在调整大小时垂直收缩,从而为底部 JScrollPane 组件提供更多垂直区域以侵入;这种情况发生在某些尺寸上,我不知道它们是否随机。

重要提示:

我已经将 GridBagConstraints#weighty = 0.5; 应用于两个组件,这应该可以完成工作。

我注意到问题是因为 JTextArea 的存在,因为如果我 切换位置 ;在底部制作 JScrollPane/JTextArea,在顶部制作 JScrollPane/JTable;底部组件 JScrollPane/JTextArea 垂直收缩,为顶部组件 JScrollPane/JTable 提供更多垂直区域以侵入。

我知道这个确切的案例可以简单地使用 GridLayout(2,1) 而不是 GridBagLayout 来解决(或者可能是其他解决方案),但是我是 GridBagLayout 的粉丝并且我想使用大部分。此外,如果我想添加三个组件而不是两个组件,并且我想让它们按百分比共享垂直区域,例如 (%25, %25, 50%),我可以使用 GridBagConstraints#weighty = (0.25, 0.25, 0.5),这很容易与 GridBagLayout.

那么,在这种情况下,如何让组件 与 [=12] 均匀地共享 JPanel 容器的垂直区域=] 调整大小?或者,换句话说;如何强制组件遵守 GridBagConstraints#weighty = 0.5; 调整大小设置,这适用于两者?

SSCCE 格式的代码:

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.table.DefaultTableModel;

public class GridBagFluidFill {

    private static JPanel createFormPanel() {

        JPanel containerPanel = new JPanel(new GridBagLayout());
        containerPanel.setPreferredSize(new Dimension(350, 200));
        JScrollPane scrollableTextArea;
        JScrollPane scrollableTable;

        JTextArea textArea = new JTextArea();
        scrollableTextArea = new JScrollPane(textArea);

        DefaultTableModel model = new DefaultTableModel(new String[]{"Column1", "Column2", "Column3"}, 0);
        JTable table = new JTable(model);
        scrollableTable = new JScrollPane(table);

        GridBagConstraints c = new GridBagConstraints();
        c.gridy = 0;
        c.weightx = 1.0;
        // I expect this to reserve 50% of the height all time.
        c.weighty = 0.5;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.PAGE_START;
        containerPanel.add(scrollableTextArea, c);

        c = new GridBagConstraints();
        c.gridy = 1;
        c.weightx = 1.0;
        // I expect this to reserve 50% of the height all time.
        c.weighty = 0.5;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.PAGE_END;
        containerPanel.add(scrollableTable, c);

        return containerPanel;
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(() -> {
            JPanel formPanel = createFormPanel();
            JFrame frame = new JFrame("GridBagFluidFill");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(formPanel);
            frame.pack();
            frame.setVisible(true);
        });
    }
}

GridBagLayout 很复杂,虽然 documentation 确实描述了它的所有功能,但其中一些很容易被误解。

I already applied GridBagConstraints#weighty = 0.5; to both components, which is supposed to do the job.

那不是 weightx 和 weighty 做的。

相等的 weightx 和 weighty 值不会强制组件具有相同的宽度或高度。当容器大于每个组件的首选尺寸时,重量值决定了 额外 space 的分配。

这是一个演示这个的程序:

import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;

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

public class GridBagEqualWeightsDemo {
    static void showWindow() {
        // Source: https://www.gutenberg.org/files/98/98-h/98-h.htm
        String text =
            "It was the best of times, it was the worst of times, it was"
            + " the age of wisdom, it was the age of foolishness, it was the"
            + " epoch of belief, it was the epoch of incredulity, it was the"
            + " season of Light, it was the season of Darkness, it was the"
            + " spring of hope, it was the winter of despair, we had everything"
            + " before us, we had nothing before us, we were all going direct"
            + " to Heaven, we were all going direct the other way—in short,"
            + " the period was so far like the present period, that some of its"
            + " noisiest authorities insisted on its being received, for good"
            + " or for evil, in the superlative degree of comparison only.";

        JTextArea topArea = new JTextArea(text, 4, 20);
        topArea.setLineWrap(true);
        topArea.setWrapStyleWord(true);

        JTextArea bottomArea = new JTextArea(text, 8, 20);
        bottomArea.setLineWrap(true);
        bottomArea.setWrapStyleWord(true);

        JScrollPane top = new JScrollPane(topArea);
        JScrollPane bottom = new JScrollPane(bottomArea);

        top.setMinimumSize(top.getPreferredSize());
        bottom.setMinimumSize(bottom.getPreferredSize());

        JPanel panel = new JPanel(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.BOTH;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.weightx = 1;

        gbc.weighty = 0.5;
        panel.add(top, gbc);
        panel.add(bottom, gbc);

        JFrame frame = new JFrame("GridBagConstraints Equal Weights");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> showWindow());
    }
}

如您所见,这两个组件开始时具有不同的首选高度,因此 GridBagLayout 永远不会为它们提供相同的高度。当你 window 变高时,由于相对差异较小,组件各自的高度看起来更相似,但它们永远不会相同。

GridBagLayout 仅使用 weighty 来确定如何分配额外的 space——即超过每个组件(不同的)首选高度的容器高度。如果面板比首选高度高 50 像素(根据每个子组件的首选大小计算得出),则等于 weighty 值意味着每个组件的布局将在其各自的首选高度上增加 25 像素。

如果您想强制两个可能具有不同首选尺寸的组件始终具有相同的宽度 and/or 高度,GridBagLayout 是不适合该工作的工具。正如您所提到的,GridLayout 非常适合这一点。 SpringLayout也可以做到这一点,虽然它的用法很复杂。

您可以自由(并鼓励!)使用不同布局的多个面板。

I already applied GridBagConstraints#weighty = 0.5; to both components, which is supposed to do the job.

weighty 约束用于告诉 GridBagLayout 如何分配“额外”(显然更少)space。

也就是说,每个组件首先分配其首选space。然后,如果有额外的(或明显更少的)space 可用,则使用该约束。

如果您添加调试代码以显示每个滚动窗格的首选大小,您将获得:

java.awt.Dimension[width=223,height=19] // text area
java.awt.Dimension[width=453,height=403] // table

所以问题在于这个语句:

containerPanel.setPreferredSize(new Dimension(350, 200));

您强制组件小于其首选尺寸。所以看起来 GridBagLayout 正在为每个分配 50%。

I was wondering why JTextArea shrinks without a good reason

随着框架高度的增加,每个组件都会分配 50%,直到框架大到足以以其首选尺寸显示每个组件为止。这就是文本区域缩小和 table 跳跃的原因。

当您从那里增加高度时,额外的 space 会按预期以 50/50 的比例分配。

如果您取消对 setPreferredSize() 语句的注释,您将看到自然的布局,并且跳转的原因变得清晰。

I want to make them share the vertical area by percentage for example (%25, %25, 50%)

如果您想要这样的分配,请查看为此目的设计的 Relative Layout