直到现在,我们处理的都是最简单的 GUI。我们已经向您展示了怎样不使用布局管理器构建 GUI,每个控件通过 setBounds(Rectangle) 方法接收确定的位置和大小。我们还向您展示了如何使用最简单的布局管理器 FlowLayout ,它使用 getPreferredSize() 方法和单独的控件方法来设定每个控件的大小和位置。使用 FlowLayout ,您只控制组件布局的顺序。对于更多的控件,您将必须使用被称为 约束布局管理器。
约束布局管理器有一个与其关联的约束对象。无论组件在何时被添加到容器中,都是通过一个约束对象来完成,这个对象可以被看成是一个布局提示。 GridBagLayout 是约束布局管理器的一个示例,它有约束对象 GridBagConstraints 。 GridBagLayout 让您将窗口分成一系列逻辑的行和列。
清单 5 展示了我们会怎样将 GridBagLayout 添加到 FlowLayout 示例中使用的五个控件的每一个中。 GridBagConstraints 对象是向 frame 添加每个组件的 add(Component,Object) 方法的第二个参数。
清单 5. 计算首选大小的控件方法
2 frame.setLayout(new GridBagLayout());
3 GridBagConstraints constraints = new GridBagConstraints();
4 Button button1 = new Button("Next");
5 frame.add(button1 , constraints);
6 TextField text1 = new TextField();
7 text1.setColumns(10);
8 constraints.gridx = 1;
9 frame.add(text1 , constraints);
10 Label label1 = new Label("First Name:");
11 constraints.gridx = 2;
12 frame.add(label1 , constraints );
13 TextArea textArea1 = new TextArea("This is some text in a text area");
14 textArea1.setRows(2);
15 textArea1.setColumns(10);
16 constarints.gridx = 0;
17 constraints.gridy = 1;
18 frame.add(textArea1 , constraints );
19 List list1 = new List();
20 list1.add("FirstItem");
21 list1.add("SecondItem");
22 list1.add("ThirdItem");
23 list1.add("FourthItem");
24 list1.add("FifthItem");
25 constraints.gridx = 1;
26 frame.add(list1 , constraints );
27 frame.pack();
28 frame.setVisible(true);
29 List list1 = new List();
30 list1.add("FirstItem");
31 list1.add("SecondItem");
32 list1.add("ThirdItem");
33 list1.add("FourthItem");
34 list1.add("FifthItem");
35 constraints.gridx = 1;
36 frame.add(list1 , constraints );
37 frame.pack();
38 frame.setVisible(true);
39
图 8 展示了使用 GridBagLayout 怎样导致 GUI 窗口上控件分离成列。
图 8. 控件使用 GridBagLayout 的结果

控件布置和列的大小设置
Next 按钮被放在左上格,gridx 为 0,gridy 为 0。文本域的 gridx 约束被设置为 1,所以文本域被放在按钮的下一列。gridx 为 2 的标签被放在再下一个(或第三个)列。
接下来我们看到文本域与被放在 0 号列,它还包括按钮。 GridBagLayout 使用每一列中最大的控件的首选大小来设置该列的大小。这保证了控件在处于对于它不够宽的列或不够高的行中时不会被截断。
对于按钮和文本域,两者都在同一列中,文本域是两者中较大的。列被调整大小以适应文本域,这意味着按钮周围会有剩余的空间。在上一个示例中,这是通过在列的中心放置较小的控件来解决的。
我们更希望得到的效果可能是在列的左侧放置按钮。同样,文本框应该锚定在列的左侧或右侧。
在列的一侧锚定控件保证了控件总是在列增大时增大,从而让用户有可用空间。您可以通过设置 GridBagConstraints 对象的锚定字段来锚定控件。其值为您想将控件固定到的列边缘的方向点。例如,要将按钮固定在左侧,您将使用:
2
这条命令会将控件的左侧锚定到列的左侧。如果和被锚定的控件在同一行中的所有控件的首选高度都比这个控件大,那么按照缺省它也会被垂直放置在中心。如果您想将控件固定到一个特定的角落,可以使用方向点的组合,如下面的示例所示:
2
要让控件锚定在左边同时填满所有可用的空间,可以使用填充属性,如下所示:
2
如果您想既填满水平的也填满垂直的,那么应该使用 Both 值:
2
我们使用同一个 GUI 示例想做的另一件事是使用列表框标签下面的列。这将让列表能够跨越两列。创建这种效果的参数是 GridBagConstraints 的 Gridwidth 字段,如下所示:
2
使用 gridheight 字段,您既可以占用不止一列,也可以指定使用不止一行。在代码中包括所有这些字段的结果如图 9 所示。
图 9. 向控件添加 GridBagConstraints 的结果

下面是包括所有新字段的代码。
清单 6. 添加了 GridBagConstraints 的简单的 GUI
2 frame.setLayout(new GridBagLayout());
3 GridBagConstraints constraints = new GridBagConstraints();
4 Button button1 = new Button("Next");
5 constraints.anchor = GridBagConstraints.WEST;
6 frame.add(button1 , constraints);
7 TextField text1 = new TextField();
8 text1.setColumns(10);
9 constraints.gridx = 1;
10 constraints.fill = GridBagConstraints.HORIZONTAL;
11 frame.add(text1 , constraints);
12 Label label1 = new Label("First Name:");
13 constraints.gridx = 2;
14 frame.add(label1 , constraints );
15 TextArea textArea1 = new TextArea("This is some text in a text area");
16 textArea1.setRows(2);
17 textArea1.setColumns(10);
18 constraints.gridx = 0;
19 constraints.gridy = 1;
20 frame.add(textArea1 , constraints );
21 List list1 = new List();
22 list1.add("FirstItem");
23 list1.add("SecondItem");
24 list1.add("ThirdItem");
25 list1.add("FourthItem");
26 list1.add("FifthItem");
27 constraints.gridx = 1;
28 constraints.gridwidth = 2;
29 frame.add(list1 , constraints );
30 frame.pack();
31 frame.setVisible(true);
32
在窗口中使用剩余空间
迄今为止,我们讨论了使用控件和列的方法;我们想要讨论的另一个元素是窗口中的整体空间,还有在 GUI 的设计和开发中是怎样使用这个空间的。为了说明 GUI 设计中对空间的考虑是多么重要,图 10 展示了简单的 GUI 按缺省定位(即在中心),而且不考虑窗口的流动性时看起来怎样。
图 10. 空间利用很差的 GUI

相对于只是浪费空白上剩余的空间,我们可以利用它来让控件变得更大。按照缺省,控件不会随着窗口大小的增大而增大,因为这对如按钮和标签的控件没有什么意义。然而,文本框和列表框就会得益于这种增大。
为了在窗口大小的扩展事件中指定增量,我们使用了 GridBagConstraints 的 weightx 和 weighty 字段。这些字段从 0 到 1.0 取值,缺省值为 0。当有剩余水平空间时, GridBagLayout 类查询每个控件的 weightx 字段,并按照 weightx 的比例在它们之间划分剩余空间。
使用简单的 GUI 示例,我们可以将 TextField 的 weightx 设为 0.5,列表框的 weightx 设为 1。这样做的结果是剩余水平空间按照 1 比 2 的比例划分给文本域和列表框。另外,将列表框的 weighty 设为 1.0 可以让其增大使用所有剩余垂直空间,如图 11 所示。
图 11. 使用 weightx 和 weighty 字段的结果

当使用 weightx 和 weighty 时,剩余空间不是分配给指定的控件,而是分配给列。控件是否使用剩余空间依赖于控件怎样锚定和填充。要让 图 9 中的列表框使用分配给列的剩余空间,它必须指定为 fill = BOTH ,从而让其水平和垂直地填满来占用剩余的空间。
清单 7 中是我们简单的 GUI 的最后的代码。这个简单的 GUI 是为了可移植性和可扩展性设计的。它在基本的参数(如字符串长度和窗口大小)改变时,利用一个高级的布局管理器来动态地定位和设定控件的大小。
清单 7. 最后的添加了 weightx 和 weighty 参数的简单的 GUI。
2 frame.setLayout(new GridBagLayout());
3 GridBagConstraints constraints = new GridBagConstraints();
4 Button button1 = new Button("Next");
5 constraints.anchor = GridBagConstraints.WEST;
6 frame.add(button1 , constraints);
7 TextField text1 = new TextField();
8 text1.setColumns(10);
9 constraints.gridx = 1;
10 constraints.fill = GridBagConstraints.HORIZONTAL;
11 frame.add(text1 , constraints);
12 Label label1 = new Label("First Name:");
13 constraints.gridx = 2;
14 frame.add(label1 , constraints );
15 TextArea textArea1 = new TextArea("This is some text in a text area");
16 textArea1.setRows(2);
17 textArea1.setColumns(10);
18 constraints.gridx = 0;
19 constraints.gridy = 1;
20 constraints.weightx = 0.5;
21 frame.add(textArea1 , constraints );
22 List list1 = new List();
23 list1.add("FirstItem");
24 list1.add("SecondItem");
25 list1.add("ThirdItem");
26 list1.add("FourthItem");
27 list1.add("FifthItem");
28 constraints.gridx = 1;
29 constraints.gridwidth = 2;
30 constraints.weightx = 1.0;
31 constraints.weighty = 1.0;
32 constraints.fill = GridBagConstraints.BOTH;
33 frame.add(list1 , constraints );
34 frame.pack();
35 frame.setVisible(true);
36
结论
在本文中,我们精确地指出了可能导致差的 GUI 设计和令人失望的开发过程的常见的错误和假设。我们还向您展示了 Java 布局管理器可以怎样纠正这些错误和假设,只要通过为 GUI 设计和开发提供了一种公用的而灵活的框架。使用布局管理器作为 GUI 设计过程的基础让设计师不仅为开发小组展现了原型 GUI 的屏幕截图,还可以更精确地为每个控件指定布局管理器的设置。
这种精确让开发小组在某些时刻(例如字体改变时、窗口大小改变时或应用程序运行在不同的操作系统上时)可以不必第二次猜测开发者的意图以了解 GUI 的外观应该是什么样的。相反,设计师和开发者可以合作来分析和理解 GUI 在运行时的不同状态下会怎样表现。最终,使用布局管理器可以使 GUI 更紧密地符合设计师设定的原始规范,同时更紧密地符合开发小组设定的运行时的需求。