编程人员可以使用 Standard Widget Toolkit (SWT) 和 JFace 库来开发适用于 Eclipse 环境的图形用户界面 (GUI),以及开发单独的 GUI 本机应用程序。
在本系列的 第 1 部分 中,我向您介绍了 Eclipse、Eclipse SWT 和 JFace GUI 工具包,以构造 Eclipse 和单独的富 GUI(rich GUI)。我还介绍了一些基本的标签、文本和按钮 GUI 控件,以及复合、组和 shell 容器类型。最后,我展示了如何将这些控件组合到一个简单的工作应用程序中。
在这一期中,您将学习如何向应用程序添加菜单,如何使用一些列表输入控件,以及如何使用更高级的表和三个容器控件。我还将通过采用一些使构建 GUI 变得更容易的服务方法来演示一些最佳实践。最后,我将向您展示如何将可重用的函数应用到基本应用程序类中。
除非特别注明,所有讨论的小部件和控件都位于 org.eclipse.swt.widgets
包中。
除了最基本的 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. 创建一个菜单系统和一个弹出菜单
import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; : Shell shell = ...; : Label body = ...; : // Create the menu bar system Menu main = createMenu(shell, SWT.BAR | SWT.LEFT_TO_RIGHT); shell.setMenuBar(main); MenuItem fileMenuItem = createMenuItem(main, SWT.CASCADE, "&File", null, -1, true, null); Menu fileMenu = createMenu(shell, SWT.DROP_DOWN, fileMenuItem, true); MenuItem exitMenuItem = createMenuItem(fileMenu, SWT.PUSH, "E&xit/tCtrl+X", null, SWT.CTRL + 'X', true, "doExit"); MenuItem helpMenuItem = createMenuItem(main, SWT.CASCADE, "&Help", null, -1, true, null); Menu helpMenu = createMenu(shell, SWT.DROP_DOWN, helpMenuItem, true); MenuItem aboutMenuItem = createMenuItem(helpMenu, SWT.PUSH, "&About/tCtrl+A", null, SWT.CTRL + 'A', true, "doAbout"); // add popup menu Menu popup = createPopupMenu(shell, body); MenuItem popupMenuItem1 = createMenuItem(popup, SWT.PUSH, "&About", null, -1, true, "doAbout"); MenuItem popupMenuItem2 = createMenuItem(popup, SWT.PUSH, "&Noop", null, -1, true, "doNothing"); |
此代码序列创建了以下菜单栏,该菜单栏中包含一些子菜单和一个弹出菜单(参见 图 1、图 2、图 3 和 图 4)。body
值是一个标签控件,包含文本“Sample body”。弹出菜单与这个控件在上下文上存在关联。
|
图 1. 带有 File 和 Help 菜单的菜单栏
图 2. 下拉状态的 File 菜单
图 3. 下拉状态的 Help 菜单
图 4. 弹出菜单
正如您所见,菜单项可以具有加速器(Ctrl+?
)和记忆术(给通过 &
标识的字符加下划线),帮助用户使用键盘选择一些项。
我使用一组 helper 方法创建了这些菜单,如清单 2 中所示。最佳实践是创建与这些 helper 方法类似的方法,用这些方法创建重复的 GUI 部分,如菜单。随着时间的推移,您可以向这些 helper 方法添加更多的支持功能,并将它们应用到所有使用点。这些方法还有助于提示您获得所有需要的值。
清单 2. 菜单创建 helper 例程
protected Menu createMenu(Menu parent, boolean enabled) { Menu m = new Menu(parent); m.setEnabled(enabled); return m; } protected Menu createMenu(MenuItem parent, boolean enabled) { Menu m = new Menu(parent); m.setEnabled(enabled); return m; } protected Menu createMenu(Shell parent, int style) { Menu m = new Menu(parent, style); return m; } protected Menu createMenu(Shell parent, int style, MenuItem container, boolean enabled) { Menu m = createMenu(parent, style); m.setEnabled(enabled); container.setMenu(m); return m; } protected Menu createPopupMenu(Shell shell) { Menu m = new Menu(shell, SWT.POP_UP); shell.setMenu(m); return m; } protected Menu createPopupMenu(Shell shell, Control owner) { Menu m = createPopupMenu(shell); owner.setMenu(m); return m; } protected MenuItem createMenuItem(Menu parent, int style, String text, Image icon, int accel, boolean enabled, String callback) { MenuItem mi = new MenuItem(parent, style); if (text != null) { mi.setText(text); } if (icon != null) { mi.setImage(icon); } if (accel != -1) { mi.setAccelerator(accel); } mi.setEnabled(enabled); if (callback != null) { registerCallback(mi, this, callback); } return mi; } |
清单 3 显示了如何使用 Java 的反射 功能,利用处理菜单项的代码来链接菜单项。此功能创建了一个易于使用的方法,在这个方法中,只需要给应用程序类添加一个 public
方法(比如 doExit
、doAbout
或 doNothing
),就可以处理菜单命令。
清单 3. 处理菜单命令的
Callback
例程protected void registerCallback(final MenuItem mi, final Object handler, final String handlerName) { mi.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { try { Method m = handler.getClass().getMethod(handlerName, null); m.invoke(handler, null); } catch (Exception ex) { ex.printStackTrace(); } } }); } |
我在本系列的 第 1 部分 中描述了使用 SelectionListener
的细节。
请注意,菜单项(以及稍后讨论的列表、表、和树控件中的项)只支持字符串值;在添加其他类型的值之前,这些值将被转换成字符串值。
|
通常,您希望 GUI 的用户从预先确定的值列表中进行选择。列表 控件是做到这一点的最简单的方法。列表显示了一组预先定义的、用户可以从中进行选择的字符串值。列表通常需要大量的屏幕实际信息(real estate)。如果您想节省空间,那么可以使用组合框 控件,组合框允许在需要的时候让列表处于下拉状态。组合框还可以有选择地允许用户在类似文本的字段中输入所需要的值。
必须将组合框定义为以下两种互斥样式之一:
SIMPLE
显示值的列表。DROP_DOWN
使值的列表处于下拉状态。
组合框支持一种可选样式:
READ_ONLY
防止用户编辑此组合框的文本字段。
我所讨论的所有控件(列表、组合框、表和树)都支持以下两种互斥样式之一:
SINGLE
用户只能选择一个项。MULTI
用户可以选择多个项。
这些控件还支持其他样式:
H_SCROLL
在需要时显示了一个水平滚动的条。V_SCROLL
在需要时显示了一个垂直滚动的条。
创建组合框和列表相当容易。创建这些控件和添加所需要的字符串值,如清单 4 所示。
清单 4. 使用
FormLayout
创建一个组合框和一个列import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.*; import org.eclipse.swt.events.*; import org.eclipse.swt.layout.*; : setLayout(new FormLayout()); String[] data = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10" }; Combo c = createCombo(this, data); configureLayout(c, new FormAttachment(0, 5), new FormAttachment(0, 5), new FormAttachment(100, -5), null); List l = createList(this, data); configureLayout(l, new FormAttachment(0, 5), new FormAttachment(c, 5), new FormAttachment(100, -5), new FormAttachment(100, -5)); // Create a Combo protected Combo createCombo(Composite parent, String[] data) { Combo combo = new Combo(parent, SWT.DROP_DOWN | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL); combo.addSelectionListener(new SelectionListener() { : }); setComboContents(data); return combo; } // Create a List protected List createList(Composite parent, String[] data) { List list = new List(parent, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL); list.addSelectionListener(new SelectionListener() { : }); setListContents(data); return list; } public void setComboContents(String[] data) { combo.removeAll(); for (int i = 0; i < data.length; i++) { combo.add(data[i]); } } public void setListContents(String[] data) { list.removeAll(); for (int i = 0; i < data.length; i++) { list.add(data[i]); } } |
如果添加 SelectionListener
,那么它允许应用程序在用户更改所选定的项时采取行动。
清单 4 中的主代码序列的流假定 SelectionListener 包含在 this
引用的一些合成物中。它创建了如图 5 中所示的组合框和(部分已隐藏的)列表。
图 5. 组合框和列表的例子
您可以使用组合框控件的一个叫做 CCombo 的替代实现(位于 org.eclipse.swt.custom
包中)。除了支持一些额外的功能,CCombo 类似于 Combo,最重要的是,您可以以编程方式要求 CCombo 将文本剪切、复制或粘贴到它的嵌入式 Text 控件中,反之亦可。此外,CCombo 总是以 DROP_DOWN
样式出现,所以它不支持类型样式。
CCombos 还支持一些可选样式:
BORDER
显示了一个围绕文本区的边框。READ_ONLY
防止用户编辑该组合框的文本字段。
清单 4 中的例子使用 FormLayout
来放置组合框和列表。FormLayout
是最有用的布局管理器之一,因为它允许您相对于其他控件来安排每个控件,允许您将控件的任意一边(左边、顶部、右边或底部)附着到另一个控件的(通常相对的)边,或者附着到容器的某一边上。未附着的边则采用该控件的自然相对维数(natural corresponding dimension)。可以使用 FormAttachment
的一个实例,将引用控件或容器大小的百分比指定为附着点,并提供距离此点的像素偏移量。清单 4 中的代码使用了来自清单 5 的 helper 方法。
清单 5.
configureLayout
: FormLayout 帮助器方法protected static void configureLayout(Control c, FormAttachment left, FormAttachment top, FormAttachment right, FormAttachment bottom) { FormData fd = new FormData(); if (left != null) { fd.left = left; } if (top != null) { fd.top = top; } if (right != null) { fd.right = right; } if (bottom != null) { fd.bottom = bottom; } c.setLayoutData(fd); } |
|
表 是支持 TableColumns 的列表的增强形式。这些列将它们的数据对齐成一种更可读的形式。它们还支持列名,并能调整列的大小。要创建表,首先要创建表控件,然后添加 TableItems 中包装的字符串数据。
表支持以下可选样式:
CHECK
将复选框添加到第一列中。VIRTUAL
支持大型表(特定于平台)。FULL_SELECTION
选择所有列(不仅仅是第一列)。
清单 6 创建了图 6 中所示的表。
清单 6. 使用 helper 方法创建一个表
// Create the Table and TableColumns protected Table createTable(Composite parent, int mode, Object[] contents) { table = new Table(parent, mode | SWT.SINGLE | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL); table.setHeaderVisible(true); table.setLinesVisible(true); createTableColumn(table, SWT.LEFT, "Column 1", 100); createTableColumn(table, SWT.CENTER, "Column 2", 100); createTableColumn(table, SWT.RIGHT, "Column 3", 100); addTableContents(contents); return table; } protected TableColumn createTableColumn(Table table, int style, String title, int width) { TableColumn tc = new TableColumn(table, style); tc.setText(title); tc.setResizable(true); tc.setWidth(width); return tc; } protected void addTableContents(Object[] items) { for (int i = 0; i < items.length; i++) { String[] item = (String[])items[i]; TableItem ti = new TableItem(table, SWT.NONE); ti.setText(item); } } : // sample creation code protected void initGui() { Object[] items = { new String[] {"A", "a", "0"}, new String[] {"B", "b", "1"}, new String[] {"C", "c", "2"}, new String[] {"D", "d", "3"}, new String[] {"E", "e", "4"}, new String[] {"F", "f", "5"}, new String[] {"G", "g", "6"}, new String[] {"H", "h", "7"}, new String[] {"I", "i", "8"}, new String[] {"J", "j", "9"} }; table = createTable(this, SWT.CHECK, items); } |
图 6. 表的例子
第一列中的复选框是可选的。注意列的对齐方式。
|
树 是可以显示分层信息的列表。树支持应用程序的扩展和折叠层次结构的中间级别的能力。
因为树常常显示分层结构,所以应该给它们提供一个数据模型供它们使用(在谈论 JFace 时,我将再次提到这个模型概念)。为此,在我们的例子中使用了内部类 Node
,如清单 7 所示。
清单 7. 树模型的类节点
public class Node { protected java.util.List children; public java.util.List getChildren() { return children; } public void setChildren(java.util.List children) { this.children = children; } public void addChild(Node node) { children.add(node); } protected String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Node(String name) { this(name, new ArrayList()); } public Node(String name, java.util.List children) { setName(name); setChildren(children); } } |
要创建树,首先要创建树控件,然后添加 TreeItems 中包装的字符串数据。TreeItems 可以包含其他 TreeItems,这样就可以创建值的层次结构。清单 8 创建了图 7 中所示的树。
清单 8. 使用 helper 方法创建树
// Create the Tree protected Tree createTree(Composite parent, int mode, Node root) { tree = new Tree(parent, mode | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL); tree.addSelectionListener(new SelectionListener() { : }); setTreeContents(root); return tree; } protected void setTreeContents(Node root) { tree.removeAll(); TreeItem ti = new TreeItem(tree, SWT.NONE); setTreeItemContents(ti, root); } protected void setTreeItemContents(TreeItem ti, Node root) { ti.setText(root.getName()); java.util.List children = root.getChildren(); if (children != null && children.size() > 0) { for (Iterator i = children.iterator(); i.hasNext();) { Node n = (Node)i.next(); TreeItem tix = new TreeItem(ti, SWT.NONE); setTreeItemContents(tix, n); } } } : // sample creation code protected void addChildren(Node n, int count, int depth, String prefix) { if (depth > 0) { for (int i = 0; i < count; i++) { String name = prefix + '.' + i; Node child = new Node(name); n.addChild(child); addChildren(child, count, depth - 1, name); } } } Node root = new Node("<root>"); addChildren(root, 3, 3, "Child"); tree = createTree(this, SWT.CHECK, root); |
图 7. 树的例子
复选框是可选的。
|
除了菜单的例子之外,本文中的所有例子都使用了一个叫做 BasicApplication 的基类,以简化它们的实现。作为另一个最佳实践的例子,我将 SWT GUI 应用程序的一些常见功能应用到这个基类中(包括来自菜单示例的 helper 方法),以使它们更易于使用。
BasicApplication 是一个合成物,它创建了自己的 shell。该类提供了一些额外的功能,比如退出确认对话框(参见图 8),以及将小部件树作为诊断帮助工具(diagnostic aid)转储出来的能力(参见清单 9 中一个经过删减的例子)。请参阅 参考资料,以获得这个类的代码。
图 8. 确认消息对话框
清单 9. 控件层次结构的打印输出(部分)
Shell {Tree1App Example} Tree1App {} Tree {} TreeItem {<root>} TreeItem {Child.0} TreeItem {Child.0.0} TreeItem {Child.0.0.0} TreeItem {Child.0.0.1} TreeItem {Child.0.0.2} TreeItem {Child.0.1} TreeItem {Child.0.1.0} TreeItem {Child.0.1.1} TreeItem {Child.0.1.2} TreeItem {Child.0.2} TreeItem {Child.0.2.0} TreeItem {Child.0.2.1} TreeItem {Child.0.2.2} TreeItem {Child.1} : TreeItem {Child.2} : |
清单 10 显示了每个子类(来自 清单 4 中组合框和列表的例子)的 main
方法,并提供了 shell 的标题和大小、应用程序合成物的样式和所有命令行输入。
清单 10. 示例列表应用程序的 main 方法
public static void main(String[] args) { run(List1App.class.getName(), "List1App Example", SWT.NONE, 400, 300, args); } |
每个通过 Java 反射技术加载的子类都必须定义一个构造函数和 completeGui
方法。子类可以选择性地提供 initGui
方法。再一次使用 清单 4 中的组合框和列表应用程序作为例子,这些方法如清单 11 中所示。
清单 11. 应用程序子类中提供的所需要的方法
public List1App(Shell shell, int style) { super(shell, style); // must always supply parent and style } // Allow subclasses to complete the GUI protected void completeGui(String[] args) { // create GUI here : } // Allow subclasses to initialize the GUI protected void initGui() { // finish GUI and add dynamic contents here : } |
在结束本文的讨论之前,我将向您展示如何使用
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
创建一个退出确认对话框shell.addShellListener(new ShellAdapter() { public void shellClosed(ShellEvent e) { MessageBox mb = new MessageBox(shell, SWT.ICON_QUESTION | SWT.OK | SWT.CANCEL); mb.setText("Confirm Exit"); mb.setMessage("Are you sure you want to exit?"); int rc = mb.open(); e.doit = rc == SWT.OK; } }); |
|
在 SWT 和 JFace 系列的第二期中,我介绍了更多的 SWT 控件:组合框、列表、表和树。我还展示了如何为 SWT 应用程序创建基类,以及如何使用 helper 方法使构建 GUI 变得更容易。
本系列的下一期将向您展示如何创建更多的容器和输入控件,以及如何使用 StackLayout
布局管理器。