【IT168 技术文章】除了最基本的 GUI 应用程序之外,几乎所有的 GUI 应用程序都需要菜单。菜单增加了任何 GUI 的可用性。菜单是动态呈现的选择列表,它对应于可用的函数(常称为命令)或 GUI 状态。正如您所期望的,您可以使用菜单小部件创建菜单。菜单可以包含其他菜单或者menuItems(菜单项),而 menuItems 也可以包含菜单(即分层的菜单)。menuItems 表示您可以执行的命令或您所选择的 GUI 状态。菜单可以与应用程序(即 shell)的菜单栏相关,或者,这些菜单可以是漂浮在应用程序窗口之上的弹出式菜单。
必须将菜单定义为以下三种互斥样式之一:
BAR 充当 shell 的菜单栏。
DROP_DOWN 从菜单栏或一个菜单项往下拉。
POP_UP 从 shell 弹出,但上下文则针对于一个特定的控件。
菜单支持一些附加的可选样式:
NO_RADIO_GROUP 不充当单选按钮组;当菜单中包含 RADIO 样式的菜单项时可以使用它。
LEFT_TO_RIGHT 或 RIGHT_TO_LEFT 负责选择文本方向。
必须将菜单项定义为以下 5 种互斥样式之一:
CHECK 可以是持久选定的(即复选的)。
CASCADE 包含一个应该以下拉方式出现的菜单。
PUSH 行为类似于造成某一直接动作的按钮。
RADIO 行为类似于一个 CHECK,但是只有一个这种类型的项被选中。
SEPARATOR 充当菜单项的组之间的隔离物(通常是一个条),这一项没有任何功能。
创建一个菜单系统是相当复杂的。清单 1 显示了一个代码示例,该示例创建了一个可操作的菜单系统。
清单 1. 创建一个菜单系统和一个弹出菜单
2 import org.eclipse.swt.widgets.*;
3 import org.eclipse.swt.events.*;
4 import org.eclipse.swt.graphics.*;
5 :
6 Shell shell = ...;
7 :
8 Label body = ...;
9 :
10 // Create the menu bar system
11 Menu main = createMenu(shell, SWT.BAR | SWT.LEFT_TO_RIGHT);
12 shell.setMenuBar(main);
13 MenuItem fileMenuItem = createMenuItem(main, SWT.CASCADE, "&File",
14 null, -1, true, null);
15 Menu fileMenu = createMenu(shell, SWT.DROP_DOWN, fileMenuItem, true);
16 MenuItem exitMenuItem = createMenuItem(fileMenu, SWT.PUSH, "E&xit\tCtrl+X",
17 null, SWT.CTRL + 'X', true, "doExit");
18 MenuItem helpMenuItem = createMenuItem(main, SWT.CASCADE, "&Help",
19 null, -1, true, null);
20 Menu helpMenu = createMenu(shell, SWT.DROP_DOWN, helpMenuItem, true);
21 MenuItem aboutMenuItem = createMenuItem(helpMenu, SWT.PUSH, "&About\tCtrl+A",
22 null, SWT.CTRL + 'A', true, "doAbout");
23 // add popup menu
24 Menu popup = createPopupMenu(shell, body);
25 MenuItem popupMenuItem1 = createMenuItem(popup, SWT.PUSH, "&About",
26 null, -1, true, "doAbout");
27 MenuItem popupMenuItem2 = createMenuItem(popup, SWT.PUSH, "&Noop",
28 null, -1, true, "doNothing");
29
此代码序列创建了以下菜单栏,该菜单栏中包含一些子菜单和一个弹出菜单(参见 图 1、图 2、图 3 和 图 4)。body 值是一个标签控件,包含文本“Sample body”。弹出菜单与这个控件在上下文上存在关联。
图 1. 带有 File 和 Help 菜单的菜单栏

图 2. 下拉状态的 File 菜单

图 3. 下拉状态的 Help 菜单

图 4. 弹出菜单

正如您所见,菜单项可以具有加速器(Ctrl+?)和记忆术(给通过 & 标识的字符加下划线),帮助用户使用键盘选择一些项。
我使用一组 helper 方法创建了这些菜单,如清单 2 中所示。非常好的实践是创建与这些 helper 方法类似的方法,用这些方法创建重复的 GUI 部分,如菜单。随着时间的推移,您可以向这些 helper 方法添加更多的支持功能,并将它们应用到所有使用点。这些方法还有助于提示您获得所有需要的值。
清单 2. 菜单创建 helper 例程
2 Menu m = new Menu(parent);
3 m.setEnabled(enabled);
4 return m;
5 }
6 protected Menu createMenu(MenuItem parent, boolean enabled) {
7 Menu m = new Menu(parent);
8 m.setEnabled(enabled);
9 return m;
10 }
11 protected Menu createMenu(Shell parent, int style) {
12 Menu m = new Menu(parent, style);
13 return m;
14 }
15 protected Menu createMenu(Shell parent, int style,
16 MenuItem container, boolean enabled) {
17 Menu m = createMenu(parent, style);
18 m.setEnabled(enabled);
19 container.setMenu(m);
20 return m;
21 }
22 protected Menu createPopupMenu(Shell shell) {
23 Menu m = new Menu(shell, SWT.POP_UP);
24 shell.setMenu(m);
25 return m;
26 }
27 protected Menu createPopupMenu(Shell shell, Control owner) {
28 Menu m = createPopupMenu(shell);
29 owner.setMenu(m);
30 return m;
31 }
32 protected MenuItem createMenuItem(Menu parent, int style, String text,
33 Image icon, int accel, boolean enabled,
34 String callback) {
35 MenuItem mi = new MenuItem(parent, style);
36 if (text != null) {
37 mi.setText(text);
38 }
39 if (icon != null) {
40 mi.setImage(icon);
41 }
42 if (accel != -1) {
43 mi.setAccelerator(accel);
44 }
45 mi.setEnabled(enabled);
46 if (callback != null) {
47 registerCallback(mi, this, callback);
48 }
49 return mi;
50 }
51
清单 3 显示了如何使用 Java 的反射 功能,利用处理菜单项的代码来链接菜单项。此功能创建了一个易于使用的方法,在这个方法中,只需要给应用程序类添加一个 public 方法(比如 doExit、doAbout 或 doNothing),就可以处理菜单命令。
清单 3. 处理菜单命令的 Callback 例程
2 final Object handler,
3 final String handlerName) {
4 mi.addSelectionListener(new SelectionAdapter() {
5 public void widgetSelected(SelectionEvent e) {
6 try {
7 Method m = handler.getClass().getMethod(handlerName, null);
8 m.invoke(handler, null);
9 }
10 catch (Exception ex) {
11 ex.printStackTrace();
12 }
13 }
14 });
15 }
16
请注意,菜单项(以及稍后讨论的列表、表、和树控件中的项)只支持字符串值;在添加其他类型的值之前,这些值将被转换成字符串值。
通常,您希望 GUI 的用户从预先确定的值列表中进行选择。列表 控件是做到这一点的最简单的方法。列表显示了一组预先定义的、用户可以从中进行选择的字符串值。列表通常需要大量的屏幕实际信息(real estate)。如果您想节省空间,那么可以使用组合框 控件,组合框允许在需要的时候让列表处于下拉状态。组合框还可以有选择地允许用户在类似文本的字段中输入所需要的值。
必须将组合框定义为以下两种互斥样式之一:
SIMPLE 显示值的列表。
DROP_DOWN 使值的列表处于下拉状态。
组合框支持一种可选样式:
READ_ONLY 防止用户编辑此组合框的文本字段。
我所讨论的所有控件(列表、组合框、表和树)都支持以下两种互斥样式之一:
SINGLE 用户只能选择一个项。
MULTI 用户可以选择多个项。
这些控件还支持其他样式:
H_SCROLL 在需要时显示了一个水平滚动的条。
V_SCROLL 在需要时显示了一个垂直滚动的条。
创建组合框和列表相当容易。创建这些控件和添加所需要的字符串值,如清单 4 所示。
清单 4. 使用 FormLayout 创建一个组合框和一个列
2 import org.eclipse.swt.widgets.*;
3 import org.eclipse.swt.events.*;
4 import org.eclipse.swt.layout.*;
5 :
6 setLayout(new FormLayout());
7 String[] data = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5",
8 "Item 6", "Item 7", "Item 8", "Item 9", "Item 10" };
9 Combo c = createCombo(this, data);
10 configureLayout(c, new FormAttachment(0, 5), new FormAttachment(0, 5),
11 new FormAttachment(100, -5), null);
12 List l = createList(this, data);
13 configureLayout(l, new FormAttachment(0, 5), new FormAttachment(c, 5),
14 new FormAttachment(100, -5), new FormAttachment(100, -5));
15 // Create a Combo
16 protected Combo createCombo(Composite parent, String[] data) {
17 Combo combo = new Combo(parent,
18 SWT.DROP_DOWN | SWT.MULTI |
19 SWT.V_SCROLL | SWT.H_SCROLL);
20 combo.addSelectionListener(new SelectionListener() {
21 :
22 });
23 setComboContents(data);
24 return combo;
25 }
26 // Create a List
27 protected List createList(Composite parent, String[] data) {
28 List list = new List(parent, SWT.MULTI |
29 SWT.V_SCROLL | SWT.H_SCROLL);
30 list.addSelectionListener(new SelectionListener() {
31 :
32 });
33 setListContents(data);
34 return list;
35 }
36 public void setComboContents(String[] data) {
37 combo.removeAll();
38 for (int i = 0; i < data.length; i++) {
39 combo.add(data[i]);
40 }
41 }
42 public void setListContents(String[] data) {
43 list.removeAll();
44 for (int i = 0; i < data.length; i++) {
45 list.add(data[i]);
46 }
47 }
48
如果添加 SelectionListener,那么它允许应用程序在用户更改所选定的项时采取行动。
清单 4 中的主代码序列的流假定 SelectionListener 包含在 this 引用的一些合成物中。它创建了如图 5 中所示的组合框和(部分已隐藏的)列表。
图 5. 组合框和列表的例子

您可以使用组合框控件的一个叫做 CCombo 的替代实现(位于 org.eclipse.swt.custom 包中)。除了支持一些额外的功能,CCombo 类似于 Combo,最重要的是,您可以以编程方式要求 CCombo 将文本剪切、复制或粘贴到它的嵌入式 Text 控件中,反之亦可。此外,CCombo 总是以 DROP_DOWN 样式出现,所以它不支持类型样式。
CCombos 还支持一些可选样式:
BORDER 显示了一个围绕文本区的边框。
READ_ONLY 防止用户编辑该组合框的文本字段。
FormLayout
清单 4 中的例子使用 FormLayout 来放置组合框和列表。FormLayout 是最有用的布局管理器之一,因为它允许您相对于其他控件来安排每个控件,允许您将控件的任意一边(左边、顶部、右边或底部)附着到另一个控件的(通常相对的)边,或者附着到容器的某一边上。未附着的边则采用该控件的自然相对维数(natural corresponding dimension)。可以使用 FormAttachment 的一个实例,将引用控件或容器大小的百分比指定为附着点,并提供距离此点的像素偏移量。清单 4 中的代码使用了来自清单 5 的 helper 方法。
清单 5. configureLayout: FormLayout 帮助器方法
2 FormAttachment left,
3 FormAttachment top,
4 FormAttachment right,
5 FormAttachment bottom) {
6 FormData fd = new FormData();
7 if (left != null) {
8 fd.left = left;
9 }
10 if (top != null) {
11 fd.top = top;
12 }
13 if (right != null) {
14 fd.right = right;
15 }
16 if (bottom != null) {
17 fd.bottom = bottom;
18 }
19 c.setLayoutData(fd);
20 }
21
表是支持TableColumns 的列表的增强形式。这些列将它们的数据对齐成一种更可读的形式。它们还支持列名,并能调整列的大小。要创建表,首先要创建表控件,然后添加 TableItems 中包装的字符串数据。
表支持以下可选样式:
CHECK 将复选框添加到第一列中。
VIRTUAL 支持大型表(特定于平台)。
FULL_SELECTION 选择所有列(不仅仅是第一列)。
清单 6 创建了图 6 中所示的表。
清单 6. 使用 helper 方法创建一个表
2 protected Table createTable(Composite parent, int mode, Object[] contents) {
3 table = new Table(parent, mode | SWT.SINGLE | SWT.FULL_SELECTION |
4 SWT.V_SCROLL | SWT.H_SCROLL);
5 table.setHeaderVisible(true);
6 table.setLinesVisible(true);
7 createTableColumn(table, SWT.LEFT, "Column 1", 100);
8 createTableColumn(table, SWT.CENTER, "Column 2", 100);
9 createTableColumn(table, SWT.RIGHT, "Column 3", 100);
10 addTableContents(contents);
11 return table;
12 }
13 protected TableColumn createTableColumn(Table table, int style, String title, int width) {
14 TableColumn tc = new TableColumn(table, style);
15 tc.setText(title);
16 tc.setResizable(true);
17 tc.setWidth(width);
18 return tc;
19 }
20 protected void addTableContents(Object[] items) {
21 for (int i = 0; i < items.length; i++) {
22 String[] item = (String[])items[i];
23 TableItem ti = new TableItem(table, SWT.NONE);
24 ti.setText(item);
25 }
26 }
27 :
28 // sample creation code
29 protected void initGui() {
30 Object[] items = {
31 new String[] {"A", "a", "0"}, new String[] {"B", "b", "1"},
32 new String[] {"C", "c", "2"}, new String[] {"D", "d", "3"},
33 new String[] {"E", "e", "4"}, new String[] {"F", "f", "5"},
34 new String[] {"G", "g", "6"}, new String[] {"H", "h", "7"},
35 new String[] {"I", "i", "8"}, new String[] {"J", "j", "9"}
36 };
37 table = createTable(this, SWT.CHECK, items);
38 }
39
图 6. 表的例子

第一列中的复选框是可选的。注意列的对齐方式。
树是可以显示分层信息的列表。树支持应用程序的扩展和折叠层次结构的中间级别的能力。
因为树常常显示分层结构,所以应该给它们提供一个数据模型供它们使用(在谈论 JFace 时,我将再次提到这个模型概念)。为此,在我们的例子中使用了内部类 Node,如清单 7 所示。
清单 7. 树模型的类节点
2 protected java.util.List children;
3 public java.util.List getChildren() {
4 return children;
5 }
6 public void setChildren(java.util.List children) {
7 this.children = children;
8 }
9 public void addChild(Node node) {
10 children.add(node);
11 }
12 protected String name;
13 public String getName() {
14 return name;
15 }
16 public void setName(String name) {
17 this.name = name;
18 }
19 public Node(String name) {
20 this(name, new ArrayList());
21 }
22 public Node(String name, java.util.List children) {
23 setName(name);
24 setChildren(children);
25 }
26 }
27
要创建树,首先要创建树控件,然后添加 TreeItems 中包装的字符串数据。TreeItems 可以包含其他 TreeItems,这样就可以创建值的层次结构。清单 8 创建了图 7 中所示的树。
清单 8. 使用 helper 方法创建树
2 protected Tree createTree(Composite parent, int mode, Node root) {
3 tree = new Tree(parent, mode | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
4 tree.addSelectionListener(new SelectionListener() {
5 :
6 });
7 setTreeContents(root);
8 return tree;
9 }
10 protected void setTreeContents(Node root) {
11 tree.removeAll();
12 TreeItem ti = new TreeItem(tree, SWT.NONE);
13 setTreeItemContents(ti, root);
14 }
15 protected void setTreeItemContents(TreeItem ti, Node root) {
16 ti.setText(root.getName());
17 java.util.List children = root.getChildren();
18 if (children != null && children.size() > 0) {
19 for (Iterator i = children.iterator(); i.hasNext();) {
20 Node n = (Node)i.next();
21 TreeItem tix = new TreeItem(ti, SWT.NONE);
22 setTreeItemContents(tix, n);
23 }
24 }
25 }
26 :
27 // sample creation code
28 protected void addChildren(Node n, int count, int depth, String prefix) {
29 if (depth > 0) {
30 for (int i = 0; i < count; i++) {
31 String name = prefix + '.' + i;
32 Node child = new Node(name);
33 n.addChild(child);
34 addChildren(child, count, depth - 1, name);
35 }
36 }
37 }
38 Node root = new Node("<root>");
39 addChildren(root, 3, 3, "Child");
40 tree = createTree(this, SWT.CHECK, root);
41
图 7. 树的例子

复选框是可选的。
除了菜单的例子之外,本文中的所有例子都使用了一个叫做 BasicApplication 的基类,以简化它们的实现。作为另一个非常好的实践的例子,我将 SWT GUI 应用程序的一些常见功能应用到这个基类中(包括来自菜单示例的 helper 方法),以使它们更易于使用。
BasicApplication 是一个合成物,它创建了自己的 shell。该类提供了一些额外的功能,比如退出确认对话框(参见图 8),以及将小部件树作为诊断帮助工具(diagnostic aid)转储出来的能力(参见清单 9 中一个经过删减的例子)。
图 8. 确认消息对话框

清单 9. 控件层次结构的打印输出(部分)
2 Tree1App {}
3 Tree {}
4 TreeItem {<root>}
5 TreeItem {Child.0}
6 TreeItem {Child.0.0}
7 TreeItem {Child.0.0.0}
8 TreeItem {Child.0.0.1}
9 TreeItem {Child.0.0.2}
10 TreeItem {Child.0.1}
11 TreeItem {Child.0.1.0}
12 TreeItem {Child.0.1.1}
13 TreeItem {Child.0.1.2}
14 TreeItem {Child.0.2}
15 TreeItem {Child.0.2.0}
16 TreeItem {Child.0.2.1}
17 TreeItem {Child.0.2.2}
18 TreeItem {Child.1}
19 :
20 TreeItem {Child.2}
21 :
22
清单 10 显示了每个子类(来自 清单 4 中组合框和列表的例子)的 main 方法,并提供了 shell 的标题和大小、应用程序合成物的样式和所有命令行输入。
清单 10. 示例列表应用程序的 main 方法
2 run(List1App.class.getName(), "List1App Example", SWT.NONE, 400, 300, args);
3 }
4
每个通过 Java 反射技术加载的子类都必须定义一个构造函数和 completeGui 方法。子类可以选择性地提供 initGui 方法。再一次使用 清单 4 中的组合框和列表应用程序作为例子,这些方法如清单 11 中所示。
清单 11. 应用程序子类中提供的所需要的方法
2 super(shell, style); // must always supply parent and style
3 }
4 // Allow subclasses to complete the GUI
5 protected void completeGui(String[] args) {
6 // create GUI here
7 :
8 }
9 // Allow subclasses to initialize the GUI
10 protected void initGui() {
11 // finish GUI and add dynamic contents here
12 :
13 }
14
MessageBox
在结束本文的讨论之前,我将向您展示如何使用 MessageBox 控件请求用户输入选择的信息。
必须将 MessageBox 定义为以下 5 种互斥样式之一:
ICON_ERROR 表示一条错误消息。
ICON_INFORMATION 表示一条信息消息。
ICON_QUESTION 表示一条问题消息。
ICON_WARNING 表示一条警告消息。
ICON_WORKING 表示一条运行情况消息。
MessageBoxes 支持其他一些可选样式,所有样式都表示了它们在按钮上的各自选择:
OK, OK | CANCEL
YES | NO, YES | NO | CANCEL
RETRY | CANCEL
ABORT | RETRY | IGNORE
清单 12 显示了 MessageBox 一个典型用法,它在用户关闭应用程序 shell 时显示确认对话框,如 图 8 所示。
清单 12. 使用 MessageBox 创建一个退出确认对话框
2 public void shellClosed(ShellEvent e) {
3 MessageBox mb = new MessageBox(shell, SWT.ICON_QUESTION | SWT.OK | SWT.CANCEL);
4 mb.setText("Confirm Exit");
5 mb.setMessage("Are you sure you want to exit?");
6 int rc = mb.open();
7 e.doit = rc == SWT.OK;
8 }
9 });
10
结束语
在 SWT 和 JFace 系列的第二期中,我介绍了更多的 SWT 控件:组合框、列表、表和树。我还展示了如何为 SWT 应用程序创建基类,以及如何使用 helper 方法使构建 GUI 变得更容易。
本系列的下一期将向您展示如何创建更多的容器和输入控件,以及如何使用 StackLayout 布局管理器。
代码下载:os-jface2source.ZIP