第一篇文章中,有一个问题我没有解释,在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");这行代码加上断点,然后运行程序,然后点击按钮一,程序暂停,运行上下文如下:
程序执行的流程和分析一中的一样,我们并没有看到上下文中看到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函数加一个断点:
运行程序,点击按钮一后,程序果然停在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。好了,文章一遗留的问题解决了。