系统事件转发
任何语言编写的应用程序,底层都是由操作系统来接受GUI事件,操作系统为每个程序独立分配一个GUI事件队列。当用户点击鼠标或者触摸屏幕,操作系统就会产生GUI事件,然后确定哪个应用程序的哪个窗口应当接受这个GUI事件,并将该GUI事件放入此应用程序的事件队列。
GUI应用程序的底层是一个event loop,应用程序初始化启动后便不停地从GUI事件队列中读取事件(Event),并作出响应。任何事件的处理,都必须很快地完成,以保证程序界面流畅地响应用户的操作。
需要长时间处理的UI事件,应当放在一个单独的非UI线程中执行,否则程序事件队列便进入等待状态,导致GUI停止刷新(感觉程序挂了)。
SWT的UI线程
SWT应用程序在主线程中运行一个event loop,该event loop与操作系统不停地转发事件。创建Display的线程是UI线程,界面元素都是在UI线程中创建的。
由于任何事件(event)都是由UI线程处理的,所以处理任何控件的响应时,我们可以随意访问UI中的任何控件及相关类。但是需要长时间运行的任务,会单独启动一个线程来执行,以便让程序继续响应用户。
注意:非UI线程访问UI线程时,会引发SWTException。
下列代码片段,创建一个主线程(GUI线程),包括一个event loop:
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 ();
}
当控件初始化完毕,shell打开后,程序便不停地从操作系统队列中读取事件和发送事件。如果系统队列中没有事件,display调用sleep()方法让出CPU资源给其它应用程序运行。
非UI线程访问UI线程
SWT提供了一个非常特殊的方法,让非UI线程访问UI线程中的控件和对象。使用Display中的syncExec(Runnable)和asyncExex(Runnable)方法,传入一个Runnable便可。
- syncExec(Runnable):当非UI线程需要用到Runnable的返回值时,使用该方法。SWT会阻塞住调用Display.getDefaut().syncExec(Runnable)的线程,直到Runnable执行完毕。例如,后台线程需要等待获得窗口大小后,再执行代码做相关计算。
- asyncExec(Runnable):当非UI线程的执行不依赖于Runnable中执行的结果时,使用该方法。例如,后台线程需要更新下进度条状态,然后便可继续执行,并不需要等待Runnable执行完毕。
// 执行非常耗时的计算
...
// 更新UI,不依赖Runnable结果,选用async。
display.asyncExec (new Runnable () {
public void run () {
if (!myWindow.isDisposed())
myWindow.redraw ();
}
});
// 执行其它的计算
...
在访问UI线程的时候,最好检查下使用到的widget是否被dispose掉了。
- 使用syncExec(Runnable)时,在调用syncExec之前检查widget状态便可。若widget没有被dispose,便可调用执行syncExec(Runnable)。
- 使用asyncExec(Runnable)时,应当在Runnable内部检查widget状态。因为是异步执行的,纵然执行asyncExec前已经检验过widget没有dispose掉,此时依然无法保证该widget是否可用,有可能被已经被其它线程销毁了。