`
edgar108
  • 浏览: 32602 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

SWT源码分析(五)

阅读更多

先看程序:

 

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语句了。根据不同的消息,会调用不同的方法处理。至于处理消息的过程,下篇文章中讲。

 

未完待续。

  • 大小: 6.5 KB
  • 大小: 17.5 KB
  • 大小: 15.4 KB
分享到:
评论

相关推荐

    Eclipse从入门到精通 陈刚 源码

    “插件开发篇”介绍了Eclipse插件开发的各个步骤,并给出了一个基于数据库开发和面向对象分析设计的完整插件实例;“Web开发篇”以Tomcat+Lomboz+Hibernate为开发环境,详述了其下载、安装、配置和开发的全过程。 ...

    java源码包---java 源码 大量 实例

    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM...

    java源码包2

    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行...

    java源码包3

    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行...

    java源码包4

    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行...

    eclipse.rcp应用系统开发方法与实战(含源码)

    内附有一个高校收费分析系统实例源码,用hibernate 与rcp结合使用,对初学者来说很实用的,特别是想了解如何在桌面程序使用jface,swt并与hibernate结合处理数据库使用方面很到位。

    JAVA上百实例源码以及开源项目源代码

    Java 源码包 Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来...

    JAVA上百实例源码以及开源项目

    笔者当初为了学习JAVA,收集了很多经典源码,源码难易程度分为初级、中级、高级等,详情看源码列表,需要的可以直接下载! 这些源码反映了那时那景笔者对未来的盲目,对代码的热情、执着,对IT的憧憬、向往!此时此...

    成百上千个Java 源码DEMO 4(1-4是独立压缩包)

    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM机...

    成百上千个Java 源码DEMO 3(1-4是独立压缩包)

    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM机...

    Eclipse开发入门与项目实践 源代码

    案例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...

    Visual C++ 编程资源大全(源码 窗体)

    用到了CSplitterWnd::DeleteView()和CSplitterWnd::CreateView()等成员函数(35KB)&lt;END&gt;&lt;br&gt;44,swt.zip DOS下仿WIN95界面及图标编辑器源程序(498k C&ASM 作者:添翼虎)(499KB)&lt;END&gt;&lt;br&gt;45,menutest.zip 定制WIN...

    java基础案例与开发详解案例源码全

    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 ...

    049-冰蝎,从入门到魔改.pdf

    我们将反编译之后的源码和 JAR 包导入项目,在通过搜索源码和修复报错(会有一大波报错等待你修复,可以多种反编译工具对比结果来修改)等方式尝试将源码跑起来。来。最终我们终于成功跑起来了反编译之后的代码。 ...

    Eclipse+Web开发从入门到精通(实例版)

    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 移动实例... ...

Global site tag (gtag.js) - Google Analytics