編程人員可以使用 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 防止用戶編輯該組合框的文本字段。
FormLayout
清單 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 控件請求用戶輸入選擇的信息。
必須將 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 布局管理器。
來源:http://www.ibm.com/developerworks/cn/opensource/os-jface2/