Java语言的声望和它在桌面应用程序(GUI程序)所取得的成就显然极不相符
至今仍然很少能看到非常成功Java桌面程序
虽然有JBuilder
Netbean
JProbe等大型软件作为代表
但这仍不能证明Java的GUI程序是成功的
它们的外观总是和同一操作系统平台下的其它软件显得格格不入
对机器配置的需求也似乎永无止境
这使得它们只能被一些总是拥有当前最高性能PC的程序员们所容忍
或是那些不在乎金钱和时间的专业用户所接受
对绝大多数计算机使用者来说
AWT或SWING代表着怪异的界面和无法接受的速度
Standard Widget Toolkit(SWT)或许是Java这一噩梦的终结者
广大Java程序员终于可以开发出高效率的GUI程序
它们拥有标准的外观
几乎没有人能看出你的程序是用Java写出来的
更为重要的是
这些程序是跨平台的
SWT本身仅仅是Eclipse组织为了开发Eclipse IDE环境所编写的一组底层图形界面 API或许是无心插柳或是有意为之至今为止SWT无论是在性能和外观上都超越了SUN公司提供的AWT和SWING目前Eclipse IDE已经开发到了版本SWT已经十分稳定这里指的稳定应该包含两层意思
一是指性能上的稳定其中的关键是源于SWT的设计理念SWT最大化了操作系统的图形构件API就是说只要操作系统提供了相应图形的构件那么SWT只是简单应用JNI技术调用它们只有那些操作系统中不提供的构件SWT才自己去做一个模拟的实现可以看出SWT的性能上的稳定大多时候取决于相应操作系统图形构件的稳定性
另一个稳定是指SWT API包中的类方法的名称和结构已经少有改变程序员不用担心由于Eclipse组织开发进度很快(Eclipse IDE每天都会有一个Nightly版本的发布)而导致自己的程序代码变化过大从一个版本的SWT更新至另一版本通常只需要简单将SWT包换掉就可以了
第一个SWT程序
下面让我们开始一个SWT程序(注意以下的例子和说明主要针对Windows平台其它的操作系统应该大同小异)首先要在Eclipse安装文件中找到SWT包Eclipse组织并不提供单独的SWT包下载必须下载完整的Eclipse开发环境才能得到SWT包SWT是作为Eclipse开发环境的一个插件形式存在可以在${你的eclipse安装路径}\plugins路径下的众多子目录下去搜索SWTJAR文件在找到的JAR文件中包含了SWT全部的Java类文件因为SWT应用了JNI技术因此同时也要找到相对应的JNI本地化库文件由于版本和操作平台的不同本地化库文件的名称会有些差别比如SWTWINDLL是Window平台下Eclipse Build 的动态库而在Unix平台相应版本的库文件的扩展名应该是so等等注意的是Eclipse是一个开放源代码的项目因此你也可以在这些目录中找到SWT的源代码相信这会对开发很有帮助下面是一段打开空窗口的代码(只有main方法)
import comeoneexample;
public class OpenShell{
public static void main(String [] args) {
Display display = new Display();
Shell shell = new Shell(display);
shellopen();
// 开始事件处理循环直到用户关闭窗口
while (!shellisDisposed()) {
if (!displayreadAndDispatch())
displaysleep();
}
displaydispose();
}
}
确信在CLASSPATH中包括了SWTJAR文件先用Javac编译例子程序编译无错后可运行java Djavalibrarypath=${你的SWT本地库文件所在路径} comeoneexampleOpenShell比如SWTWINDLL件所在的路径是C:\swtlib运行的命令应该是java Djavalibrarypath=c:\swtlib comeoneexampleOpenShell成功运行后系统会打开了一个空的窗口
剖析SWT API
下面再让我们进一步分析SWT API的组成所有的SWT类都用orgeclipseswt做为包的前缀下面为了简化说明我们用*号代表前缀orgeclipseswt比如*widgets包代表的是orgeclipseswtwidgets包
我们最常用的图形构件基本都被包括在*widgets包中比如ButtonComboTextLabelSashTable等等其中两个最重要的构件当数Shell和CompositeShell相当于应用程序的主窗口框架上面的例子代码中就是应用Shell构件打开一个空窗口Composite相当于SWING中的Panel对象充当着构件容器的角色当我们想在一个窗口中加入一些构件时最好到使用Composite作为其它构件的容器然后再去*layout包找出一种合适的布局方式SWT对构件的布局也采用了SWING或AWT中Layout和Layout Data结合的方式在*layout包中可以找到四种Layout和与它们相对应的布局结构对象(Layout Data)在*custom包中包含了对一些基本图形构件的扩展比如其中的CLabel就是对标准Label构件的扩展上面可以同时加入文字和图片也可以加边框StyledText是Text构件的扩展它提供了丰富的文本功能比如对某段文字的背景色前景色或字体的设置在*custom包中也可找到一个新的StackLayout布局方式
SWT对用户操作的响应比如鼠标或键盘事件也是采用了AWT和SWING中的Observer模式在*event包中可以找到事件监听的Listener接口和相应的事件对象例如常用的鼠标事件监听接口MouseListenerMouseMoveListener和MouseTrackListener及对应的事件对象MouseEvent
*graphics包中可以找到针对图片光标字体或绘图的API比如可通过Image类调用系统中不同类型的图片文件通过GC类实现对图片构件或显示器的绘图功能
对不同平台Eclipse还开发了一些富有针对性的API例如在Windows平台可以通过*olewin包很容易的调用ole控件这使Java程序内嵌IE浏览器或WordExcel等程序成为可能!
更复杂的程序
下面让我们展示一个比上面例子更加复杂一些的程序这个程序拥有一个文本框和一个按键当用户点击按键的时候文本框显示一句欢迎信息
为了文本框和按键有比较合理的大小和布局这里采用了GridLayout布局方式这种布局是SWT中最常用也是最强大的布局方式几乎所有的格式都可能通过GridLayout去达到下面的程序也涉及到了如何应用系统资源(Color)以及如何释放系统资源
private void initShell(Shell shell) {
//为Shell设置布局对象
GridLayout gShellLay = new GridLayout();
shellsetLayout(gShellLay);
//构造一个Composite构件作为文本框和按键的容器
Composite panel = new Composite(shellSWTNONE);
//为Panel指定一个布局结构对象
这里让Panel尽可能的占满Shell也就是全部应用程序窗口的空间
GridData gPanelData = new GridData(GridDataGRAB_HORIZONTAL| GridDataGRAB_VERTICAL|GridDataFILL_BOTH);
panelsetLayoutData(gPanelData);
//为Panel也设置一个布局对象文本框和按键将按这个布局对象来显示
GridLayout gPanelLay = new GridLayout();
panelsetLayout(gPanelLay);
//为Panel生成一个背景色
final Color bkColor = new Color(DisplaygetCurrent());
panelsetBackground(bkColor);
//生成文本框
final Text text = new Text(panelSWTMULTI|SWTWRAP);
//为文本框指定一个布局结构对象
这里让文本框尽可能的占满Panel的空间
GridData gTextData = new GridData (GridDataGRAB_HORIZONTAL| GridDataGRAB_VERTICAL|GridDataFILL_BOTH);
textsetLayoutData(gTextData);
//生成按键
Button butt = new Button(panelSWTPUSH);
buttsetText(Push);
//为按键指定鼠标事件
buttaddMouseListener(new MouseAdapter(){
public void mouseDown(MouseEvent e){
//当用户点击按键的时候显示信息
textsetText(Hello SWT);
}
};
//当主窗口关闭时会触发DisposeListener这里用来释放Panel的背景色
shelladdDisposeListener(new DisposeListener(){
public void widgetDisposed(DisposeEvent e) {
bkColordispose();
}
};
}
把这段代码中的方法initShell()加入到第一个打开空窗口的例子中得到的是一段能成功运行的完整GUI应用程序运行方法可参考第一个例子
系统资源的管理
在一个图形化的操作系统中开发程序都要调用系统中的资源如图片字体颜色等通常这些资源都是有限的程序员务必非常小心的使用这些资源当不再使用它们时就请尽快释放不然操作系统迟早会油尽灯枯不得不重新启动更严重的会导致系统崩溃
SWT是用Java开发的Java语言本身的一大优势就是JVM的垃圾回收机制程序员通常不用理会变量的释放内存的回收等问题那么对SWT而言系统资源的操作是不是也是如此?答案是一个坏消息一个好消息
坏消息是SWT并没采用JVM的垃圾回收机制去处理操作系统的资源回收问题一个关键的因素是因为JVM的垃圾回收机制是不可控的也就是说程序员不能知道也不可能做到在某一时刻让JVM回收资源!这对系统资源的处理是致命的试想你的程序希望在一个循环语句中去查看数万张图片常规的处理方式是每次调入一张查看然后就立即释放该图片资源而后在循环调入下一张图片这对操作系统而言任何时刻程序占用的仅仅是一张图片的资源但如果这个过程完全交给JVM去处理也许会是在循环语句结束后JVM才会去释放图片资源其结果可能是你的程序还没有运行结束操作系统已经宕掉
但下面的好消息也许会让这个坏消息变得无关紧要对于SWT只需了解两条简单的黄金法则就可以放心的使用系统资源!之所以称为黄金法则一是因为少只有两条二是因为它们出奇的简单第一条是谁占用谁释放第二条是父构件被销毁子构件也同时被销毁第一条原则是一个无任何例外的原则只要程序调用了系统资源类的构造函数程序就应该关心在某一时刻要释放这个系统资源比如调用了
Font font = new Font (display Courier SWTNORMAL);
那么就应该在不在需要这个Font的时候调用
fontdispose();
对于第二个原则是指如果程序调用某一构件的dispose()方法那么所有这个构件的子构件也会被自动调用dispose()方法而销毁通常这里指的子构件与父构件的关系是在调用构件的构造函数时形成的比如
Shell shell = new Shell();
Composite parent = new Composite(shellSWTNULL);
Composite child = new Composite(parentSWTNULL);
其中parent的父构件是shell而shell则是程序的主窗口所以没有相应的父构件同时parent又包括了child子构件如果调用shelldispose()方法应用第二条法则那么parent和child构件的dispose()方法也会被SWT API自动调用它们也随之销毁
线程问题
在任何操作平台的GUI系统中对构件或一些图形API的访问操作都要被严格同步并串行化例如在一个图形界面中的按键构件可被设成可用状态(enable)或禁用状态(disable)正常的处理方式是用户对按键状态设置操作都要被放入到GUI系统的事件处理队列中(这意味着访问操作被串行化)然后依次处理(这意味着访问操作被同步)想象当按键可用状态的设置函数还没有执行结束的时候程序就希望再设置该按键为禁用状态势必会引起沖突实际上这种操作在任何GUI系统都会触发异常
Java语言本身就提供了多线程机制这种机制对GUI编程来说是不利的它不能保证图形构件操作的同步与串行化SWT采用了一种简单而直接的方式去适应本地GUI系统对线程的要求在SWT中通常存在一个被称为用户线程的唯一线程只有在这个线程中才能调用对构件或某些图形API的访问操作如果在非用户线程中程序直接调用这些访问操作那么SWTExcepiton异常会被抛出但是SWT也在*widgetDisplay类中提供了两个方法可以间接的在非用户线程的进行图形构件的访问操作这是通过syncExec(Runnable)和asyncExec(Runnable)这两个方法去实现例如
//此时程序运行在一个非用户线程中并且希望在构件panel上加入一个按键
DisplaygetCurrent()asyncExec(new Runnable() {
public void run() {
Button butt = new Button(panelSWTPUSH);
buttsetText(Push);
}
});
方法syncExec()和asyncExec()的区别在于前者要在指定的线程执行结束后才返回而后者则无论指定的线程是否执行都会立即返回到当前线程
SWT的扩展JFace
JFace与SWT的关系好比Microsoft的MFC与SDK的关系JFace是基于SWT开发其API比SWT更加易于使用但功能却没SWT来的直接比如下面的代码应用JFace中的MessageDialog打开一个警告对话框
MessageDialogopenWarning(parentWarningWarning message);
如果只用SWT完成以上功能语句不会少于行!
JFace原本是为更加方便的使用SWT而编写的一组API其主要目的是为了开发Eclipse IDE环境而不是为了应用到其它的独立应用程序因此在Eclipse 版本之前很难将JFace API完整的从Eclipse的内核API中剥离出来总是要多多少少导入一些非JFace以外的Eclipse核心代码类或接口才能得到一个没有任何编译错误的JFace开发包但目前Eclipse组织似乎已经逐渐意识到了JFace在开发独立应用程序起到的重要作用在开发的版本中JFace也开始变成了和SWT一样的完整独立的开发包只是这个开发包还在变动中(笔者写本文时应用的EclipseM 版本)JFace开发包的包前缀是以orgeclipsejface开头JAR包的源代码也和SWT一样也在${你的eclipse安装路径}\plugins路径下去找
对开发人员来说在开发一个图形构件的时候比较好的方式是先到JFace包去找一找看是不是有更简洁的实现方法如果没有再用SWT包去自己实现比如JFace为对话框提供了很好的支持除了各种类型的对话框(比如上面用的MessageDialog或是带有Title栏的对话框)如要实现一个自定义的对话框也最好从JFace中的Dialog类继承而不是从SWT中的*widgetDialog继承
应用JFace中的Preference包中的类很容易为自己的软件做出一个很专业的配置对话框对于TreeTable等图形构件它们在显示的同时也要和数据关联例如Table中显示的数据在JFace 中的View包中为此类构件提供了ModelView方式的编程方法这种方法使显示与数据分开更加利于开发与维护JFace中提供最多的功能就是对文本内容的处理可以在orgeclipsejfacetext* 包中找到数十个与文本处理相关类
与应用程序更近一步
Java程序通常是以class文件的方式发布的运行class需要JRE或JDK的支持这又是Java GUI程序的另一个致命的弱点想象对一个面向广大用户的应用程序来说无论你的程序功能有多简单或是你的代码十分的精简你都不得不让用户去下载一个M的JRE那是多么令人沮丧的一件事而且对程序员来说Class通常意味着源代码的暴露反编译的工具让那些居心叵测的人轻易得到你的源代码虽然有很多对Class的加密方法但那总是以牺牲性能为代价的好在我们还有其它的方式可用把Class编译成exe文件!
通过SWT开发包简单跨平台可靠等这些Java语言本身所具有的优点正渐渐融合到图形界面的应用程序开发中去因此我相信Java语言的另一扇成功之门正在逐渐打开