5

我正在使用 GroupLayout 观察到一些奇怪的行为。我有一个包含在 JScrollPane 内的 JTextArea,它正在调整大小并将其他组件推出 JFrame。奇怪的是,如果我重新排列布局以使 JTextArea 在其上方或下方没有任何内容(也没有间隙),它可以正常工作。就好像文本区域是在询问容器,容器中有多少空间,然后就占去 100% 的空间,而不管其他组件。另一个奇怪的事情是,它似乎只发生在 JTextArea(不是 JScrollPane)大小加上容器内的其他组件高度达到 Short.MAX_VALUE 时。

如果我将滚动窗格的垂直组中的最大大小(将组件添加到布局中时)指定为小于 Short.MAX_VALUE 的值,它似乎可以解决问题(只要值和 Short.MAX_VALUE 之间的差异)。 MAX_VALUE 大于所有其他组件的高度)。例如

.addComponent(textArea, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE - 500)

此外,如果我将首选大小设置为一个小的正值,而不是 GroupLayout.PREFERRED_SIZE 或 GroupLayout.DEFAULT_SIZE,它似乎也会使这种行为消失。例如

.addComponent(textArea, 0, 1, Short.MAX_VALUE)

GroupLayout 上的 Java 教程似乎没有提到任何关于此的内容,并且倾向于在所有地方使用 Short.MAX_VALUE。我尝试用谷歌搜索找到答案,但我发现这个问题很难用搜索词来描述。

我是否发现了一个错误,或者我只是不了解 GroupLayout?后者当然似乎更有可能。

此示例将创建一个简单的文本区域。按下下方按钮以使用文本填充它(并调整 JScrollPane 内的 JTextArea 的大小)。然后,您可以在文本区域内单击并添加或删除行。添加一些额外的行后,单击重绘按钮(或调整框架大小)以查看奇怪的行为。

public class GroupLayoutTest {
    public GroupLayoutTest() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                final JFrame frame = new JFrame("GroupLayout test");
                Container panel = frame.getContentPane();

                GroupLayout layout = new GroupLayout(panel);
                panel.setLayout(layout);

                JButton addBtn = new JButton("Add Lines");
                JButton redrawBtn = new JButton("Redraw");

                final JTextArea textArea = new JTextArea();
                final JScrollPane textPane = new JScrollPane(textArea);

                layout.setHorizontalGroup(layout.createParallelGroup()
                        .addComponent(redrawBtn)
                        .addComponent(textPane)
                        .addComponent(addBtn));

                layout.setVerticalGroup(layout.createSequentialGroup()
                        .addComponent(redrawBtn)
                        .addComponent(textPane)
                        .addComponent(addBtn));

                addBtn.addActionListener(new ActionListener() {
                    int m = 0;

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        for (int i = m; m < i + 2044; ++m) {
                            textArea.append("Line " + m + "\n");
                        }

                        // redraw the frame
                        frame.validate();
                    }
                });

                redrawBtn.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        frame.validate();
                    }
                });

                frame.setPreferredSize(new Dimension(640, 480));
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

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

2 回答 2

2

我对 GroupLayout 不太熟悉,这正是我通过查看文档和思考它所观察到的。希望它会有所帮助。我的回答有点长,所以我会总结一下:

为什么会这样?3件事的结合。

  1. GroupLayout 尊重组件的首选大小。(这就是你选择它的原因,从你的评论来看)。
  2. 您的垂直组是连续的。它一次布置一个组件 - 以他们喜欢的尺寸,或者如果有空间则更大。
  3. 您的 JScrollPane 具有无限的首选大小。无论 JTextArea 的首选大小是什么,它都会增长到......一旦它的首选大小变得大于框架的大小,它下面的组件就没有空间了。

将尊重首选尺寸的布局管理器与具有非常大首选尺寸的组件组合通常总是会导致此问题。我没有进行广泛的测试,但我知道它也发生在 FormLayout 中(如果你告诉它使用首选大小)。

更长的解释/思考过程尝试:

就好像文本区域是在询问容器,容器中有多少空间,然后就占去 100% 的空间,而不管其他组件。

文本区域不是询问容器它可以有多大——它是相反的——它是告诉容器它想要多大。在这种情况下,textArea 直接包含在滚动窗格中。文本区域的首选大小将随着文本区域中文本的大小而增长。滚动窗格的首选大小将随着文本区域的首选大小而增长,除非您在滚动窗格上设置首选大小。从那里,布局管理器将决定如何布局。

在这种情况下,您将 GroupLayout 与 SequentialGroup 一起用于垂直布局。这将根据组件的最小/首选/最大尺寸按顺序排列组件。如果您将 textPane 重新定位为组中的最后一项,它不会从您的其他组件中窃取空间......所以我的猜测是,对于非常大的尺寸,GroupLayout 不在乎是否所有组件都显示 - 如果最后一个组件之后没有剩余空间,直到容器放大后才会显示。对于小尺寸,用户可以调整框架的大小......但对于像您的示例中这样的大尺寸,这是不可行的。

与遗漏分开 - 它看起来只是一个巨大的滚动窗格和 GroupLayout 不知道何时停止的组合......所以滚动窗格栏的底部被切断了。不过,正如@camickr 所建议的那样,只需在 JScrollPane 上设置一个合理的首选大小,就可以避免这一切。

另一个奇怪的事情是,它似乎只发生在 JTextArea(不是 JScrollPane)大小加上容器内的其他组件高度达到 Short.MAX_VALUE 时。

如果您记录每个组件的大小,您会发现除非您为 JScrollPane 指定首选大小,否则它将始终具有大于 textArea 的首选大小,因此我认为上述说法不正确. 滚动窗格的大小加上容器中的其他组件仍将超过 Short.MAX_VALUE,并且似乎确实会导致 GroupLayout 出现问题,这是真的。

在我看来,JScrollPane 的重点是将一个具有较大(或可能较大)首选尺寸的组件容纳在一个包含的较小区域(适当时带有滚动条)中。然后,您始终可以让滚动窗格增长到大于其首选大小的大小......但它首先告诉滚动窗格它应该有多大。事实上,设置一个首选大小可以有效地告诉 JScrollPane 何时需要滚动条。例如,如果滚动窗格的首选大小是 400,300,那么每当包含的组件的首选宽度超过 400(或滚动窗格的大小,如果它位于让它增长到超过其首选大小的容器中) , 显示水平滚动条。同样,对于高度也是如此。否则它总是会增长到你所拥有的大小,并且它不需要滚动条,

GroupLayout 的文档提到它通常由其他布局构建器工具使用,而不是通常由开发人员使用(尽管您仍然可以)。我会考虑使用不同的布局,而不是通常需要您使用特殊最大值才能正常工作的布局。我个人最喜欢的是FormLayout,一个免费的、获得 BSD 许可的第三方布局管理器。我认为您不需要为普通组件指定固定大小,但是对于滚动窗格,您确实希望在尊重首选大小的布局中为其提供首选大小。

我希望我的所有布局都尊重 DPI,在适当的情况下增长,并在国际化中正常工作(文本长度可能非常不同)......所以我不想使用任何需要为所有内容指定固定大小的东西。我认为在大多数布局中,在滚动窗格上设置固定的首选大小并不是一件坏事。在按钮、文本字段或其他明显具有明显首选大小的组件上执行此操作,而不是太多。

这是它在 FormLayout 中的外观(它也有构建器,但在这种情况下,我使用单元格约束轻松跨越列):

FormLayout layout = new FormLayout(
      "pref,fill:pref:grow", // cols
      "pref,3dlu,fill:pref:grow,3dlu,pref" // rows
);

JPanel panel = new JPanel(layout);

CellConstraints cc = new CellConstraints();

panel.add(redrawBtn, cc.xy(1, 1));
panel.add(textPane, cc.xyw(1, 3, 2)); // span 2 columns
panel.add(addBtn, cc.xy(1, 5));

frame.setContentPane(panel);
于 2010-12-08T23:31:24.377 回答
1

我不知道 GroupLayout 的内部结构,但通常您需要指定 textArea/scrollpane 组合的首选大小,以便为布局管理器提供一些可以使用的信息。这是通过使用以下任一方式完成的:

final JTextArea textArea = new JTextArea(10, 30);

或者

textPane.setPreferredSize( new Dimension(400, 200) );

否则我相信首选大小基本上是添加到文本区域的所有文本的大小。

于 2010-12-08T18:50:17.413 回答