edgar108 发表于 2013-1-28 18:13:38

SWT源码分析 (六)

第一篇文章中,有一个问题我没有解释,在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 ;if (event == null) break;int length = eventQueue.length;System.arraycopy (eventQueue, 1, eventQueue, 0, --length);eventQueue = 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() {@Overridepublic void mouseDown(MouseEvent e) {// TODO Auto-generated method stubSystem.out.println("mousedown");}});                shell.open(); // 打开主窗体                              while (!shell.isDisposed()) { // 如果主窗体没有关闭则一直循环                        if (!display.readAndDispatch()) { // 如果display不忙                              display.sleep(); // 休眠                        }                }                display.dispose(); // 销毁display      }} 我在System.out.println("mousedown");这行代码加上断点,然后运行程序,然后点击按钮一,程序暂停,运行上下文如下:

http://dl.iteye.com/upload/attachment/424716/c5c8e2c0-899e-3df9-85c5-c7f8ad0f2157.png
 程序执行的流程和分析一中的一样,我们并没有看到上下文中看到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函数加一个断点:

http://dl.iteye.com/upload/attachment/424722/2d766406-6898-36f7-ad5b-bfafe111c30a.png
 运行程序,点击按钮一后,程序果然停在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 ;consume = new boolean ;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 )) {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 ) {/** 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 ;int index = 0;int length = eventQueue.length;while (index < length) {if (eventQueue == null) break;index++;}if (index == length) {Event [] newQueue = new Event ;System.arraycopy (eventQueue, 0, newQueue, 0, length);eventQueue = newQueue;}eventQueue = event;} postEvent方法,正是把此时的事件event存入了eventQueue。好了,文章一遗留的问题解决了。
页: [1]
查看完整版本: SWT源码分析 (六)