先看程序:
package com.edgar; import org.eclipse.swt.SWT; 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); shell.open(); // 打开主窗体 while (!shell.isDisposed()) { // 如果主窗体没有关闭则一直循环 if (!display.readAndDispatch()) { // 如果display不忙 display.sleep(); // 休眠 } } display.dispose(); // 销毁display } }
窗口中有2个按钮,运行效果如下:
从前面文章我们已经知道,在执行final Shell shell = new Shell(display);这行代码时,会调用Control类中的
createWidget()方法:
void createWidget () { state |= DRAG_DETECT; foreground = background = -1; checkOrientation (parent); createHandle (); checkBackground (); checkBuffered (); checkComposited (); register (); subclass (); setDefaultFont (); checkMirrored (); checkBorder (); if ((state & PARENT_BACKGROUND) != 0) { setBackground (); } }
可以看到,中间调用了一个register()方法:
void register () { display.addControl (handle, this); }
意思是把当前控件(this),加到了display对象中,handle是什么?handle中文是句柄的意思,句柄是某个资源在操作系统(Windows)中的一个标识符,相当于该资源的代号。handle是Control类中的一个变量,所以shell,button等子类也有该变量。addControl方法的代码如下:
void addControl (int /*long*/ handle, Control control) { if (handle == 0) return; if (freeSlot == -1) { int length = (freeSlot = indexTable.length) + GROW_SIZE; int [] newIndexTable = new int [length]; Control [] newControlTable = new Control [length]; System.arraycopy (indexTable, 0, newIndexTable, 0, freeSlot); System.arraycopy (controlTable, 0, newControlTable, 0, freeSlot); for (int i=freeSlot; i<length-1; i++) newIndexTable [i] = i + 1; newIndexTable [length - 1] = -1; indexTable = newIndexTable; controlTable = newControlTable; } if (USE_PROPERTY) { OS.SetProp (handle, SWT_OBJECT_INDEX, freeSlot + 1); } else { OS.SetWindowLongPtr (handle, OS.GWLP_USERDATA, freeSlot + 1); } int oldSlot = freeSlot; freeSlot = indexTable [oldSlot]; indexTable [oldSlot] = -2; controlTable [oldSlot] = control; }
里面用到了Display中的一些变量:
Control [] controlTable; int freeSlot; static final boolean USE_PROPERTY = !OS.IsWinCE; static { if (USE_PROPERTY) { SWT_OBJECT_INDEX = OS.GlobalAddAtom (new TCHAR (0, "SWT_OBJECT_INDEX", true)); //$NON-NLS-1$ } else { SWT_OBJECT_INDEX = 0; } }
可见,display对象,保存着程序中所有控件(Control)的一个数组,还有一个USE_PROPERTY变量来标识当前系统是不是WINCE系统,对于普通机器来说,不是WINCE系统,所以USE_PROPERTY为true。SWT_OBJECT_INDEX 被赋予了一个值。这个值是什么,我们不关心。
回到addControl方法,前面是一些判断,数组的扩充操作,这里不关心。因为USE_PROPERTY为true。所以执行
OS.SetProp()方法,这个方法的作用就是为某个handle的某个属性赋值。在这里是把SWT_OBJECT_INDEX属性的值设置为freeSlot +1,freeSlot初始值为0,第一次SetProp时SWT_OBJECT_INDEX的属性
为1.可以把OS.SetProp(),理解为Java web中的request.setAttribute("xxxx",xxxx);给一个变量上绑定一些属性。
后面有 controlTable [oldSlot] = control;即把本次要“注册”的控件加入到控件数组中,第一个控件的下标为0 。
以此类推, Button b1 = new Button(shell,SWT.NONE); Button b2 = new Button(shell,SWT.NONE);
会把按钮b1,b2也放入display对象的controlTable 数组中,下标分别为1,2。(Button的构造方法中最终也会调用createWidget()方法
,在这就不贴代码了)。
通过以上观察,可以得出:程序中的每一个控件,都会在display的controlTable 数组中保存着。
在之前的文章中,我们还没有看到windows程序中的“窗口过程函数”,窗口过程函数是C程序中处理窗口消息的地方。前面说过,窗口过程函数是一个回调函数,是由操作系统调用的,对于SWT程序来说,情况就变成了操作系统(C程序)调用SWT(Java程序)。如何在C中调用Java,这又牵扯到了JNI。
前面提到过,创建Display对象时,会调用Display中的init()方法:
protected void init () { super.init (); ...... /* Create the callbacks */ windowCallback = new Callback (this, "windowProc", 4); //$NON-NLS-1$ windowProc = windowCallback.getAddress (); if (windowProc == 0) error (SWT.ERROR_NO_MORE_CALLBACKS); /* Remember the current thread id */ threadId = OS.GetCurrentThreadId (); /* Use the character encoding for the default locale */ windowClass = new TCHAR (0, WindowName + WindowClassCount, true); windowShadowClass = new TCHAR (0, WindowShadowName + WindowClassCount, true); windowOwnDCClass = new TCHAR (0, WindowOwnDCName + WindowClassCount, true); WindowClassCount++; /* Register the SWT window class */ int /*long*/ hHeap = OS.GetProcessHeap (); int /*long*/ hInstance = OS.GetModuleHandle (null); WNDCLASS lpWndClass = new WNDCLASS (); lpWndClass.hInstance = hInstance; lpWndClass.lpfnWndProc = windowProc; lpWndClass.style = OS.CS_BYTEALIGNWINDOW | OS.CS_DBLCLKS; lpWndClass.hCursor = OS.LoadCursor (0, OS.IDC_ARROW); lpWndClass.lpszClassName = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount); OS.MoveMemory (lpWndClass.lpszClassName, windowOwnDCClass, byteCount); OS.RegisterClass (lpWndClass); OS.HeapFree (hHeap, 0, lpWndClass.lpszClassName); /* Create the message only HWND */ hwndMessage = OS.CreateWindowEx (0, windowClass, null, OS.WS_OVERLAPPED, 0, 0, 0, 0, 0, 0, hInstance, null); String title = "SWT_Window_"+APP_NAME; OS.SetWindowText(hwndMessage, new TCHAR(0, title, true)); messageCallback = new Callback (this, "messageProc", 4); //$NON-NLS-1$ messageProc = messageCallback.getAddress (); if (messageProc == 0) error (SWT.ERROR_NO_MORE_CALLBACKS); OS.SetWindowLongPtr (hwndMessage, OS.GWLP_WNDPROC, messageProc); ...... }
这个方法比较长,重点看:
/* Create the callbacks */ windowCallback = new Callback (this, "windowProc", 4); //$NON-NLS-1$ windowProc = windowCallback.getAddress (); ....... WNDCLASS lpWndClass = new WNDCLASS (); lpWndClass.hInstance = hInstance; lpWndClass.lpfnWndProc = windowProc; ......
这里创建了一个Callback对象,"Callback"就是回调的意思。windowProc是Callback对象的地址,然后把windowProc赋值给了lpWndClass.lpfnWndProc ,在C中,lpWndClass.lpfnWndProc存放的就是窗口过程函数的地址(函数指针).
通过RegisterClass函数注册lpWndClass后,操作系统就知道该窗口的窗口过程函数了。
可见,在SWT中是通过Callback对象来模拟窗口过程函数的地址,是C和Java之间的“桥梁”。
如果进入Callback最后调用的构造函数:
public Callback (Object object, String method, int argCount, boolean isArrayBased, int /*long*/ errorResult) { /* Set the callback fields */ this.object = object; this.method = method; this.argCount = argCount; this.isStatic = object instanceof Class; this.isArrayBased = isArrayBased; this.errorResult = errorResult; /* Inline the common cases */ if (isArrayBased) { signature = SIGNATURE_N; } else { switch (argCount) { case 0: signature = SIGNATURE_0; break; //$NON-NLS-1$ case 1: signature = SIGNATURE_1; break; //$NON-NLS-1$ case 2: signature = SIGNATURE_2; break; //$NON-NLS-1$ case 3: signature = SIGNATURE_3; break; //$NON-NLS-1$ case 4: signature = SIGNATURE_4; break; //$NON-NLS-1$ default: signature = getSignature(argCount); } } /* Bind the address */ address = bind (this, object, method, signature, argCount, isStatic, isArrayBased, errorResult); }
前面是一些赋值,最后有一个bind函数,并且是一个native函数:
static native synchronized int /*long*/ bind (Callback callback, Object object, String method, String signature, int argCount, boolean isStatic, boolean isArrayBased, int /*long*/ errorResult);
去callback.c中看具体实现:
JNIEXPORT jintLong JNICALL Java_org_eclipse_swt_internal_Callback_bind (JNIEnv *env, jclass that, jobject callbackObject, jobject object, jstring method, jstring signature, jint argCount, jboolean isStatic, jboolean isArrayBased, jintLong errorResult) { int i; jmethodID mid = NULL; jclass javaClass = that; const char *methodString = NULL, *sigString = NULL; if (jvm == NULL) (*env)->GetJavaVM(env, &jvm); if (JNI_VERSION == 0) JNI_VERSION = (*env)->GetVersion(env); if (!initialized) { memset(&callbackData, 0, sizeof(callbackData)); initialized = 1; } ...... }
这些都是什么啊?看不懂。。。。上网查了一下,很多都是C通过JNI调用Java的步骤。具体的我也不了解了。有兴趣的可以查一下。
我从网上找了一篇文章,也是讲SWT实现的,里面对这个Callback做了一些介绍:http://wenku.baidu.com/view/acbbf9b069dc5022aaea0044.html
既然没法从Callback内部实现继续分析了,就只能从代码中找那个窗口过程函数了。在Display中发现了一个windowProc(int /*long*/ hwnd, int /*long*/ msg, int /*long*/ wParam, int /*long*/ lParam)方法:
int /*long*/ windowProc (int /*long*/ hwnd, int /*long*/ msg, int /*long*/ wParam, int /*long*/ lParam) { /* * Feature in Windows. On Vista only, it is faster to * compute and answer the data for the visible columns * of a table when scrolling, rather than just return * the data for each column when asked. */ if (columnVisible != null) { if (msg == OS.WM_NOTIFY && hwndParent == hwnd) { OS.MoveMemory (hdr, lParam, NMHDR.sizeof); switch (hdr.code) { case OS.LVN_GETDISPINFOA: case OS.LVN_GETDISPINFOW: { OS.MoveMemory (plvfi, lParam, NMLVDISPINFO.sizeof); if (0 <= plvfi.iSubItem && plvfi.iSubItem < columnCount) { if (!columnVisible [plvfi.iSubItem]) return 0; } break; } } } } if ((int)/*64*/msg == TASKBARBUTTONCREATED) { if (taskBar != null) { TaskItem [] items = taskBar.items; for (int i=0; i<items.length; i++) { TaskItem item = items [i]; if (item != null && item.shell != null && item.shell.handle == hwnd) { item.recreate (); break; } } } } /* * Bug in Adobe Reader 7.0. For some reason, when Adobe * Reader 7.0 is deactivated from within Internet Explorer, * it sends thousands of consecutive WM_NCHITTEST messages * to the control that is under the cursor. It seems that * if the control takes some time to respond to the message, * Adobe stops sending them. The fix is to detect this case * and sleep. * * NOTE: Under normal circumstances, Windows will never send * consecutive WM_NCHITTEST messages to the same control without * another message (normally WM_SETCURSOR) in between. */ if ((int)/*64*/msg == OS.WM_NCHITTEST) { if (hitCount++ >= 1024) { try {Thread.sleep (1);} catch (Throwable t) {} } } else { hitCount = 0; } if (lastControl != null && lastHwnd == hwnd) { return lastControl.windowProc (hwnd, (int)/*64*/msg, wParam, lParam); } int index; if (USE_PROPERTY) { index = (int)/*64*/OS.GetProp (hwnd, SWT_OBJECT_INDEX) - 1; } else { index = (int)/*64*/OS.GetWindowLongPtr (hwnd, OS.GWLP_USERDATA) - 1; } if (0 <= index && index < controlTable.length) { Control control = controlTable [index]; if (control != null) { lastHwnd = hwnd; lastControl = control; return control.windowProc (hwnd, (int)/*64*/msg, wParam, lParam); } } return OS.DefWindowProc (hwnd, (int)/*64*/msg, wParam, lParam); }
这个方法和C程序中的窗口过程函数的签名是一样的,他会不会就是SWT中的窗口过程函数呢?我在这个方法第一行
if (columnVisible != null) 处加一个断点,然后运行本文一开始的TestMultButton程序,会发现程序多次执行到
这个windowProc方法,而且不是在Java里面调用的,第一次执行windowProc的上下文环境如下:
可见,调用Display.windowProc(int,int,int,int)之前调用的是OS.createWindowEx,这也说明了windowProc
是一个回调函数。windowProc的关键代码是这些:
if (lastControl != null && lastHwnd == hwnd) { return lastControl.windowProc (hwnd, (int)/*64*/msg, wParam, lParam); } int index; if (USE_PROPERTY) { index = (int)/*64*/OS.GetProp (hwnd, SWT_OBJECT_INDEX) - 1; } else { index = (int)/*64*/OS.GetWindowLongPtr (hwnd, OS.GWLP_USERDATA) - 1; } if (0 <= index && index < controlTable.length) { Control control = controlTable [index]; if (control != null) { lastHwnd = hwnd; lastControl = control; return control.windowProc (hwnd, (int)/*64*/msg, wParam, lParam); } } return OS.DefWindowProc (hwnd, (int)/*64*/msg, wParam, lParam);
先是判断lastControl和lastHwnd是不是null,lastControl和lastHwnd正是在下面赋的值,从上文已经知道USE_PROPETRY为true,前面在addControl()方法中是
OS.SetProp (handle, SWT_OBJECT_INDEX, freeSlot + 1);
现在是
index = (int)/*64*/OS.GetProp (hwnd, SWT_OBJECT_INDEX) - 1;
所以取到的index的值就是set的时候的freeslot的值,也就是当前控件 (hwnd是窗口的句柄,代表要处理消息的控件),
index的值就是hwnd对应的控件在controlTable数组中的下标。
如果index大于等于0,就找出这个控件,调用该控件的windowProc(int,int,int,int)方法。
其他情况,调用系统默认的窗口函数,来处理消息。
Control的一些子类重写了windowProc(int,int,int,int)方法:
比如Text:
int /*long*/ windowProc (int /*long*/ hwnd, int msg, int /*long*/ wParam, int /*long*/ lParam) { if (msg == OS.EM_UNDO) { int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); if ((bits & OS.ES_MULTILINE) == 0) { LRESULT result = wmClipboard (OS.EM_UNDO, wParam, lParam); if (result != null) return result.value; return callWindowProc (hwnd, OS.EM_UNDO, wParam, lParam); } } if (msg == Display.SWT_RESTORECARET) { callWindowProc (hwnd, OS.WM_KILLFOCUS, 0, 0); callWindowProc (hwnd, OS.WM_SETFOCUS, 0, 0); return 1; } return super.windowProc (hwnd, msg, wParam, lParam); }
所有的子类在最后都会有super.windowProc(hwnd,msg,wParam,lParam)来调用父类的windowProc方法。
所以windowProc的调用顺序是:先从最底的类调用,逐级向上,最后调用的是Control的windowProc方法:
int /*long*/ windowProc (int /*long*/ hwnd, int msg, int /*long*/ wParam, int /*long*/ lParam) { LRESULT result = null; switch (msg) { case OS.WM_ACTIVATE: result = WM_ACTIVATE (wParam, lParam); break; case OS.WM_CAPTURECHANGED: result = WM_CAPTURECHANGED (wParam, lParam); break; case OS.WM_CHANGEUISTATE: result = WM_CHANGEUISTATE (wParam, lParam); break; case OS.WM_CHAR: result = WM_CHAR (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_SYSKEYUP: result = WM_SYSKEYUP (wParam, lParam); break; case OS.WM_TIMER: result = WM_TIMER (wParam, lParam); break; ...... case OS.WM_XBUTTONUP: result = WM_XBUTTONUP (wParam, lParam); break; } if (result != null) return result.value; return callWindowProc (hwnd, msg, wParam, lParam); }
我们又看到类似Windows C程序中的swtich-case语句了。根据不同的消息,会调用不同的方法处理。至于处理消息的过程,下篇文章中讲。
未完待续。
相关推荐
“插件开发篇”介绍了Eclipse插件开发的各个步骤,并给出了一个基于数据库开发和面向对象分析设计的完整插件实例;“Web开发篇”以Tomcat+Lomboz+Hibernate为开发环境,详述了其下载、安装、配置和开发的全过程。 ...
Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM...
Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行...
Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行...
Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行...
内附有一个高校收费分析系统实例源码,用hibernate 与rcp结合使用,对初学者来说很实用的,特别是想了解如何在桌面程序使用jface,swt并与hibernate结合处理数据库使用方面很到位。
Java 源码包 Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来...
笔者当初为了学习JAVA,收集了很多经典源码,源码难易程度分为初级、中级、高级等,详情看源码列表,需要的可以直接下载! 这些源码反映了那时那景笔者对未来的盲目,对代码的热情、执着,对IT的憧憬、向往!此时此...
Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM机...
Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM机...
案例7-2 借助SWT/JFace实现录入信息有效性检查 253 7.4 SWT/JFace中的常用组件 258 案例7-3 借助SWT/JFace中实现文件阅读器 259 7.5 SWT/JFace中的线程 264 案例7-4 在SWT/JFace中实现多线程效果 265 第8...
用到了CSplitterWnd::DeleteView()和CSplitterWnd::CreateView()等成员函数(35KB)<END><br>44,swt.zip DOS下仿WIN95界面及图标编辑器源程序(498k C&ASM 作者:添翼虎)(499KB)<END><br>45,menutest.zip 定制WIN...
3.3.2 经验之谈-常见错误的分析与处理40 3.3.3 Java标识符命名规则41 3.3.4 关键字42 3.3.5 常量42 3.4 运算符43 3.4.1 算术运算符43 3.4.2 赋值操作符45 3.4.3 关系操作符47 3.4.4 逻辑操作符48 3.4.5 位操作符49 ...
我们将反编译之后的源码和 JAR 包导入项目,在通过搜索源码和修复报错(会有一大波报错等待你修复,可以多种反编译工具对比结果来修改)等方式尝试将源码跑起来。来。最终我们终于成功跑起来了反编译之后的代码。 ...
1.5 SWT 界面开发实例... 13 1.5.1 使用Shell 创建窗口... 13 1.5.2 简单的用户密码验证器... 16 1.5.3 文件选择器... 19 第 2 章 在 Eclipse 中进行重构... 22 2.1 重命名实例... 22 2.2 移动实例... ...