SWT/JFace专题 --- SWT中Display和多线程

Display

1.Display 的创建

一个SWT程序至少需要一个Display对象,创建Display对象的线程被称为UI线程,一个UI线程只有唯一的display对象(如果需要,可以在其他线程中创建)。
Display.getDefault()方法首次调用时会创建Display实例,随后再次调用这个方法可获取已创建的实例。
Display.getCurrent()方法可以获得当前线程对应的Display实例;
Display.findDisplay(Thread)则可以找到线程对应的Display对象;
在并发多线程的程序中推荐如下的代码来管理Display实例:
public static Display getThreadDisplay(){
 return Display.getCurrent()==null?new Display():Display.getCurrent();
}

2.Display的事件循环队列和事件循环

创建一个窗口后并打开后,就进入了事件循环部分(Event Loop)
while(!shell.isDisposed()){
  if(!display.readAndDispatch()){
    display.sleep();
  }
}
常见的图形操作系统会为每一个GUI程序分配一个事件队列,用户操作鼠标和键盘时,操作系统负责将这些事件放到对应程序的事件队列中。但同时,程序可能希望在事件循环中处理一些自定义事件。为此,Display额外维护了一个应用程序级别的事件队列,自定义的事件可以被添加到这个队列中。在Display的事件循环中,同时处理着系统队列和这个自定义队列中的事件。
下面看一下readAndDispatch()的实现:
public boolean readAndDispatch () {
	checkDevice ();
	lpStartupInfo = null;
	drawMenuBars ();
	runSkin ();
	runDeferredLayouts ();
	runPopups ();
	if (OS.PeekMessage (msg, 0, 0, 0, OS.PM_REMOVE)) {//1.从事件队列中取出消息事件
		if (!filterMessage (msg)) {                  //2.对消息事件过滤
			OS.TranslateMessage (msg);              //3.翻译msg
			OS.DispatchMessage (msg);               //4.发送到窗口处理
		}
         //5.runDeferredEvents()和runAsyncMessages()处理自定义队列中的事件 
		runDeferredEvents (); 
		return true;
	}
	return isDisposed () || (runMessages && runAsyncMessages (false));
}
这里调用了很多org.eclipse.swt.internal.win32.OS类的方法。OS是SWT用来包装操作系统API的类型,它的方法都是JNI接口,而且与操作系统的API一一对应。
其中调用OS类的PeekMessage()方法,从系统队列中取出一条消息
实现如下:
public static final boolean PeekMessage (MSG lpMsg, int /*long*/ hWnd, int wMsgFilterMin, int wMsgFilterMax, int wRemoveMsg) {
	if (IsUnicode) return PeekMessageW (lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
	return PeekMessageA (lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
}
调用PeekMessageW (),PeekMessageA():就是native声明的JNI接口
public static final native boolean PeekMessageW (MSG lpMsg, int /*long*/ hWnd, int wMsgFilterMin, int wMsgFilterMax, int wRemoveMsg);
public static final native boolean PeekMessageA (MSG lpMsg, int /*long*/ hWnd, int wMsgFilterMin, int wMsgFilterMax, int wRemoveMsg);

在调用系统API转发消息之前,SWT还使用filterMessage方法对消息事件做了过滤。在通常情况下,发送到窗口中控件的事件都会交由窗口的消息处理函数统一处理。如果事件是发送到某一个特定控件(如按钮,菜单等),这个方法允许过滤掉这个事件而不发送到主窗口。如果在菜单和他的窗口中为同一个快捷键设置了不同的功能,打开菜单式,快捷键消息就会被菜单捕捉下来不发送到窗口。
SWT的事件循环中,方法runDeferredEvents()和runAsyncMessages()负责处理Display的自定义事件队列中的事件。
boolean runDeferredEvents () {
	boolean run = false;
	/*
	* eventQueue就是Display维护的事件循环队列,由于在一个UI线程中只存在一个Display对象,
     *只有一个display可以操作这个eventQueue,所以这里不需要做同步处理。
	*/
	while (eventQueue != null) {
		/* 取出队列中的event事件 */
		Event event = eventQueue [0];
		if (event == null) break;
		int length = eventQueue.length;
		System.arraycopy (eventQueue, 1, eventQueue, 0, --length);
		eventQueue [length] = null;
		/* 把event事件发到对应的控件去处理 */
		Widget widget = event.widget;
		if (widget != null && !widget.isDisposed ()) {
			Widget item = event.item;
			if (item == null || !item.isDisposed ()) {
				run = true;
				widget.sendEvent (event);
			}
		}
	}
	/* 设为null,便于jvm及时回收对象 */
	eventQueue = null;
	return run;
}

/* Synchronizer 是Display中实现非ui线程和ui线程同步操作的关键实现类(下文有该类的详细说明)*/
Synchronizer synchronizer = new Synchronizer (this);

boolean runAsyncMessages (boolean all) {
	return synchronizer.runAsyncMessages (all);
}

现在可以整理出Display.readAndDispathch的流程:
首先冲系统事件队列中读取消息,如果在程序的事件队列中读到事件,就将它发送到窗口去处理;如果在线程交互的事件队列中有需要执行的事件,就去执行它。如果readAndDispatch
返回false,事件循环就会调用Display的sleep方法。sleep方法调用了OS.WaitMessage,
WaitMessage会使当前线程(UI线程)休眠,这样就可以将处理器交给别的线程使用。当事件队列中有新的事件传来时,UI线程会被唤醒并恢复事件循环过程。
public boolean sleep () {
	checkDevice ();
	if (runMessages && getMessageCount () != 0) return true;
	if (OS.IsWinCE) {
		OS.MsgWaitForMultipleObjectsEx (0, 0, OS.INFINITE, OS.QS_ALLINPUT, 
                                                          OS.MWMO_INPUTAVAILABLE);
		return true;
	}
	return OS.WaitMessage ();
}

3.监视器(Monitor)、边界(Bounds)和客户区域(ClientArea)
使用Display.getMonitor()方法可以取得与这个Display相连的所有监视器的信息,而Display.getPrimaryMonitor()可以得到主监视器对象。监视器包括两个部分:Bounds和ClientArea。
边界代表了这个监视器的屏幕的大小,而客户区域则代表可以用来显示窗口的区域的大小。
Monitor monitor= disp.getPrimaryMonitor();
monitor.getClientArea();
monitor.getBounds();

4.SWT程序中的多线程
SWT采用单线程模型管理绘图操作,只有UI线程才能进行控件重绘和处理事件循环等直接访问Display的操作,非UI线程试图直接操作Display会抛出一个SWT异常。

Display维护着一个自定义的事件队列,这个队列就是用来供后台线程和UI线程同步的。后台线程用Runnable对象将绘图操作包装起来,然后将对象插入到事件队列中,这样Display执行信息循环时就会执行这些操作。
Display提供了如下两个方法向队列中插入事件:

1)Display.syncExec(Runnable runnable)

同步调用。调用这个方法会通知UI线程在下一次事件循环时执行runnable参数的run方法。调用这个方法的线程将被阻塞到runnable执行完为止。如果参数是null,调用这个方法会唤醒休眠中的UI线程。
public void syncExec (Runnable runnable) {
	Synchronizer synchronizer;
	synchronized (Device.class) {
		if (isDisposed ()) error (SWT.ERROR_DEVICE_DISPOSED);
		synchronizer = this.synchronizer;
	}
	synchronizer.syncExec (runnable);
}

2)Display.asyncExec(Runnable runnable)

异步调用。调用这个方法同样通知UI线程在下一次事件循环时执行runnable参数的run方法。调用这个方法的线程不会阻塞,而且在runnable执行完成后不会得到通知。如果参数是null,调用这个方法会唤醒休眠中的UI线程。
public void asyncExec (Runnable runnable) {
	synchronized (Device.class) {
		if (isDisposed ()) error (SWT.ERROR_DEVICE_DISPOSED);
		synchronizer.asyncExec (runnable);
	}
}

可以看出syncExec/asyncExec 都是通过Synchronizer 这个类实现的.
下面看一下
/**
 * Synchronizer 为Display的消息队列的处理提供了同步的支持
 */
public class Synchronizer {
	Display display;//需要控制的display对象
	int messageCount;//当前消息数量
	RunnableLock [] messages;//Runnable对象数组,存放着要处理的消息事件
	Object messageLock = new Object ();//同步时的一个参照对象(一个事件只能被一个线程拥有)
	Thread syncThread;//在runAsyncMessages()异步消息处理时会用到(没看明白它有什么作用)
	static final int GROW_SIZE = 4;//messages数组的增量
	static final int MESSAGE_LIMIT = 64;//messages数组的最大容量

	//TEMPORARY CODE
	static final boolean IS_CARBON = "carbon".equals (SWT.getPlatform ());
	static final boolean IS_COCOA = "cocoa".equals (SWT.getPlatform ());
	static final boolean IS_GTK = "gtk".equals (SWT.getPlatform ());

/**
 * Constructs a new instance of this class.
 */
public Synchronizer (Display display) {
	this.display = display;
}
/**
 * 把runnable消息对象添加到messages数组中,等待UI线程处理
 */
void addLast (RunnableLock lock) {
	boolean wake = false;
	synchronized (messageLock) {
		if (messages == null) messages = new RunnableLock [GROW_SIZE];//初始容量为GROW_SIZE
		if (messageCount == messages.length) {//当message个数和数组长度相同时,就扩容
			RunnableLock[] newMessages = new RunnableLock [messageCount + GROW_SIZE];
			System.arraycopy (messages, 0, newMessages, 0, messageCount);
			messages = newMessages;
		}
		messages [messageCount++] = lock;//添加runnable对象
         //当message对象从无到有时,也就是刚刚有一个进来,就该唤醒UI线程了
         //如果大于1个,所有UI线程已经在活动状态,就无效唤醒了
		wake = messageCount == 1;
	}	
	if (wake) display.wakeThread ();
}

/**
 * 
 *异步调用。调用这个方法同样通知UI线程在下一次事件循环时执行runnable参数的run
 *方法。调用这个方法的线程不会阻塞,而且在runnable执行完成后不会得到通知。如果参 
 * 数是null,调用这个方法会唤醒休眠中的UI线程。
 */
protected void asyncExec (Runnable runnable) {
	if (runnable == null) {
		//TEMPORARY CODE
		if (!(IS_CARBON || IS_GTK || IS_COCOA)) {
			display.wake ();
			return;
		}
	}
	addLast (new RunnableLock (runnable));
}

int getMessageCount () {
	synchronized (messageLock) {
		return messageCount;
	}
}

void releaseSynchronizer () {
	display = null;
	messages = null;
	messageLock = null;
	syncThread = null;
}

RunnableLock removeFirst () {
	synchronized (messageLock) {
		if (messageCount == 0) return null;
		RunnableLock lock = messages [0];
		System.arraycopy (messages, 1, messages, 0, --messageCount);
		messages [messageCount] = null;
		if (messageCount == 0) {
			if (messages.length > MESSAGE_LIMIT) messages = null;
		}
		return lock;
	}
}

boolean runAsyncMessages () {
	return runAsyncMessages (false);
}
/**
 *运行message数组中runnable对象的run方法对消息事件进行异步处理
 */
boolean runAsyncMessages (boolean all) {
	boolean run = false;
	do {
		RunnableLock lock = removeFirst ();
		if (lock == null) return run;
		run = true;
		synchronized (lock) {//同步runnable对象,表示该runnable对象仅能被一个线程处理
			syncThread = lock.thread;
			try {
				lock.run ();
			} catch (Throwable t) {
				lock.throwable = t;
				SWT.error (SWT.ERROR_FAILED_EXEC, t);
			} finally {
				syncThread = null;
                   lock.notifyAll ();
			}
		}
	} while (all);
	return run;
}

/**
 *同步调用。调用这个方法会通知UI线程在下一次事件循环时执行runnable参数的run方
 *法。调用这个方法的线程将被阻塞到runnable执行完为止。如果参数是null,调用这个
 *方法会唤醒休眠中的UI线程。
 */
protected void syncExec (Runnable runnable) {
	RunnableLock lock = null;
	synchronized (Device.class) {
		if (display == null || display.isDisposed ()) SWT.error (SWT.ERROR_DEVICE_DISPOSED);
		if (!display.isValidThread ()) {
			if (runnable == null) {
				display.wake ();
				return;
			}
			lock = new RunnableLock (runnable);
			/*
			 * Only remember the syncThread for syncExec.
			 */
			lock.thread = Thread.currentThread();
			addLast (lock);
		}
	}
	if (lock == null) {
		if (runnable != null) runnable.run ();
		return;
	}
	synchronized (lock) {
		boolean interrupted = false;
		while (!lock.done ()) {
			try {
				lock.wait ();
			} catch (InterruptedException e) {
				interrupted = true;
			}
		}
		if (interrupted) {
			Compatibility.interrupt();
		}
		if (lock.throwable != null) {
			SWT.error (SWT.ERROR_FAILED_EXEC, lock.throwable);
		}
	}
}

}
其中RunnableLock类的实现
/**
 * Instances of this class are used to ensure that an
 * application cannot interfere with the locking mechanism
 * used to implement asynchronous and synchronous communication
 * between widgets and background threads.
 */
class RunnableLock {
	Runnable runnable;
	Thread thread;
	Throwable throwable;
	
	RunnableLock (Runnable runnable) {
		this.runnable = runnable;
	}
	boolean done () {
		return runnable == null || throwable != null;
	}
	void run () {
		if (runnable != null) runnable.run ();
		runnable = null;
	}
}

----------
5.其他
---------------
使用系统托盘

SWT允许Java程序像本地程序一样直接访问系统托盘。在SWT中,系统托盘资源由Tray类型管理,调用Display.getSystemTray方法,可以得到唯一的Tray实例并操作它,如果所在的平台上没有系统托盘,就返回null。

得到Tray后,就可以创建TrayItem向系统托盘添加一个Item,并设置图标和提示信息,例子代码如下:
public static void main(String[] args){
   Display display=Display.getDefault();
   Shell shell= new Shell(display,SWT.NO_TRIM);//SWT.NO_TRIM表示不带边框
   shell.setSize(120,80);
   shell.open();

   Tray systemTray=display.getSystemTray();
   TrayItem newItem=new TrayItem(systemTray,SWT.NONE);
   newItem.setImage(...);
   newItem.setToolTipText("Text Tray!");

    while(!shell.isDisposed()){
       if(!display.readAndDispatch()){
          display.sleep();
       }
    }
    newItem.dispose();
    display.dispose();

}
-----------
用Region构造不规则窗口

Region类型代表的是平面坐标系上的由任意个多边形组成的一个区域。
... ...
Region region=new Region(display);
region.add(new Rectangle(10,10,10,100));
region.add(new Rectangle(10,100,10,10));
region.add(new Rectangle(10,10,100,10));
region.add(new Rectangle(100,10,10,100));
... ...

你可能感兴趣的:(display)