【IT168 技术文章】SWT 支持这些控件的一个混合版本,在 custom 包中,这些控件被称为 TableTree。在 Eclipse V3.1 中,Tree 控件被增强为 TableTree 的一个功能替代,TableTree 则遭到反对。图 1 展示了一个表格式(TableTree 仿真模式)的示例 Tree。正如您可以看到的,树中的每一个项都被划分到列中。“第 2 部分”中展示了如何创建这些表和树,而创建一个表 Tree 实质上就是将这两项任务组合在一起。创建 TableTree 的代码与创建 Tree 的代码非常相似,因此,如果需要支持 Eclipse 的以前版本,那么可以使用 TableTree 控件。
图 1. 表树的例子
本文的其余部分将展示如何使用许多新的 SWT 控件。我将在一个称为 TabFolder1App 的单个应用程序的上下文中做这一介绍。
TabFolder(和 CTabFolder)
TabFolders 是一个创建使用有限数量空间的复杂 GUI 的简便方法。一个选项卡文件夹(tab folder)被分成一个或多个选项卡(tab),其中每个选项卡都是它本身的一个完整 GUI。一次只显示一个选项卡。在 custom 包中,CTabFolder 是 TabFolder 的增强版,它看起来更好一些,并且可以支持选项卡的关闭。
必须将 TabFolders 和 CTabFolders 定义为以下两个相互排斥的样式之一:
TOP —— 将选项卡放置在顶部。
BOTTOM —— 将选项卡放置在底部。
CTabFolder 支持其他一些可选样式:
FLAT —— 为文件夹提供一个扁平的外观。
BORDER —— 在控件的周围显示边界。
CLOSE —— 允许选项卡关闭(显示一个 Close 按钮)。
与包含一些项的 Trees 和 Tables 类似,TabFolders 也包含一些定义选项卡的 TabItems(或者 CTabItems)。TabFolders 还包含多个控件(通常是 Composites),每个控件都定义了选项卡的一个内容。TabItem.setControl 方法将该控件与相关的选项卡连接起来。
图 2 展示了一个示例 TabFolder,而图 3 展示了一个使用 CTabFolder 的类似 GUI。注意,选定的 Canvas 选项卡在 CTabFolder 上有一个 Close (X) 按钮。
图 2. 带有 4 个选项卡的 TabFolder
图 3. 带有 4 个选项卡的 CTabFolder
清单 1 详细介绍了一种方法,并展示了如何创建 TabFolders;也存在用于创建 CTabFolders 的类似代码。
清单 1. 用于创建 TabFolder 和 TabItem 的方法
2 return new TabFolder(parent, style);
3 }
4 protected TabItem createTabItem(TabFolder parent, int style,
5 String text, Image icon, Control ctl) {
6 TabItem ti = new TabItem(parent, style);
7 if (text != null) {
8 ti.setText(text);
9 }
10 if (icon != null) {
11 ti.setImage(icon);
12 }
13 if (ctl != null) {
14 ti.setControl(ctl);
15 }
16 return ti;
17 }
18 protected TabItem createTabItem(TabFolder parent,
19 String text, Image icon, Control ctl) {
20 return createTabItem(parent, SWT.NONE, text, icon, ctl);
21 }
22
Canvas 是最基本的控件类型之一,可以用它来创建定制控件或绘图。图 2 和 图 3 展示了使用 Canvas 来绘制由重叠的矩形和椭圆形组成的图片的一个例子。在这幅绘画中,一些图片被填充,而其他一些则没有被填充。颜色、大小和位置的分配是随意的。
清单 2 展示了用于创建 Canvas 的代码。要实际地在 Canvas 上进行绘图,必须向该 Canvas 添加一个 PaintListener。每当 Canvas 需要重新绘制其客户机区域的任何部分时,都需要调用其 paintControl 方法。有两种绘制风格:
直接绘制 —— 很简单,但内容在整个重绘期间是不稳定的。
在进行绘制之前构建一个模型,然后再根据此模型进行重新绘制 —— 比较复杂,但很稳定。这通常是首选方法。
清单 2. 用于创建 Canvas 的方法
2 PaintListener pl) {
3 Canvas c = new Canvas(parent, style);
4 if (pl != null) {
5 c.addPaintListener(pl);
6 }
7 return c;
8 }
9 protected Canvas createCanvas(Composite parent, PaintListener pl) {
10 return createCanvas(parent, SWT.NONE, pl);
11 }
12
作为绘制风格 2 的一个例子,可以考虑一下清单 3 中定义的简单模型:
清单 3. PaintItems 的层次结构
2 public Color color;
3 public void paint(GC gc) {
4 gc.setForeground(color);
5 gc.setBackground(color);
6 }
7 }
8 abstract protected class BaseRectItem extends PaintItem {
9 public boolean fill;
10 public Rectangle extent;
11 }
12 protected class ElipseItem extends BaseRectItem {
13 public void paint(GC gc) {
14 super.paint(gc);
15 if (fill) {
16 gc.fillOval(extent.x, extent.y,
17 extent.width, extent.height);
18 }
19 else {
20 gc.drawOval(extent.x, extent.y,
21 extent.width, extent.height);
22 }
23 }
24 }
25 protected class RectangleItem extends BaseRectItem {
26 public void paint(GC gc) {
27 super.paint(gc);
28 if (fill) {
29 gc.fillRectangle(extent.x, extent.y,
30 extent.width, extent.height);
31 }
32 else {
33 gc.drawRectangle(extent.x, extent.y,
34 extent.width, extent.height);
35 }
36 }
37 }
38
这些绘制项都由 清单 4 中显示的 PaintListener 绘制。paintControl 方法是随在其上进行绘制的图形上下文(org.eclipse.swt.graphics 包中的 GC)一起提供的。您可以使用 GC 绘制文本和许多形状。此代码将重用通过 Display 类可用的标准系统颜色。由 Canvas 决定是否使用某种背景色填充其区域。gcObjects 集合包含所有需要绘制的 PaintItem 实例。数组 colorIds 是一个到选定的系统颜色的映射。
清单 4. 用于创建 TabFolder 和 TabItem 的方法
2 public void paintControl(PaintEvent e) {
3 GC gc = e.gc;
4 gc.setBackground(canvas.getDisplay().
5 getSystemColor(colorIds[0]));
6 Point cext = canvas.getSize();
7 gc.fillRectangle(0, 0, cext.x, cext.y);
8 for (Iterator i = gcObjects.iterator();
9 i.hasNext();) {
10 PaintItem pi = (PaintItem)i.next();
11 pi.paint(gc);
12 }
13 }
14 }…
15 protected static int[] colorIds = {
16 SWT.COLOR_WHITE, SWT.COLOR_BLUE, SWT.COLOR_CYAN,
17 SWT.COLOR_GRAY, SWT.COLOR_GREEN, SWT.COLOR_MAGENTA,
18 SWT.COLOR_RED, SWT.COLOR_YELLOW, SWT.COLOR_BLACK
19 };
20
清单 5 中显示了一些代码,这些代码先清除绘画,然后创建由一组矩形和椭圆组成的绘画。通过 GUI 上的按钮可以激活此代码。
清单 5. 用于处理绘制事件的方法
2 gcObjects.clear();
3 canvas.redraw();
4 }
5 public void doDraw() {
6 gcObjects.clear();
7 Display display = drawButton.getDisplay();
8 // create a bunch of objects
9 for (int i = 0; i < 50; i++) {
10 if (i % 2 == 0) {
11 RectangleItem ri = new RectangleItem();
12 ri.extent = new Rectangle(nextInt(500), nextInt(250),
13 nextInt(500), nextInt(250));
14 ri.color = display.
15 getSystemColor(colorIds[nextInt(colorIds.length)]);
16 ri.fill = i % 3 == 0;
17 gcObjects.add(ri);
18 }
19 else {
20 ElipseItem ei = new ElipseItem();
21 ei.extent = new Rectangle(nextInt(500), nextInt(250),
22 nextInt(500), nextInt(250));
23 ei.color = display.
24 getSystemColor(colorIds[nextInt(colorIds.length)]);
25 ei.fill = i % 5 == 0;
26 gcObjects.add(ei);
27 }
28 }
29 canvas.redraw();
30 }
31
SWT 支持几种输入离散值的方法。Scale 允许在(通常很小的)整数范围内挑选一个值。Slider 允许使用类似滚动条的方法在(可能很大的)整数范围内挑选一个值。Spinner 允许挑选(通过向前/撤退按钮)或键入一个(可能为小数的)数字。注意,Spinner 是 Eclipse V3.1 中的一个新特性。ProgressBar 类似于一个只输出的 Slider,因为可以用它来展示增量活动(进度)。
通常,这些控件允许您提供最小值、最大值和初始值。除了 ProgressBars 之外,这些控件还支持增量值和页面增量值,Sliders 还支持 thumb 宽度。图 4 展示了一个 GUI,它在控件组内包含一个 Slider、一个 Spinner 和一个 Scale,在这些控件的下方是一个 ProgressBar。紧贴在进度条上的是一个(居中的)Label,它展示了进度条的值。
图 4. 控件的例子
必须将所有这些控件定义为以下两种相互排斥的样式之一:
HORIZONTAL —— 水平地布置控件。
VERTICAL —— 垂直地布置控件。
Spinners 支持其他一些可选样式:
WRAP —— 从高值向低值换行排列。
READ_ONLY —— 不允许键入输入值。
ProgressBars 支持其他一些可选样式:
SMOOTH —— 更新不是在截然不同的步骤中进行的。
INDETERMINATE —— 没有预先确定步骤数的范围;进度条只是在时间上重复。
要创建这些控件,可以使用清单 6-9 中所示的代码。将通过 registerCallback 方法,使用 Java 反射将 SelectionListeners 添加到这些控件中。每当控件的值发生更改时,都会调用此侦听器。
清单 6. 用于创建 Slider 的方法
2 int min, int current, int max,
3 int inc, int pageinc, int thumb,
4 String callback) {
5 Slider s = new Slider(parent, style);
6 if (min >= 0) {
7 s.setMinimum(min);
8 }
9 if (max >= 0) {
10 s.setMaximum(max);
11 }
12 if (current >= 0) {
13 s.setSelection(current);
14 }
15 if (inc >= 1) {
16 s.setIncrement(inc);
17 }
18 if (pageinc >= 1) {
19 s.setPageIncrement(pageinc);
20 }
21 if (thumb >= 1) {
22 s.setThumb(thumb);
23 }
24 if (callback != null) {
25 registerCallback(s, this, callback);
26 }
27 return s;
28 }
29 protected Slider createVSlider(Composite parent,
30 int min, int current, int max,
31 int inc, int pageinc, int thumb,
32 String callback) {
33 return createSlider(parent, SWT.VERTICAL, min, current, max,
34 inc, pageinc, thumb, callback);
35 }
36 protected Slider createHSlider(Composite parent,
37 int min, int current, int max,
38 int inc, int pageinc, int thumb,
39 String callback) {
40 return createSlider(parent, SWT.HORIZONTAL, min, current, max,
41 inc, pageinc, thumb, callback);
42 }
43
清单 7. 用于创建 Spinner 的方法
2 int min, int current, int max,
3 int inc, int pageinc, String callback) {
4 Spinner s = new Spinner(parent, style);
5 if (min >= 0) {
6 s.setMinimum(min);
7 }
8 if (max >= 0) {
9 s.setMaximum(max);
10 }
11 if (current >= 0) {
12 s.setSelection(current);
13 }
14 if (inc >= 1) {
15 s.setIncrement(inc);
16 }
17 if (pageinc >= 1) {
18 s.setPageIncrement(pageinc);
19 }
20 if (callback != null) {
21 registerCallback(s, this, callback);
22 }
23 return s;
24 }
25
清单 8. 用于创建 Scale 的方法
2 int min, int current, int max,
3 int inc, int pageinc) {
4 Scale s = new Scale(parent, style);
5 if (min >= 0) {
6 s.setMinimum(min);
7 }
8 if (max >= 0) {
9 s.setMaximum(max);
10 }
11 if (current >= 0) {
12 s.setSelection(current);
13 }
14 if (inc >= 1) {
15 s.setIncrement(inc);
16 }
17 if (pageinc >= 1) {
18 s.setPageIncrement(pageinc);
19 }
20 return s;
21 }
22 protected Scale createVScale(Composite parent,
23 int min, int current, int max,
24 int inc, int pageinc) {
25 return createScale(parent, SWT.VERTICAL, min, current, max,
26 inc, pageinc);
27 }
28 protected Scale createHScale(Composite parent,
29 int min, int current, int max,
30 int inc, int pageinc) {
31 return createScale(parent, SWT.HORIZONTAL, min, current, max,
32 inc, pageinc);
33 }
34
清单 9. 用于创建 ProgressBar 的方法
2 int min, int current, int max) {
3 ProgressBar pb = new ProgressBar(parent, style);
4 if (min >= 0) {
5 pb.setMinimum(min);
6 }
7 if (max >= 0) {
8 pb.setMaximum(max);
9 }
10 if (current >= 0) {
11 pb.setSelection(current);
12 }
13 return pb;
14 }
15 protected ProgressBar createVProgressBar(Composite parent,
16 int min, int current, int max) {
17 return createProgressBar(parent, SWT.VERTICAL, min, current, max);
18 }
19 protected ProgressBar createHProgressBar(Composite parent,
20 int min, int current, int max) {
21 return createProgressBar(parent, SWT.HORIZONTAL, min, current, max);
22 }
23
您可以查询或设置这些控件的当前值。考虑一下清单 10 中定义的线程,该线程将更新 图 4 中的标签、进度条和滑块。此线程在选中“Automatic Update”按钮(即代码中的 modeButton)时启动。
清单 10. 用于更新控件的线程
2 protected int delay;
3 protected Display display;
4 public BarUpdater(Display display) {
5 this.display = display;
6 }
7 public void run() {
8 try {
9 while (true) {
10 try {
11 if (!display.isDisposed()) {
12 display.syncExec(new Runnable() {
13 public void run() {
14 if (!modeButton.isDisposed() &&
15 !scale.isDisposed()) {
16 delay = modeButton.getSelection()
17 ? scale.getSelection() : -1;
18 }
19 }
20 });
21 if (delay >= 0) {
22 Thread.sleep(delay);
23 if (!display.isDisposed()) {
24 display.syncExec(new Runnable() {
25 public void run() {
26 if (!bar.isDisposed()) {
27 int v = bar.getSelection() + 1;
28 if (v > bar.getMaximum()) {
29 v = bar.getMinimum();
30 }
31 bar.setSelection(v);
32 if (!slider.isDisposed()) {
33 slider.setSelection(v);
34 }
35 if (!valueLabel.isDisposed()) {
36 valueLabel.setText(
37 Integer.toString(v));
38 }
39 }
40 }
41 });
42 }
43 }
44 }
45 Thread.sleep(100);
46 }
47 catch (InterruptedException ie) {
48 }
49 }
50 }
51 catch (Exception e) {
52 e.printStackTrace();
53 }
54 }
55 }
56
注意,此代码小心地进行检查,看各种控件在使用之前是否已经就绪。在异步 GUI 操作中,这很关键。还会注意到,所有 GUI 访问都是在一个 syncExec(或其同类 asyncExec)方法中进行的。每当在与创建 GUI 所在的线程不同的线程上访问 GUI 时,都需要这样做。
SWT 通过 Text 控件支持纯文本的输入和显示。对于更高级的文本表示形式,需要定义字体和颜色,因此可以使用 custom 包中的 StyledText 控件。StyledText 是可由许多 Eclipse 编辑器使用的控件。请考虑一下图 5 中所示的样式文本的示例。该文本包含不同的颜色和字体修饰,比如下划线、删除线、粗体和斜体。注意,删除线和下划线只在 Eclipse V3.1 上受到支持。
图 5. StyledText 的例子

必须将 StyledText 定义为以下两种相互排斥的样式之一:
MULTI —— 显示多个行。
SINGLE —— 显示单个行。
StyledText 支持其他一些可选样式:
WRAP —— 从控件的右边换行。
READ_ONLY —— 不允许键入输入值。
清单 11 显示了用于创建 StyledText 的代码。清单 12 使用简单的类似 XML 的语言展示了它的用法,以定义具有这些属性的文本的范围。
清单 11. 用于创建 StyledText 的方法
2 return new StyledText(parent, style);
3 }
4
清单 12. StyledText 的例子
2 SWT.FULL_SELECTION);
3 styledText.addMouseListener(new MouseAdapter() {
4 public void mouseDown(MouseEvent e) {
5 if (e.button == 3) {
6 processPopup();
7 }
8 }});
9 TextContent tc = new TextContent(body.getDisplay());
10 tc.setContent(dummyContent);
11 styledText.setText(tc.toPlainText());
12 styledText.setStyleRanges(tc.getStyleRanges());
13 :
14 protected static final String dummyContent =
15 "Just plain text!\n" +
16 "Now is the time for <b>all</b> good men " +
17 "to come to the aid of their country\n" +
18 "<red><i>To <b>be</b></i> or <i>not to <b>be</b></i>?</red> " +
19 "<blue><u>That</u> is the <so>question</so></blue>\n" +
20 "That's all folks!\n";
21
StyledText 的例子使用了一个 helper 类 TextContent,以确定具有特殊属性的文本的范围。这个类包含在示例代码中,支持对文档进行分析并获得其纯文本内容,以及查找各种范围,其中的纯文本内容具有不同属性或属性组合。它创建了表示这些范围的 SWT 类 StyleRange 的一个数组。StyleRange 有一些字段,这些字段描述了这样的范围,该范围包括前景色和背景色,以及要应用的起始偏移量、长度和属性,比如字体样式(粗体或斜体)、删除线和下划线。
StyledText 所具有的功能比这里展示的还要多。例如,在将文本输入到文档中时,文本没有任何属性,即使是在具有属性的点输入它也是如此。您可以使用 StyledText 的特性来改正这一行为。
有时,当您希望能够将一个选择列表显示为一个弹出式菜单而不用创建一个弹出式菜单时,可以使用 PopupList 控件做到这一点。图 6 显示了如何使用此控件将选择、剪切、复制和粘贴功能添加到 图 5 的文本编辑器中。
图 6. PopupList 的例子

清单 13 显示了 processPopup 方法的内容。注意,安置弹出式菜单的代码与它所涉及的控件(即样式文本)有关。
清单 13. PopupList 的例子
2 popup.select(popupItems[0]);
3 Point p = styledText.getLocation();
4 p = shell.getDisplay().map(styledText, null, p.x, p.y);
5 String choice = popup.open(new Rectangle(
6 p.x + 100, p.y - 100, 100, 200));
7 if (choice != null) {
8 if (popupItems[0].equals(choice)) {
9 styledText.selectAll();
10 }
11 else if (popupItems[1].equals(choice)) {
12 styledText.cut();
13 }
14 else if (popupItems[2].equals(choice)) {
15 styledText.copy();
16 }
17 else if (popupItems[3].equals(choice)) {
18 styledText.paste();
19 }
20 }
21 :
22 protected static final String[] popupItems = {
23 "Select All", "Cut", "Copy", "Paste"
24 };
StackLayout
在前两篇文章中,我讨论了随 SWT 一起提供的几个布局管理器,其中包括 FillLayout、GridLayout 和 FormLayout。custom 包提供了 StackLayout,可以用它在一次只显示一个 GUI 的 TabFolder(有些类似于没有选项卡的 TabFolder)的顶部放置多个 GUI。考虑一下 图 7 和 图 8,它们显示了一个显示带编号标签的堆栈布局的两种状态。通过“>>”按钮可以让堆栈前进,而通过“<<”按钮则可以让堆栈后退。图 8 显示了按下“>>”按钮 4 次后的布局。
图 7. StackLayout 的例子 1
图 8. StackLayout 的例子 2
此堆栈由清单 14 中的代码创建。
清单 14. StackLayout 的例子
2 Composite clabels = createComposite(body, SWT.BORDER,
3 stackLayout);
4 Label[] labels = new Label[5];
5 :
6 for (int i = 0; i < labels.length; i++) {
7 Label xlabel = new Label(clabels, SWT.CENTER);
8 xlabel.setText("Stack " + i);
9 labels[i] = xlabel;
10 }
11 stackLayout.topControl = labels[0];
12 :
13 protected Composite createComposite(Composite parent,
14 int style,
15 Layout layout) {
16 Composite c = new Composite(parent, style);
17 if (layout != null) {
18 c.setLayout(layout);
19 }
20 return c;
21 }
22 protected Composite createComposite(Composite parent,
23 Layout layout) {
24 return createComposite(parent, SWT.NONE, layout);
25 }
26
清单 15 显示了通过“>>”按钮到达下一个堆栈的代码。对于“<<”按钮,代码与此类似。
清单 15. 前进到下一个堆栈的代码
2 public void doNext() {
3 ++currentLabel;
4 if (currentLabel >= labels.length) {
5 currentLabel = 0;
6 }
7 stackLayout.topControl = labels[currentLabel];
8 clabels.layout();
9 }
10
结束语
在 SWT 和 JFace 系列的第三期中,我介绍了更多的 SWT 控件,比如用于创建表树的 Tree;用于绘图的 Canvas;用于输入数字值的 Slider、Scale 和 Spinner;用于显示进度的 ProgressBar;用于输入具有某些属性的文本的 StyledText;以及用于简单动态菜单的 PopupList。我还展示了如何使用 StackLayout 创建时间合理的重叠 GUI。本系列的下一期将展示如何使用更多的 SWT 控件。
代码下载:os-Samples-SWT3.ZIP