Standard Widget Toolkit ( SWT,标准窗口小部件工具箱 ),是在 Eclipse 平台上使用的窗口小部件工具箱,它能向开发者提供和本机平台一致的用户界面和比较稳定的性能,并封装了大量的绘图 API,提供了强大的图像处理和绘图功能。在文章 SWT 图像处理入门中已经详细介绍了如何用 SWT 封装的 Image,ImageData 等类实现常用的图像处理功能,本文将重点介绍 SWT 高级绘图模式(其中包含 GDI+ 的高级特性:变换、路径、透明度、反锯齿)、图形上下文(其中包含 XOR 方法与高级模式、绘制渐进色、图像拷贝、计算文本大小与高级模式),最后将介绍如何使用双缓冲区来消除闪屏。
相信对 SWT 有一定了解的人都知道,SWT 为我们开发人员提供了大量与绘图相关的 API,大大降低了我们工作学习当中使用 Java 技术进行开发的难度。为了正确的使用 SWT 所提供的一系列绘图接口,读者有必要对 SWT 高级模式、图形上下文、双缓冲区等绘图相关的问题有一个大概地了解。表 1 列举了文章中出现的所有代码清单以及其所对应的名称。
表 1. 示例代码清单
代码清单 | 名称 |
清单 1 | 保持输出一致性示例 |
清单 2 | 图形旋转(用变换矩阵) |
清单 3 | 图形旋转(调用旋转函数) |
清单 4 | 二次方曲线路径 |
清单 5 | XOR 失效示例 |
清单 6 | 渐进色示例 |
清单 7 | 绘制透明文本 |
清单 8 | 文本大小与高级模式 |
清单 9 | 用 SWT.NO_REDRAW_RESIZE 消除闪屏 |
清单 10 | 实现 DOUBLE BUFFERED |
清单 11 | 设置 SWT.DOUBLE_BUFFERED 样式 |
在 Eclipse 3.1 Release 中, SWT 高级绘图模式作为平台新特性被添加进来。在高级绘图模式下,开发人员可以使用普通绘图模式下没有的 GDI+ 高级特性,例如变换、路径、透明度、反锯齿等。下面将逐个介绍如何使用这些 GDI+ 高级特性。
方法 setAdvance 是系统高级图形子系统的开关,当参数为 true 时,GC 将采用系统高级图形子系统来进行绘图(注意:某些操作系统只有一个绘图子系统,从普通模式切换到高级绘图模式将不起任何作用)。值得注意的是,高级图形子系统将被自动的调用,如果方法 setAlpha(int alpha)、setAntialias(int antialias) 等被调用的话。由于高级模式和普通模式的不同,其输出也可能不同。因此,如果要使用高级模式,应该在所有的绘图操作之前,切换当前模式为高级模式以保证输出的一致性。例如清单 1 。
清单 1:保持输出一致性示例
boolean isAdvanced = gc.getAdvanced(); gc.setAdvanced(true/false); // 进行各种操作 … … // 恢复绘图模式 gc.setAdvanced(isAdvanced); |
除了在文章 SWT 图像处理入门中提到的利用 ImageData 完成各种图形变换之外,SWT 高级模式下的类 Transform 为用户提供了另外一种选择。类 org.eclipse.swt.graphics.Transform 封装了图像的反向 (void invert())、旋转 (void rotate(float angle),参数 angle 为旋转角度 )、拉伸 (void scale(float scaleX, float scaleY)) 等操作,另外它还提供获取和设置变换矩阵的方法。清单 2 和 3 分别演示了两种利用 Transform 来完成图形的旋转 45 度。
清单 2. 图形旋转(用变换矩阵)
GC gc = e.gc; gc.setAdvanced(true); if (!gc.getAdvanced()) { gc.drawText("Advanced graphics not supported", 30, 30, true); gc.dispose(); return; } gc.drawImage(image, x, y); Transform transform = new Transform(display); float cos45 = (float)Math.cos(Math.PI/4); float sin45 = (float)Math.sin(Math.PI/4); transform.setElements(cos45, sin45, -sin45, cos45, 0, 0); gc.setTransform(transform); gc.drawImage(image, 350, 100); transform.dispose(); gc.dispose(); |
清单 3. 图形旋转(调用旋转函数)
GC gc = e.gc; gc.setAdvanced(true); if (!gc.getAdvanced()) { gc.drawText("Advanced graphics not supported", 30, 30, true); gc.dispose(); return; } gc.drawImage(image, x, y); Transform transform = new Transform(display); transform.rotate(45); gc.setTransform(transform); gc.drawImage(image, 350, 100); transform.dispose(); gc.dispose(); |
路径指得是二维系统中的路径。它不一定是连续的,可以是直线、矩形、弧形、立方体、文字,甚至是二次方曲线等其他路径。清单 4 演示了二次方曲线路径,如图 1 所示。
清单 4. 二次方曲线路径
final Display display = new Display(); final Shell shell = new Shell(display); shell.setText("二次方曲线路径"); final Image image = new Image(display, 400, 300); final Rectangle rect = image.getBounds(); shell.addListener(SWT.Paint, new Listener() { public void handleEvent(Event event) { GC gc = event.gc; gc.drawImage(image,0,0, rect.width, rect.height,0,0, rect.width / 2, rect.height / 2); Path path = new Path(display);// 绘制二次方曲线路径path.quadTo(2,60,60,140); gc.setBackground(display.getSystemColor(SWT.COLOR_GREEN)); gc.setForeground(display.getSystemColor(SWT.COLOR_BLUE)); gc.fillPath(path); gc.drawPath(path);// 记住释放 Path 资源path.dispose(); } }); shell.setSize(shell.computeSize(rect.width / 2, rect.height / 2)); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } image.dispose(); display.dispose(); |
图 1. 路径示意图

透明度、反锯齿这两个特性在绘图中也有着广泛的应用。我们在实际开发中经常会用到透明文本等来增加视觉效果,在图形上下文章节中将演示如何绘制透明文本。
在绘图或者图像处理中,缩放等操作会导致明显的锯齿和失真。 SWT 高级绘图模式允许用户使用 GC.setAntialias(int antialias) 来使能 / 禁止反锯齿功能。
谈到 SWT 的绘图技术,我不得不谈到 GC (Graphics Context) 。 org.eclipse.swt.graphics.GC 封装了大量的绘图 API,其中包含常见的绘图技术,例如如何绘制不同厚度和不同样式的直线、如何绘制文本和各式各样的字体、图像以及如何填充图形。如果读者不是很熟悉这部分的内容,可以阅读参考文献中列举的相关资料自行学习。下面我将介绍一些重要的方法。
方法 setXORMode(true) 的作用是使得目标颜色是源和目标颜色进行异或操作 (XOR) 所得到的结果值。当参数为 false 时,则取消了绘图的异或效果。
在使用 setXORMode 之前,应该使用 setBackground/setForeground 等方法来设定背景色或者前景色。另外,我们不可以把 setAntialias 或者 setTransform 等 跟该方法一起使用,否则你会惊讶地发现使用 setXORMode 并没有达到你想要得效果。为什么呢?这是因为使用 setAntialias 或者 setTransform 等方法之后,高级图像模式被自动的调用,你可以使用 setAdvance(false) 将模式切换至普通模式之后再调用 setXORMode 方法。例如清单 5 。
清单 5. XOR 失效示例
gc.setBackground (display.getSystemColor (SWT.COLOR_BLUE)); gc.fillRectangle (0, 0, 60, 60); gc.setAntialias (SWT.OFF);// 调用了 setAntialias gc.setXORMode (true); gc.setBackground (display.getSystemColor (SWT.COLOR_YELLOW)); gc.fillRectangle (20, 20, 80, 80); gc.dispose (); |
值得注意的是, 在部分平台上并不支持 setXORMode,因此在开发具有可移植性的程序时, 要谨慎使用setXORMode。
渐进色在绘图过程中有着广泛的应用,能够使得图形或者界面具有很好的视觉效果。它主要分为上下梯度渐进色和左右梯度渐进色两种。 GC 就提供了方法 fillGradientRectangle 来实现这一功能,当其第 5 个参数 boolean vertical 为 true 时,将绘制上下渐进色;否则绘制左右渐进色。清单 6 举例说明如何使用 fillGradientRectangle, 图 2 显示了绘制的效果。
默认情况下,只能用此方法绘充有渐进色的矩形,如果要绘制其他图形,可以结合 GC 的其他方法一起来绘制。
清单 6. 渐进色示例
gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE)); gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE)); gc.fillGradientRectangle(0,0, 100, 100, true); |
图 2. 渐进色效果图

方法 setAlpha 是用来设置透明度,参数为透明度值。见清单 7,效果图如图 3 所示。
清单 7. 绘制透明文本
final Display display = new Display(); final Shell shell = new Shell(display); shell.setText(" 透明文本示例 "); final Font font = new Font(display, "Arial", 30, SWT.BOLD | SWT.ITALIC); final Image image = new Image(display, 450, 300); final Rectangle rect = image.getBounds(); GC gc = new GC(image); gc.dispose(); shell.addListener(SWT.Paint, new Listener() { public void handleEvent(Event event) { GC gc = event.gc; gc.drawImage(image,0,0, rect.width, rect.height,0,0, rect.width / 2, rect.height / 2); // 图像拷贝 gc.setAlpha(100); // 设置透明度 Path path = new Path(display); gc.setBackground(display.getSystemColor(SWT.COLOR_GREEN)); gc.setForeground(display.getSystemColor(SWT.COLOR_BLUE)); path.addString(" 透明字体 ", 0, 0, font); gc.fillPath(path); gc.drawPath(path); path.dispose(); } }); shell.setSize(shell.computeSize(rect.width / 2, rect.height / 2)); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } image.dispose(); font.dispose(); display.dispose(); |
图 3. 透明文本效果图

我们可以通过对图像的 ImageData 进行复制来完成图像的拷贝,除此之外, GC 提供了图像拷贝的 API 。方法 public void drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight) 允许用户拷贝源图像的任何一个方块。例如在清单 7 中,gc.drawImage(image, 0, 0, 100, 100, 0, 0, 100, 100) 是拷贝 image 左上角 100 × 100 方块。
GC 提供了方法 textExtent(String string) 来获取文本的宽度和长度。这个方法看似简单,但实际上我们应该注意同一个文本在不同的绘图模式下(具体见高级绘图模式部分)得到的文本范围并不尽相同。例如清单 8,普通模式下,笔者在 Windows XP 下实验得到的 size 是(22,13),而在高级绘图模式下得到的 size 是(23,13)。因此,在使用方法 textExtent 来计算文本大小的时候应该考虑到绘图模式的不同可能会导致得到不同的结果。
清单 8. 计算文本大小
GC gc = new GC (label); gc.setAdvanced(false/true); Point size = gc.textExtent ("hello"); gc.dispose (); |
在使用 SWT 进行绘图时,我们经常会用到画布 (org.eclipse.swt.Canvas) 。开发者既可以通过类 Canvas 来进行绘图,也可以通过它来自定义一个特制的 Control 。画布默认的行为是在其更新自己之前,首先填充整个绘图区域的背景色,而这可能导致用户在原背景和将要填充的背景之间看到闪烁。其中一种解决方法是让画布的样式设成 SWT.NO_BACKGROUND, 但是此时我们需要负责整个图形的绘制。
另外一种情况时,当我们变化其大小时,这也会触发重画事件。特定情况下可以设定 SWT.NO_REDRAW_RESIZE 样式来减少闪烁(例如仅仅缩小),但是并不是每一种情况下都适用。例如清单 9 所示。
清单 9. 用 SWT.NO_REDRAW_RESIZE 消除闪屏
shell.setLayout(new FillLayout()); final Canvas canvas = new Canvas(shell,SWT.NO_REDRAW_RESIZE); canvas.addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { Rectangle clientArea = canvas.getClientArea(); e.gc.setBackground(new Color(null, 255, 0, 0)); e.gc.fillArc(0, 0, clientArea.width,clientArea.height, 30, 30); } }); |
当你用鼠标拖着窗口的右下角进行缩放时,你会发现出现各种奇怪的现象。这是因为放大时如果使用 SWT.NO_REDRAW_RESIZE 样式将会导致部分区域更新不正常。
如何解决这些现象呢? 目前流行的方法是采取双缓冲区或者多缓冲区的方法。导致闪烁的原因是画布首先绘制其背景色,然后才绘制其他内容。双缓冲区的其中一种实现机制就是首先把背景色和其他内容保存在一幅图形当中,当收到重新绘图的时候,将整个图形设置到需要重画的区域,这样来达到消除闪烁的目的。清单 10 列举了这样的一种方法。
清单 10. 实现 DOUBLE BUFFERED
public class DoubleBufferSample { private static String text = "This is a sample about how to use double buffer to reduce flashing. "; private static int index = 0; private Canvas canvas; public static void main(String[] args) { final DoubleBufferSample inst = new DoubleBufferSample(); final Display display = new Display(); Shell shell = new Shell(display); inst.createContents(shell); shell.open(); Runnable runnable = new Runnable() { public void run() { display.timerExec(20, this); inst.canvas.redraw(); } }; display.timerExec(20, runnable); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.timerExec(-1, runnable); display.dispose(); } private void createContents(final Shell shell) { shell.setLayout(new FillLayout()); canvas = new Canvas(shell, SWT.COLOR_RED /*| SWT.DOUBLE_BUFFERED*/); canvas.addPaintListener(new PaintListener() { public void paintControl(PaintEvent event) { Image image = new Image(shell.getDisplay(), canvas.getBounds()); GC gc = new GC(image); gc.setBackground(event.gc.getBackground()); gc.fillRectangle(image.getBounds()); gc.setBackground(shell.getDisplay().getSystemColor(SWT.COLOR_WHITE)); if(index < text.length() - 1) index ++; else index = 0; gc.drawText(text.substring(0, index), 0, 0); event.gc.drawImage(image, 50, 50); image.dispose(); gc.dispose(); } }); } } |
还有一种更简单的方法, Eclipse 3.1 Release 之后,SWT 提供了样式 SWT.DOUBLE_BUFFERED,开发者是需要设置此样式就可以轻松的使用双缓冲区。在 Linux GTK 平台上,org.eclipse.swt.widgets.Canvas 默认已经支持 SWT.DOUBLE_BUFFERED 。代码见清单 11 。我们鼓励大家使用 SWT.DOUBLE_BUFFERED 而不是按照清单 10 所示那样自己手动去实现双 / 多缓冲区,在某些特定的情况下,我们需要综合很多方法才能真正意义上来消除闪屏。
清单 11. 设置 SWT.DOUBLE_BUFFERED 样式
canvas = new Canvas(shell, SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED); |
通常当一个窗口变大或者变小、隐藏或者显示,系统会发事件通知需要对当前窗口进行重画。 但是过于频繁的对窗口进行重绘,势必会影响应用程序的性能。鉴于此, SWT 是用了延迟更新策略。
尽管如此,我们不能保证延迟更新策略总是被采用。为了更大的控制和降低重绘的次数, SWT 允许开发者可以通过类 org.eclipse.swt.widgets.Control.中的方法 setRedraw 来决定接下来的重新绘图操作是否被忽略。 当参数为 false 时,接下来的重新绘图操作将被忽略,直到参数重新被设置成 true 时,系统不会相应任何重画事件。
- Eclipse 官方网站:包含大量关于 Eclipse、SWT 和 GEF/Draw2D 的信息和文章,适合大家系统的学习。
- SWT 全接触:简单的介绍了如何用 SWT 进行绘图,适合 SWT 初学者学习和阅读。
- SWT 图像处理入门:适合初学者学习如何用 SWT 进行图像处理。
- 查阅 IBM developerWorks 上的 Eclipse 项目资源,以提高您的 Eclipse 技能。
- 访问 developerWorks 开放源码专区,获得丰富的 how-to 信息、工具和项目更新,帮助您用开放源码技术进行开发,并与 IBM 产品结合使用。
- 通过 Joe Winchester 的 “Graphics Context -- Quick on the draw ”了解 SWT 的绘制能力。
- Chengdong Li 在文章 “A Basic Image Viewer ”中展示了另一种在 SWT 图像中使用二维变换的方式。
- 通过 Yannick Saillet 的教程“Migrate your Swing application to SWT”( developerWorks,2004 年 1 月)了解将 Swing 应用程序移植到 SWT/JFace 有多容易。
- 标准窗口小部件工具箱的 Java 二维作图:了解 SWT 的二维绘图技术。