SWT源码分析 (六)

时间:2024-05-20 17:28:46

第一篇文章中,有一个问题我没有解释,在Display中的runDeferedEvents方法中:

 

boolean runDeferredEvents () {
	boolean run = false;
	/*
	* Run deferred events.  This code is always
	* called in the Display's thread so it must
	* be re-enterant but need not be synchronized.
	*/
	while (eventQueue != null) {
		
		/* Take an event off the queue */
		Event event = eventQueue [0];
		if (event == null) break;
		int length = eventQueue.length;
		System.arraycopy (eventQueue, 1, eventQueue, 0, --length);
		eventQueue [length] = null;

		/* Run the event */
		Widget widget = event.widget;
		if (widget != null && !widget.isDisposed ()) {
			Widget item = event.item;
			if (item == null || !item.isDisposed ()) {
				run = true;
				widget.sendEvent (event);
			}
		}

		/*
		* At this point, the event queue could
		* be null due to a recursive invocation
		* when running the event.
		*/
	}

	/* Clear the queue */
	eventQueue = null;
	return run;
}

 事件event是从eventQueue中取出来的,eventQueue是Display中的一个Event数组:

 

Event [] eventQueue;

 我自然就会想到,系统中的各种事件,是怎么放入这个eventQueue中的呢?

继续从上文的按钮程序入手,现在监听按钮1的鼠标按下事件:

 

package com.edgar;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

class TestMultButton {
        public static void main(String[] args) {
                Display display = new Display();// 创建一个display对象。
                final Shell shell = new Shell(display);// shell是程序的主窗体
                shell.setText("Java应用程序"); // 设置主窗体的标题
                shell.setSize(300, 300); // 设置主窗体的大小
                
                Button b1 = new Button(shell,SWT.NONE);
                b1.setText("按钮1");
                b1.setBounds(100,50, 100, 50);
                Button b2 = new Button(shell,SWT.NONE);
                b2.setText("按钮2");
                b2.setBounds(100,150, 100, 50);     
                b1.addMouseListener(new MouseAdapter() {
					@Override
					public void mouseDown(MouseEvent e) {
						// TODO Auto-generated method stub
						System.out.println("mousedown");
					}
		
				});
                shell.open(); // 打开主窗体
                
                while (!shell.isDisposed()) { // 如果主窗体没有关闭则一直循环
                        if (!display.readAndDispatch()) { // 如果display不忙
                                display.sleep(); // 休眠
                        }
                }
                display.dispose(); // 销毁display
        }
}

 我在System.out.println("mousedown");这行代码加上断点,然后运行程序,然后点击按钮一,程序暂停,运行上下文如下:


SWT源码分析 (六)
 程序执行的流程和分析一中的一样,我们并没有看到上下文中看到windowProc,这是“意料之中”的,因为我们在利用SWT这个GUI类库编程,几乎搜有GUI类库都会对消息循环,窗口过程函数进行封装,让我们能以OO的方式来编程。现在为了知道SWT是怎么进行封装的,我找到Control类中的 windowProc 方法:

 

int /*long*/ windowProc (int /*long*/ hwnd, int msg, int /*long*/ wParam, int /*long*/ lParam) {
	LRESULT result = null;
	switch (msg) {
		......
		case OS.WM_KILLFOCUS:			result = WM_KILLFOCUS (wParam, lParam); break;
		case OS.WM_LBUTTONDBLCLK:		result = WM_LBUTTONDBLCLK (wParam, lParam); break;
		case OS.WM_LBUTTONDOWN:			result = WM_LBUTTONDOWN (wParam, lParam); break;
		case OS.WM_LBUTTONUP:			result = WM_LBUTTONUP (wParam, lParam); break;
		......
	}
	if (result != null) return result.value;
	return callWindowProc (hwnd, msg, wParam, lParam);
}

 对应左键点击按钮来说,windowProc中会接收到WM_LBUTTONDOWN消息,然后会调用WM_LBUTTONDOWN(wParam,lParam)函数,所以我在WM_LBUTTONDOWN函数加一个断点:


SWT源码分析 (六)
 运行程序,点击按钮一后,程序果然停在return wmLButtonDown (handle, wParam, lParam);这行!

 

现在我们就进入wmLButtonDown(handle,wParam,lParam)方法里面,看看都有什么操作:

 

LRESULT wmLButtonDown (int /*long*/ hwnd, int /*long*/ wParam, int /*long*/ lParam) {
	Display display = this.display;
	LRESULT result = null;
	int x = OS.GET_X_LPARAM (lParam);
	int y = OS.GET_Y_LPARAM (lParam);
	boolean [] consume = null, detect = null;
	boolean dragging = false, mouseDown = true;
	int count = display.getClickCount (SWT.MouseDown, 1, hwnd, lParam);
	if (count == 1 && (state & DRAG_DETECT) != 0 && hooks (SWT.DragDetect)) {
		if (!OS.IsWinCE) {
			/*
			* Feature in Windows.  It's possible that the drag
			* operation will not be started while the mouse is
			* down, meaning that the mouse should be captured.
			* This can happen when the user types the ESC key
			* to cancel the drag.  The fix is to query the state
			* of the mouse and capture the mouse accordingly.
			*/
			detect = new boolean [1];
			consume = new boolean [1];
			dragging = dragDetect (hwnd, x, y, true, detect, consume);
			if (isDisposed ()) return LRESULT.ZERO;
			mouseDown = OS.GetKeyState (OS.VK_LBUTTON) < 0;
		}
	}
	display.captureChanged = false;
	boolean dispatch = sendMouseEvent (SWT.MouseDown, 1, count, 0, false, hwnd, OS.WM_LBUTTONDOWN, wParam, lParam);
	if (dispatch && (consume == null || !consume [0])) {
		result = new LRESULT (callWindowProc (hwnd, OS.WM_LBUTTONDOWN, wParam, lParam));	
	} else {
		result = LRESULT.ZERO;
	}
	if (OS.IsPPC) {
		/*
		* Note: On WinCE PPC, only attempt to recognize the gesture for
		* a context menu when the control contains a valid menu or there
		* are listeners for the MenuDetect event.
		*/
		Menu menu = getMenu ();
		boolean hasMenu = menu != null && !menu.isDisposed ();
		if (hasMenu || hooks (SWT.MenuDetect)) {
			SHRGINFO shrg = new SHRGINFO ();
			shrg.cbSize = SHRGINFO.sizeof;
			shrg.hwndClient = hwnd;
			shrg.ptDown_x = x;
			shrg.ptDown_y = y; 
			shrg.dwFlags = OS.SHRG_RETURNCMD;
			int type = OS.SHRecognizeGesture (shrg);
			if (type == OS.GN_CONTEXTMENU) showMenu (x, y);
		}
	}
	if (mouseDown) {
		if (!display.captureChanged && !isDisposed ()) {
			if (OS.GetCapture () != hwnd) OS.SetCapture (hwnd);
		}
	}
	if (dragging) {
		sendDragEvent (1, x, y);
	} else {
		if (detect != null && detect [0]) {
			/*
			* Feature in Windows.  DragDetect() captures the mouse
			* and tracks its movement until the user releases the
			* left mouse button, presses the ESC key, or moves the
			* mouse outside the drag rectangle.  If the user moves
			* the mouse outside of the drag rectangle, DragDetect()
			* returns true and a drag and drop operation can be
			* started.  When the left mouse button is released or
			* the ESC key is pressed, these events are consumed by
			* DragDetect() so that application code that matches
			* mouse down/up pairs or looks for the ESC key will not
			* function properly.  The fix is to send the missing
			* events when the drag has not started.
			* 
			* NOTE: For now, don't send a fake WM_KEYDOWN/WM_KEYUP
			* events for the ESC key.  This would require computing
			* wParam (the key) and lParam (the repeat count, scan code,
			* extended-key flag, context code, previous key-state flag,
			* and transition-state flag) which is non-trivial.
			*/
			if (OS.GetKeyState (OS.VK_ESCAPE) >= 0) {
				OS.SendMessage (hwnd, OS.WM_LBUTTONUP, wParam, lParam);
			}
		}
	}
	return result;
}

 方法有点长,但是真正执行的地方并不长。一开始得到点击事件的x坐标和y坐标,然后得到点击的次数,然后调用sendMouseEvent方法:

 

boolean dispatch = sendMouseEvent (SWT.MouseDown, 1, count, 0, false, hwnd, OS.WM_LBUTTONDOWN, wParam, lParam);

 看方法的名字是“发送鼠标事件”进入该方法:

 

boolean sendMouseEvent (int type, int button, int count, int detail, boolean send, int /*long*/ hwnd, int msg, int /*long*/ wParam, int /*long*/ lParam) {
	if (!hooks (type) && !filters (type)) return true;
	Event event = new Event ();
	event.button = button;
	event.detail = detail;
	event.count = count;
	event.x = OS.GET_X_LPARAM (lParam);
	event.y = OS.GET_Y_LPARAM (lParam);
	setInputState (event, type);
	mapEvent (hwnd, event);
	if (send) {
		sendEvent (type, event);
		if (isDisposed ()) return false;
	} else {
		postEvent (type, event);
	}
	return event.doit;
}

 我们看到方法里面将系统的消息 “转换”成了SWT自己的事件对象。给event对象的属性赋值之后,因为send的值是false(看调用的代码和形参对应起来!),所以调用postEvent(type,event)方法:

 

void postEvent (int eventType, Event event) {
	sendEvent (eventType, event, false);
}

 又调用了sendEvent方法:

 

void sendEvent (int eventType, Event event, boolean send) {
	if (eventTable == null && !display.filters (eventType)) {
		return;
	}
	if (event == null) event = new Event ();
	event.type = eventType;
	event.display = display;
	event.widget = this;
	if (event.time == 0) {
		event.time = display.getLastEventTime ();
	}
	if (send) {
		sendEvent (event);
	} else {
		display.postEvent (event);
	}
}

 给属性赋值之后,因为send是false,继续调用display.postEvent(event):

 

void postEvent (Event event) {
	/*
	* Place the event at the end of the event queue.
	* This code is always called in the Display's
	* thread so it must be re-enterant but does not
	* need to be synchronized.
	*/
	if (eventQueue == null) eventQueue = new Event [4];
	int index = 0;
	int length = eventQueue.length;
	while (index < length) {
		if (eventQueue [index] == null) break;
		index++;
	}
	if (index == length) {
		Event [] newQueue = new Event [length + 4];
		System.arraycopy (eventQueue, 0, newQueue, 0, length);
		eventQueue = newQueue;
	}
	eventQueue [index] = event;
}

 postEvent方法,正是把此时的事件event存入了eventQueue。好了,文章一遗留的问题解决了。