对于Eclipse开发者来说,不管Plug-in还是RCP免不了要和SWT打交道,但两者似乎有些不同,Plug-in主要跟Eclipse过招,开发更多是上层应用如UI/JDT/EMF/GMF等,而RCP像是应用程序,时不时与图形系统交互,需求更是五花八门,举几例:
- RCP界面要跟Office 2007/Vista一样。
- 我喜欢上一VC版的水晶按钮。
- 商业版控件支持。
- 我的RCP程序要定时启动。
这些效果让RCP下的SWT越来越跟平台相关,但矛盾的是SWT要跨平台,提供的API只能是一个平衡产品+少许补充,更多特性依赖我们对SWT进行扩展。比较常见的是对SWT Win32 API进行扩展,因为Windows的图形特性太丰富了,不用白不用啊。
下面就以SWT win32 x86来演示一下这个扩展过程。扩展很简单,就是在swt的窗口上加一个自定义系统菜单,最终效果如下图:
首先要有SWT JNI源代码,在Eclipse plug-in目录下找到org.eclipse.swt.win32.win32.x86.source_3.X.X.vXXX.jar(xx为版本号),解压缩到c:\build\swt-jni,解完后在会发现有一堆h和c文件,其中比较重要的就是os, gdip, xpcom,wgl,awt,用途分别如下:
- os: 主要的JNI,用来创建控件,事件处理等。
- gdip: 与windows的dc交互,提供swt的gc画图功能。
- xpcom: 供swt调用mozilla系列浏览器如firefox等。
- wgl: 与3D相关,一般都用不上。
- awt:在swt中调用awt,awt也是jni。
了解swt源代码之后,接下来为build swt做准备。
- 安装vc6,尝试过用高版本,但不支持。
- 安装platform SDK 2003 February版,下载地址 遵照安装说明将SDK解压缩安装。不要尝试更新版本,不支持。
- 安装jdk,这个无所谓,1.4, 1.5, 1.6都可以。
- 下载gecko-sdk(下载地址),请务必使用1.8,swt目前不完全支持1.9。
所以的工具都安装或解压之后,在swt源代码目录下找到build.bat,在:X86 label部分做如下配置,其中配置的目录视你的安装而定:
1:X86
2
3IF x.%DEV_TOOLS%==x. set DEV_TOOLS=c:\PROGRA~1
4call %DEV_TOOLS%\MICROS~2\vc98\bin\vcvars32.bat
5IF x.%MSSDK%==x. set MSSDK=%DEV_TOOLS%\MICROS~3
6call %MSSDK%\setenv /XP32 /RETAIL
7IF x.%OUTPUT_DIR%==x. set OUTPUT_DIR=..\out
8IF x.%JAVA_HOME%==x. set JAVA_HOME=%DEV_TOOLS%\Java\jdk1.6.0_06
9IF x.%XULRUNNER_SDK%==x. set XULRUNNER_SDK=C:\gecko-sdk
10set XULRUNNER_MAKE=make_xulrunner
11IF x.%1==x.x86 shift
12GOTO MAKE
如果你和我一样的用的是Sun的JDK,且版本是1.5或1.6,还须修改一下和build.bat同目录的make_win32.mak文件。
替换
AWT_LIBS = "$(JAVA_HOME)\jre\bin\jawt.lib"
为
AWT_LIBS = "$(JAVA_HOME)\lib\jawt.lib"
一旦配置完成后,就可以在命令行中运行 build.bat x86 来build swt了,如果你的build过程中出错,使用build x86 clean删除垃圾文件,然后再查明原因。如果没有错误的话,目录下会生成5个dll文件,分别是swt-awt-win32-XXXX.dll, swt-gdip-win32-XXXX.dll, swt-wgl-win32-XXXX.dll, swt-win32-XXXX.dll, swt-xulrunner-win32-XXXX.dll,其中XXXX是视你的Eclipse版本而定。
要实现我们想要的效果,还需针对SWT的消息处理程序(WndProc) 扩展一下OS,那就是在就在主窗口建立过程中候往系统菜单里面加一自定义项。在Win32中,窗口建立的消息为WM_CREATE(值为1)。为简单起见,这里只是覆盖了SWT的默认消息处理程序,它通过os.c中的 OS_NATIVE(DefWindowProcW)方法来实现的(在98以后的版本api都是unicode,所以只覆盖W版本)
改动之前:
1#ifndef NO_DefWindowProcW
2JNIEXPORT jint JNICALL OS_NATIVE(DefWindowProcW)
3 (JNIEnv *env, jclass that, jint arg0, jint arg1, jint arg2, jint arg3)
4{
5 jint rc = 0;
6 OS_NATIVE_ENTER(env, that, DefWindowProcW_FUNC);
7 rc = (jint)DefWindowProcW((HWND)arg0, arg1, (WPARAM)arg2, (LPARAM)arg3);
8 OS_NATIVE_EXIT(env, that, DefWindowProcW_FUNC);
9 return rc;
10}
11#endif
改动之后:
1#ifndef NO_DefWindowProcW
2
3#define ID_CUSTOM_MENU 32888
4
5const wchar_t* wcTitle=L"Hello";
6const wchar_t* wcContent=L"World";
7const wchar_t* wcName = L"Bang";
8
9LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
10{
11 int wmId, wmEvent;
12 HMENU hSysMenu;
13 switch (message)
14 {
15 case WM_CREATE:
16 hSysMenu = GetSystemMenu(hWnd,FALSE);
17 InsertMenuW(hSysMenu,1,MF_BYPOSITION | MF_STRING,ID_CUSTOM_MENU,wcName);
18 break;
19 case WM_DESTROY:
20 PostQuitMessage(0);
21 break;
22 case WM_SYSCOMMAND:
23 wmId = LOWORD(wParam);
24 if (wmId==ID_CUSTOM_MENU)
25 MessageBoxW(hWnd,wcTitle,wcContent,MB_OK);
26 else
27 return DefWindowProcW(hWnd, message, wParam, lParam);
28 break;
29 default:
30 return DefWindowProcW(hWnd, message, wParam, lParam);
31 }
32 return 0;
33}
34
35JNIEXPORT jint JNICALL OS_NATIVE(DefWindowProcW)
36 (JNIEnv *env, jclass that, jint arg0, jint arg1, jint arg2, jint arg3)
37{
38 jint rc = 0;
39 OS_NATIVE_ENTER(env, that, DefWindowProcW_FUNC);
40 rc = (jint)MyWndProc((HWND)arg0, arg1, (WPARAM)arg2, (LPARAM)arg3);
41 OS_NATIVE_EXIT(env, that, DefWindowProcW_FUNC);
42 return rc;
43}
44
45#endif
改动的目的就是在系统菜单里面加了一个菜单项 "Bang",点击后会出现“Hello world” 对话框。
重新在swt源目录下运行build x86,生成的swt就是扩展以后的版本。
现在我们来测试一下效果,写一个如下所示的HelloWorld1.java,放到swt源目录下,先运行javac HelloWorld1.java,再运行java HelloWorld1。不用担心classpath与library path,build之后它们默认都在当前目录下。
import org.eclipse.swt.widgets.*;
public class HelloWorld1 {
public static void main (String [] args) {
Display display = new Display ();
Shell shell = new HelloWorld1 ().open (display);
while (!shell.isDisposed ()) {
if (!display.readAndDispatch ()) display.sleep ();
}
display.dispose ();
}
public Shell open (Display display) {
Shell shell = new Shell (display);
shell.open ();
return shell;
}
}
在窗口标题栏单击右键,点击出现的Bang菜单会有如下效果图:
这个例子虽然简单,但大致描述了扩展swt的过程,更复杂的扩展等待YOU来实现!