CSDN博客

img tar

SWT:AWT和SWING的强大竞争者

发表于2003/6/13 16:03:00  988人阅读

SWT:AWT和SWING的强大竞争者    


  
内容:

第一个SWT程序
剖析SWT API
更复杂的程序
系统资源的管理
线程问题
SWT的扩展:JFace
参考资料
关于作者


Also in the Java zone:

教学
工具与产品
代码与组件
所有文章
实用技巧




倪大鹏 (ndp@e2one.com)

2003 年 2 月

从Java诞生至今,已经在太多的领域取得成功,然而它却很少在图形界面程序上崭露头角
。究其原因,Java语言缺省的图形界面开发包AWT和SWING实在是难脱其究, 无论速度和外
观,它们都难以让人接受。 如今,Eclipse组织编写的SWT开发包,为Java程序员提供了A
WT和SWING之外的一个更佳的选择。在本文中,对SWT做了简单但尽可能全面的介绍。
Java语言的声望和它在桌面应用程序(GUI程序)所取得的成就显然极不相符,至今仍然很
少能看到非常成功Java桌面程序。虽然有JBuilder,Netbean,JProbe等大型软件作为代表
,但这仍不能证明Java的GUI程序是成功的:它们的外观总是和同一操作系统平台下的其它
软件显得格格不入。对机器配置的需求也似乎永无止境,这使得它们只能被一些总是拥有
当前最高性能PC的程序员们所容忍,或是那些不在乎金钱和时间的专业用户所接受。对绝
大多数计算机使用者来说,AWT或SWING代表着怪异的界面和无法接受的速度。Standard W
idget Toolkit(SWT)或许是Java这一噩梦的终结者,广大Java程序员终于可以开发出高
效率的GUI程序,它们拥有标准的外观,几乎没有人能看出你的程序是用Java写出来的,更
为重要的是,这些程序是跨平台的。

SWT本身仅仅是Eclipse组织为了开发Eclipse IDE环境所编写的一组底层图形界面 API。或
许是无心插柳,或是有意为之,至今为止,SWT无论是在性能和外观上,都超越了SUN公司
提供的AWT和SWING。目前Eclipse IDE已经开发到了2.1版本,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路径下的众多子
目录下去搜索SWT.JAR文件,在找到的JAR文件中包含了SWT全部的Java类文件。因为SWT应
用了JNI技术,因此同时也要找到相对应的JNI本地化库文件,由于版本和操作平台的不同
,本地化库文件的名称会有些差别,比如SWT-WIN32-2116.DLL是Window平台下Eclipse Bu
ild 2116的动态库,而在Unix平台相应版本的库文件的扩展名应该是.so,等等。注意的是
,Eclipse是一个开放源代码的项目,因此你也可以在这些目录中找到SWT的源代码,相信
这会对开发很有帮助。

下面是一段打开空窗口的代码(只有main方法)。

import com.e2one.example;
public class OpenShell{
public static void main(String [] args) {
    Display display = new Display();
    Shell shell = new Shell(display);
    shell.open();
    // 开始事件处理循环,直到用户关闭窗口
while (!shell.isDisposed()) {
        if (!display.readAndDispatch())
            display.sleep();
    }
    display.dispose();
}
}






确信在CLASSPATH中包括了SWT.JAR文件,先用Javac编译例子程序。编译无错后可运行jav
a -Djava.library.path=${你的SWT本地库文件所在路径} com.e2one.example.OpenShell
,比如SWT-WIN32-2116.DLL件所在的路径是C:/swtlib,运行的命令应该是java -Djava.l
ibrary.path=c:/swtlib com.e2one.example.OpenShell。成功运行后,系统会打开了一个
空的窗口。

剖析SWT API
下面再让我们进一步分析SWT API的组成。所有的SWT类都用org.eclipse.swt做为包的前缀
,下面为了简化说明,我们用*号代表前缀org.eclipse.swt,比如*.widgets包,代表的是
org.eclipse.swt.widgets包。

我们最常用的图形构件基本都被包括在*.widgets包中,比如Button,Combo,Text,Labe
l,Sash,Table等等。其中两个最重要的构件当数Shell和Composite。Shell相当于应用程
序的主窗口框架,上面的例子代码中就是应用Shell构件打开一个空窗口。Composite相当
于SWING中的Panel对象,充当着构件容器的角色,当我们想在一个窗口中加入一些构件时
,最好到使用Composite作为其它构件的容器,然后再去*.layout包找出一种合适的布局方
式。SWT对构件的布局也采用了SWING或AWT中Layout和Layout Data结合的方式,在*.layo
ut包中可以找到四种Layout和与它们相对应的布局结构对象(Layout Data)。在*.custo
m包中,包含了对一些基本图形构件的扩展,比如其中的CLabel,就是对标准Label构件的
扩展,上面可以同时加入文字和图片,也可以加边框。StyledText是Text构件的扩展,它
提供了丰富的文本功能,比如对某段文字的背景色、前景色或字体的设置。在*.custom包
中也可找到一个新的StackLayout布局方式。

SWT对用户操作的响应,比如鼠标或键盘事件,也是采用了AWT和SWING中的Observer模式,
在*.event包中可以找到事件监听的Listener接口和相应的事件对象,例如常用的鼠标事件
监听接口MouseListener,MouseMoveListener和MouseTrackListener,及对应的事件对象
MouseEvent。

*.graphics包中可以找到针对图片、光标、字体或绘图的API。比如可通过Image类调用系
统中不同类型的图片文件。通过GC类实现对图片、构件或显示器的绘图功能。

对不同平台,Eclipse还开发了一些富有针对性的API。例如,在Windows平台,可以通过*
.ole.win32包很容易的调用ole控件,这使Java程序内嵌IE浏览器或Word、Excel等程序成
为可能!

要进一步了解SWT的情况,可以在Eclipse IDE的帮助文档中找到SWT的JavaDoc说明。当然
最深入的了解莫过于去读SWT的源代码,这也正是开放源代码项目对程序员的魅力所在!


更复杂的程序
下面让我们展示一个比上面例子更加复杂一些的程序。这个程序拥有一个文本框和一个按
键,当用户点击按键的时候,文本框显示一句欢迎信息。

为了文本框和按键有比较合理的大小和布局,这里采用了GradLayout布局方式。这种布局
是SWT中最常用也是最强大的布局方式,几乎所有的格式都可能通过GradLayout去达到。下
面的程序也涉及到了如何应用系统资源(Color),以及如何释放系统资源。

   private void initShell(Shell shell) {
        //为Shell设置布局对象
        GridLayout gShellLay = new GridLayout();
        shell.setLayout(gShellLay);
        //构造一个Composite构件作为文本框和按键的容器
        Composite panel = new Composite(shell,SWT.NONE);
        //为Panel指定一个布局结构对象。这里让Panel尽可能的占满Shell,也就是全部
应用程序窗口的空间。
        GridData gPanelData = new GridData(GridData.GRAB_HORIZONTAL|GridData.G
RAB_VERTICAL|GridData.FILL_BOTH);
        panel.setLayoutData(gPanelData);
        //为Panel也设置一个布局对象。文本框和按键将按这个布局对象来显示。
        GridLayout gPanelLay = new GridLayout();
        panel.setLayout(gPanelLay);
        //为Panel生成一个背景色
        final Color bkColor = new Color(Display.getCurrent(),200,0,200);
        panel.setBackground(bkColor);
        //生成文本框
        final Text text = new Text(panel,SWT.MULTI|SWT.WRAP);
        //为文本框指定一个布局结构对象,这里让文本框尽可能的占满Panel的空间。

        GridData gTextData = new GridData(GridData.GRAB_HORIZONTAL|GridData.GR
AB_VERTICAL|GridData.FILL_BOTH);
        text.setLayoutData(gTextData);
        //生成按键
        Button butt = new Button(panel,SWT.PUSH);
        butt.setText("Push");
        //为按键指定鼠标事件
        butt.addMouseListener(new MouseAdapter(){
            public void mouseDown(MouseEvent e){
                //当用户点击按键的时候,显示信息
                text.setText("Hello SWT");
            }
        });
        //当主窗口关闭时,会触发DisposeListener。这里用来释放Panel的背景色。

        shell.addDisposeListener(new DisposeListener(){
            public void widgetDisposed(DisposeEvent e) {
                bkColor.dispose();
            }
        });
    }






把这段代码中的方法initShell()加入到第一个打开空窗口的例子中,得到的是一段能成功
运行的完整GUI应用程序。运行方法可参考第一个例子。

系统资源的管理
在一个图形化的操作系统中开发程序,都要调用系统中的资源,如图片、字体、颜色等。
通常这些资源都是有限的,程序员务必非常小心的使用这些资源:当不再使用它们时,就
请尽快释放,不然操作系统迟早会油尽灯枯,不得不重新启动,更严重的会导致系统崩溃


SWT是用Java开发的,Java语言本身的一大优势就是JVM的"垃圾回收机制",程序员通常不
用理会变量的释放,内存的回收等问题。那么对SWT而言,系统资源的操作是不是也是如此
?答案是一个坏消息,一个好消息。

坏消息是SWT并没采用JVM的垃圾回收机制去处理操作系统的资源回收问题,一个关键的因
素是因为JVM的垃圾回收机制是不可控的,也就是说程序员不能知道,也不可能做到在某一
时刻让JVM回收资源!这对系统资源的处理是致命的,试想你的程序希望在一个循环语句中
去查看数万张图片,常规的处理方式是每次调入一张,查看,然后就立即释放该图片资源
,而后在循环调入下一张图片,这对操作系统而言,任何时刻程序占用的仅仅是一张图片
的资源。但如果这个过程完全交给JVM去处理,也许会是在循环语句结束后,JVM才会去释
放图片资源,其结果可能是你的程序还没有运行结束,操作系统已经宕掉。

但下面的好消息也许会让这个坏消息变得无关紧要。对于SWT,只需了解两条简单的"黄金
"法则就可以放心的使用系统资源!之所以称为黄金法则,一是因为少,只有两条,二是因
为它们出奇的简单。第一条是"谁占用,谁释放",第二条是"父构件被销毁,子构件也同时
被销毁"。第一条原则是一个无任何例外的原则,只要程序调用了系统资源类的构造函数,
程序就应该关心在某一时刻要释放这个系统资源。比如调用了

Font font = new Font (display, "Courier", 10, SWT.NORMAL);

那么就应该在不在需要这个Font的时候调用

font.dispose();

对于第二个原则,是指如果程序调用某一构件的dispose()方法,那么所有这个构件的子构
件也会被自动调用dispose()方法而销毁。通常这里指的子构件与父构件的关系是在调用构
件的构造函数时形成的。比如,



   Shell shell = new Shell();
    Composite parent = new Composite(shell,SWT.NULL);
    Composite child = new Composite(parent,SWT.NULL);






其中parent的父构件是shell,而shell则是程序的主窗口,所以没有相应的父构件,同时
parent又包括了child子构件。如果调用shell.dispose()方法,应用第二条法则,那么pa
rent和child构件的dispose()方法也会被SWT API自动调用,它们也随之销毁。

对于这两个法则,参考资料2中的文章有更加深入的介绍。

线程问题
在任何操作平台的GUI系统中,对构件或一些图形API的访问操作都要被严格同步并串行化
。例如,在一个图形界面中的按键构件可被设成可用状态(enable)或禁用状态(disable),
正常的处理方式是,用户对按键状态设置操作都要被放入到GUI系统的事件处理队列中(这
意味着访问操作被串行化),然后依次处理(这意味着访问操作被同步)。想象当按键可
用状态的设置函数还没有执行结束的时候,程序就希望再设置该按键为禁用状态,势必会
引起冲突。实际上,这种操作在任何GUI系统都会触发异常。

Java语言本身就提供了多线程机制,这种机制对GUI编程来说是不利的,它不能保证图形构
件操作的同步与串行化。SWT采用了一种简单而直接的方式去适应本地GUI系统对线程的要
求:在SWT中,通常存在一个被称为"用户线程"的唯一线程,只有在这个线程中才能调用对
构件或某些图形API的访问操作。如果在非用户线程中程序直接调用这些访问操作,那么S
WTExcepiton异常会被抛出。但是SWT也在*.widget.Display类中提供了两个方法可以间接
的在非用户线程的进行图形构件的访问操作,这是通过的syncExec(Runnable)和asyncExe
c(Runnable)这两个方法去实现的。例如:

//此时程序运行在一个非用户线程中,并且希望在构件panel上加入一个按键。

Display.getCurrent().asyncExec(new Runnable() {
    public void run() {
        Button butt = new Button(panel,SWT.PUSH);
        butt.setText("Push");
    }
});





方法syncExec()和asyncExec()的区别在于前者要在指定的线程执行结束后才返回,而后者
则无论指定的线程是否执行都会立即返回到当前线程。

SWT的扩展:JFace
JFace与SWT的关系好比Microsoft的MFC与SDK的关系,JFace是基于SWT开发,其API比SWT更
加易于使用,但功能却没SWT来的直接。比如下面的代码应用JFace中的MessageDialog打开
一个警告对话框:

MessageDialog.openWarning(parent,"Warning","Warning message");

如果只用SWT完成以上功能,语句不会少于30行!

JFace原本是为更加方便的使用SWT而编写的一组API,其主要目的是为了开发Eclipse IDE
环境,而不是为了应用到其它的独立应用程序。因此在Eclipse 2.1版本之前,很难将JFa
ce API完整的从Eclipse的内核API中剥离出来,总是要多多少少导入一些非JFace以外的E
clipse核心代码类或接口才能得到一个没有任何编译错误的JFace开发包。但目前Eclipse
组织似乎已经逐渐意识到了JFace在开发独立应用程序起到的重要作用,在正在开发的2.1
版本中,JFace也开始变成了和SWT一样的完整独立的开发包,只是这个开发包还在变动中
(笔者写本文时,应用的Eclipse2.1M3版本)。JFace开发包的包前缀是以org.eclipse.jfa
ce开头。JAR包和源代码也和SWT一样,也在${你的eclipse安装路径}/plugins路径下去
找。

对开发人员来说,在开发一个图形构件的时候,比较好的方式是先到JFace包去找一找,看
是不是有更简洁的实现方法,如果没有再用SWT包去自己实现。比如JFace为对话框提供了
很好的支持,除了各种类型的对话框(比如上面用的MessageDialog,或是带有Title栏的对
话框),如要实现一个自定义的对话框也最好从JFace中的Dialog类继承,而不是从SWT中的
*.widget.Dialog继承。

应用JFace中的Preference包中的类很容易为自己的软件做出一个很专业的配置对话框。对
于Tree、Table等图形构件,它们在显示的同时也要和数据关联,例如Table中显示的数据
,在JFace中的View包中为此类构件提供了Model-View方式的编程方法,这种方法使显示与
数据分开,更加利于开发与维护。JFace中提供最多的功能就是对文本内容的处理。可以在
org.eclipse.jface.text.*包中找到数十个与文本处理相关类。

与应用程序更近一步
Java程序通常是以class文件的方式发布的,运行class需要JRE或JDK的支持。这又是Java
GUI程序的另一个致命的弱点,想象对一个面向广大用户的应用程序来说,无论你的程序
功能有多简单,或是你的代码十分的精简,你都不得不让用户去下载一个7、8M的JRE,那
是多么令人沮丧的一件事。而且对程序员来说,Class通常意味着源代码的暴露,反编译的
工具让那些居心叵测的人轻易得到你的源代码。虽然有很多对Class的加密方法,但那总是
以牺牲性能为代价的。好在我们还有其它的方式可用:把Class编译成exe文件!

参考资料3提供了应用GCJ将SWT编译成exe文件的详细方法。作为对那篇文章的补充,就是
目前也可以通过下载Cygwin得到Windows下的GCJ。 Excelsior也是笔者曾经用过的一款很
好的编译器,唯一的遗憾就是它的价格!

通过SWT开发包,简单、跨平台、可靠等这些Java语言本身所具有的优点正渐渐融合到图形
界面的应用程序开发中去。因此,我相信Java语言的另一扇成功之门正在逐渐打开。

参考资料:

http://www.e2one.com极一软件工作室应用SWT&JFace API开发出的企业实时通讯IM软件
,是SWT&JFace开发Standalone程序的范例。
http://www.eclipse.org/articles/swt-design-2/swt-design-2.html这篇文章中详细论
述了SWT开发者应该如何管理系统资源。
http://www-900.ibm.com/developerWorks/cn/linux/guitoolkit/j-nativegui/index.sh
tml 描述了如何将SWT程序应用GCJ编译成本机应用程序文件。
http://www-900.ibm.com/developerWorks/cn/java/l-eclipse/index.shtml介绍了Ecli
pse的使用及简单的插件开发过程。
www.eclipse.org 所有的关于Eclipse, SWT, JFace的问题都可以在这里找到答案。也是下
载Eclipse开发环境的网站。
http://www.cygwin.com/可以得得到Windows版本的GCJ。


关于作者
倪大鹏,有多年的软件开发经验,其中近四年的时间集中在Java相关技术的研究。可以通
过e-mail: ndp@e2one.com与他联系。
0 0

相关博文

我的热门文章

img
取 消
img