从市场上看,Eclipse的成功应该归功于开放源代码,从架构上看,Eclipse成功在于其插件机制,给了开发人员一个平台的框架,但对于用户,最直接的成功还是在于它的界面,特别是自Eclipse3.0以后,采用OSGI核心,加上对RCP的支持,这都使得Eclipse获取了更多开发人员的芳心,同时Eclipse社区也吸引了大量的商业公司和开发人员为之开发插件。
但是开发插件并不是一件简单的事情,除了基本的学习以外,SWT的使用也远不象许多开发人员想像的那样,象最著名的"Invalid Thread Access",相信让许多开发人员为之头疼,难以理解如何才能正确的使用SWT控件,接下来的章节,将会对SWT控件的使用加以说明。
操作系统对GUI的约束:
SWT与Java自带的界面Swing相比,最大的区别在于使用JNI调用本地操作系统的API进行控件处理,因此在开始对SWT的使用进行讲解前,先看一下操作系统对GUI操作的各种约束。
事实上所有的GUI都是在基于操作系统进行各种工作,尽管在不同的操作系统平台上机制稍微有些不同,但是基础是相似的。当用户单击鼠标、输入字符或者对窗口的外观进行处理时,操作系统将生成应用程序 GUI 事件,例如,鼠标单击、击键或者窗口绘制事件。它确定哪个窗口和应用程序应当接收每个事件,并将它放置在应用程序的事件队列中。基本流程如下所示:
- 事件(鼠标、键盘、程序API的调用)-->操作系统接收-->查找接收该事件的应用程序-->将事件放入应用程序的事件队列-->应用程序循环遍历事件队列,并进行处理-->绘制UI界面
因此任何窗口化的 GUI 应用程序的底层结构都是事件循环。应用程序进行初始化,然后启动循环,它只从队列中阅读 GUI 事件,并相应地作出反应。在处理其中一个事件时完成的任何工作必须快速地进行,以便让 GUI 系统可以响应用户。因此支持UI界面的程序一般都是多线程程序,事件处理是一个单独的线程,甚至是主线程,用来处理各种事件,如事件队列的管理,而通常还会有一个单独线程,它用来执行由 UI 事件触发的长时间操作,这样可以避免单线程因为UI操作过长而无法响应用户请求的不良情况。但是,必须利用显式锁定和序列化来控制从其他线程访问窗口小部件和平台 API。未能遵循规则的应用程序可能会导致操作系统调用失败。
在任何操作平台的GUI系统中,对构件或一些图形API的访问操作都要被严格同步并串行化。例如,在一个图形界面中的按键构件可被设成可用状态(enable)或禁用状态(disable),正常的处理方式是,用户对按键状态设置操作都要被放入到GUI系统的事件处理队列中(这意味着访问操作被串行化),然后依次处理(这意味着访问操作被同步)。想象当按键可用状态的设置函数还没有执行结束的时候,程序就希望再设置该按键为禁用状态,势必会引起冲突。实际上,这种操作在任何GUI系统都会触发异常。
因此在任何平台上编写UI程序都不是一件简单的事情,必须正确使用线程对UI进行各种操作,才能保证你的程序正常运行。
SWT对线程的支持
Java语言本身就提供了多线程机制,因此用户可以在任意一个线程中来操作UI界面,这种机制对GUI编程来说是不利的,它不能保证图形构件操作的同步与串行化。SWT采用了一种简单而直接的方式去适应本地GUI系统对线程的要求:在SWT中,通常存在一个被称为"用户线程"的唯一线程,只有在这个线程中才能调用对构件或某些图形API的访问操作。如果在非用户线程中程序直接调用这些访问操作,那么SWTExcepiton异常会被抛出。这个UI主线程就是下面要谈到的org.eclipse.swt.widgets.Display类。
UI主线程
在SWT中,有一个特殊的类,它就是org.eclipse.swt.widgets.Display,Eclipse建议每一个SWT程序,应该只有一个Display的实例,统一处理UI事件。如果用户有特殊的需要,可以在某些操作系统中,创建新的Display对象,但在一个线程中,它只会存在一个,直到它被dispose。下面的代码演示了一个Display对象的创建:
- public static void main (String [] args) {
- Display display = new Display ();
- Shell shell = new Shell (display);
- shell.open ();
- // start the event loop. We stop when the user has done
- // something to dispose our window.
- while (!shell.isDisposed ()) {
- if (!display.readAndDispatch ())
- display.sleep ();
- }
- display.dispose ();
- }
在创建Display对象以后,再创建一个主窗口,请注意,Display是个主线程,它本身并不会有窗口,它只是负责管理窗口,所以还需要通过Shell来创建窗口,这也是一个Eclipse会有许多窗口,包括对话框,都只有一个Display的原因,所以Display不等于Window。在创建了Shell以后,display就可以通过readAndDispatch方法进行事件的处理。
SWT在org.eclipse.swt.widgets.Display类中提供了两个方法可以间接的在非用户线程的进行图形构件的访问操作,这是通过的syncExec(Runnable)和asyncExec(Runnable)这两个方法去实现的,前者用来在UI主线程中执行UI的各种操作,如添加SWT控件,刷新控件,处理图片等一系列操作,而后者则是在UI主线程以外的线程中来处理UI操作。因此这两者在执行时也有着本质的区别,前者与UI主线程在一个线程中运行,要在指定的线程执行结束后才返回,而后者则无论指定的线程是否执行都会立即返回到当前线程,UI操作会在主线程中由SWT自行管理,在合适的时候进行操作。
在UI主线程中进行UI操作
- /**
- * {@inheritDoc}
- */
- protected Control createCustomArea(Composite parent) {
- this.table = new Table(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
- this.table.setLinesVisible(true);
- for (int i = 0; i < this.resources.length; i++) {
- IResource resource = this.resources[i];
- TableItem tableItem = new TableItem(this.table, SWT.NONE);
- tableItem.setText(resource.getFullPath().toString());
- }
- //使用表格列出要删除的资源
- return this.table;
- }
- //继承了一个MessageDialog,并在Dialog上提供一个表格
- //因为Dialog的open方法的执行代码如下
- //它正确的使用了syncExec方法,会在UI主线程中执行代码
- PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
- /* (non-Javadoc)
- * @see java.lang.Runnable#run()
- */
- public void run() {
- //Reset the watch if it is not safe to open
- if (!ProgressManagerUtil.safeToOpen(ProgressMonitorJobsDialog.this,null)){
- watchTicks();
- return;
- }
- if (!alreadyClosed) {
- open();
- }
- }
- });
在UI主线程外进行UI操作
- Display.getCurrent().asyncExec(new Runnable() {
- public void run() {
- Button butt = new Button(panel,SWT.PUSH);
- butt.setText("Push");
- }
- });
- //此时程序运行在一个非用户线程中,并且希望在构件panel上加入一个按键。