在上一章中,介绍了一个完整的插件的开发,相信读者已经掌握了插件开发的基本知识。不过我们只是介绍了每一步怎么去做,却没有介绍为什么要这么做以及相关的知识点。本章将会把插件开发相关的常用知识学习一遍。插件开发涉及到的知识是非常多的,这些知识都可以通过阅读Eclipse的帮助文档以及阅读开源项目的代码获得,所以这里没有必要去介绍每一个细节,我们只会把知识点介绍一下,读者无须强行记忆,只要知道这一章是做什么的,并且遇到问题时能去查询相关的资料就可以了。
3.1 程序界面的基础——SWT/JFace
AWT、Swing是Java标准库中的图形化界面框架,但是由于其在性能、稳定性、美观性等方面有很多问题,导致用Java开发出来的成熟GUI项目非常少,而且即使是成熟的Java GUI系统,比如JBuilder、NetBeans等的界面也是遭人诟病的。在IBM开始开发Eclipse的时候,开发人员都强烈反对使用Swing开发Eclipse,因此IBM决定采用Smalltalk GUI的实现方式来开发一个新的Java界面框架,这个框架也就是SWT。
与AWT/Swing不同,SWT底层采用的是JNI native调用本地操作系统的API,只有本地操作系统实现不了的部件才去自己绘制,因此SWT的效率是非常高的。
经过不断成熟和发展,SWT现在已经成为与Eclipse无关的开发包了,也就是用SWT做出来的程序只要没有调用Eclipse平台的东西,那么它就可以脱离Eclipse运行。由于本章中讲到的SWT界面都是运行在Eclipse中的,因此我们不再介绍单独运行SWT程序的方法。
SWT是对操作系统GUI API的封装,因此没有做更多应用层次的封装,比如要显示一个对话框,就要自己去画【确定】、【取消】按钮,要弹出消息对话框就要自己去写数行代码。为了简化SWT的开发,IBM开发出了JFace,JFace不是与SWT格格不入的,JFace就是调用SWT实现了更多实际应用开发中要用到的公共类。SWT和JFace的关系就像Windows开发中Windows SDK和MFC的关系一样,我们在开发的时候应当尽量去使用JFace的东西,只有当JFace的东西不满足我们要求的时候才去直接求助SWT。
3.1.1 SWT的类库结构
SWT的所有类都在org.eclipse.swt包下。最重要的类就是Widget,它是所有界面对象的基类,类图如图3.1所示。
Widget的直接子类有Caret(插入光标)、Menu(菜单)、ScrollBar(滚动条)、Tray(系统托盘图标)等。Widget的子类Item下的类是一些无法独立于其他部件的部件,比如MenuItem(菜单项)、TableItem(表格项)、TrayItem(系统托盘图标项)、TreeItem(树项)等。Widget的子类Control是一个比较庞大的基类,大部分SWT部件都在此类下,其直接子类有Button(按钮)、Label(标记)、ProgressBar(进度条)等。Control的子类Scrollable是所有可以带滚动条的对象的基类,比如Text(文本框)、List(列表框)等。Scrollable的子类Composite是SWT中一个重要的类,它是所有可以容纳其他部件的类的基类,其子类有Browser(浏览器)、Combo(下拉列表框)、Group(组合框)、Table(表格)、Tree(树)等。

图3.1 SWT的类结构图
上面从类层次的角度研究了SWT的类结构,下面再来看一下SWT的包结构:
l org.eclipse.swt下有SWT,SWTException和SWTError类。SWT中定义了SWT中的公共常量,包括部件风格、消息常量等;SWTException和SWTError则是SWT中异常的基类。
l org.eclipse.swt.widgets包下定义了常用、核心SWT窗口小部件(widget)的公有API类定义。如Display、Shell、Button、Menu等。一般编写GUI程序用到的Widget大部分都在这个包下。
l org.eclipse.swt.events包中提供了对SWT事件监视器(Event Listener)的支持,如Button的SelectionListener、Mouse的MouseListener、MouseMoveListener和MouseTrackListener等,还有与这些Listener对应的Adapter实现类和 Event类。
l org.eclipse.swt.layout包中定义了SWT的布局管理器,其中有FillLayout、GridLayout和RowLayout三种。
l org.eclipse.swt.graphics包中包含了SWT中graphic类,如Color、Font和Image等,这个包下的类的资源管理方式和其他部件略有不同,3.1.2节中将会介绍。
l org.eclipse.swt.printer提供了对打印的支持。
l org.eclipse.swt.custom包中包含了一些可自定义的窗口小部件,它们是学习开发自定义SWT部件的很好的例子。
l org.eclipse.swt.dnd提供了对拖放操作的支持。
3.1.2 SWT中的资源管理
AWT/Swing的资源管理使用的是Java提供的垃圾回收机制,但是由于GUI是非常消耗资源的,要求对象不被使用的时候立即被回收,而垃圾回收机制是无时间保证的。这对系统资源的处理会是致命的,比如程序在一个循环语句中去加载数万张图片,对其进行加盖印章处理后再进行保存,常规的处理方式是每次调入一张,修改保存,然后就立即释放该图片资源,而后再循环调入下一张图片,这对操作系统而言,任何时刻程序占用的仅仅是一张图片的资源。但如果资源管理完全交给垃圾回收机制去处理,也许会是在循环语句结束后,JVM才会去释放图片资源,其结果可能是你的程序还没有运行结束,系统就已经内存溢出了。而SWT则创新性地抛弃了Java提供的垃圾回收机制,让资源由开发者进行生命周期管理,即显式地释放已经分配的任何操作系统资源(调用dispose方法释放资源)。其法则就是:①如果您创建对象,则您必须销毁它;②父部件被销毁,子部件也同时被销毁。具体实施起来有如下规则:
l 如果使用构造函数来创建图形对象或窗口小部件,使用完时必须显式地将其销毁。
l 当调用一个包含子部件的部件(即Composite的子类)的dispose方法时,将递归地调用其所有子部件的dispose方法。因此无需手动去释放这些子部件的资源。
l 如果部件不是您创建的,而是调用其他类的某个方法得到的,则不要将其除去,这是因为它不是您创建的。
很多开发人员在看到“父部件被销毁,子部件也同时被销毁”这一条的时候就认为自己又不用去管理资源的释放工作了,因为只要销毁根部件,那么所有的子部件就都会被销毁了。这是十分危险的误解,因为SWT中还有一部分资源不是继承自Widget的,也就是不可能有父部件,这样就需要程序员手动去释放资源。例如org.eclipse.swt.graphics下的类,这些类都继承了Resource类,Resource中也定义了dispose方法。这些类有Color(颜色)、Cursor(鼠标指针)、Font(字体)、GC(图形设备设置)、Image(图片)等。比如您为按钮设定了一种字体,那么必须在销毁这个按钮的时候手动去释放字体对象。
当然并不是所有的SWT对象都需要程序员去释放的,比如org.eclipse.swt.graphics包下的Point(点)、Rectangle(矩形)、RGB等类是没有dispose方法的,因此只有把它们交给JVM的垃圾回收机制去管理了。
3.1.3 在非用户线程中访问用户线程的GUI资源
在非用户线程中对用户线程的GUI资源进行访问的时候,如果不进行同步的话就会造成不可预料的问题。AWT/Swing中并没有强制在非用户线程中访问用户线程的GUI资源的时候要进行同步,而SWT则进行了同步控制,这样就可以预防这些不可预料的问题。在SWT中,通常存在一个被称为“用户线程”的唯一线程,只有在这个线程中才能调用对组件或某些图形API的访问操作。如果在非用户线程中程序直接调用这些访问操作,那么SWTExcepiton异常会被抛出。
下面看一个例子:
Runnable r = new Runnable() {
public void run()
{
for (int i = 0; i < 100; i++)
{
try
{
wait(1000);
} catch (InterruptedException e)
{
}
text.setText(new Integer(i).toString());
}
}
};
我们启动一个线程,在这个线程中,每隔一秒为界面文本控件赋值一次,运行后就会抛出SWT异常。
解决这个问题的方法也是非常简单的,那就是通过Display类的syncExec(Runnable)和asyncExec(Runnable)这两个方法去实现:
Runnable r = new Runnable() {
public void run()
{
for (int i = 0; i < 100; i++)
{
try
{
wait(1000);
} catch (InterruptedException e) { }
final int j = i;
display.asyncExec(new Runnable() {
public void run()
{
text.setText(new Integer(j).toString());
}
});
}
}
};
方法syncExec()和asyncExec()的区别在于前者要在指定的线程执行结束后才返回,而后者无论指定的线程是否执行都会立即返回到当前线程。
3.1.4 访问对话框中的值
在程序中经常会弹出一些对话框,要求用户输入一些值,然后根据用户输入的值来进行后续操作。比如在程序中弹出一个对话框要求用户输入姓名和国家,然后根据输入的值显示问候语。
定义SettingDialog类:
public class SettingDialog extends Dialog
{
private Text txtName;
private Text txtCountry;
...
public String getName()
{
return txtName.getText();
}
public String getCountry()
{
return txtCountry.getText();
}
}
主界面:
SettingDialog dlg = … ;
dlg.open();
if(dlg.open()==Window.OK)
{
MessageDialog.openInformation(shell, "", dlg.getName()+" is from "+
dlg.getCountry());
}
当运行的时候就会抛出如下异常:
org.eclipse.swt.SWTException: Widget is disposed
这是为什么呢?
让我们来看一下org.eclipse.jface.window.Window类,它是SettingDialog的间接父类,在Window类的close方法中将界面控件全部销毁掉了,当我们关闭一个界面的时候就调用了close方法,这样当窗口已经关闭的时候,我们再去调用dlg.getName()的话,getName方法就会去访问txtName控件,可是txtName已经被销毁掉了,不能被访问了,所以就抛出了Widget is disposed这个异常消息。
那么我们应该怎么修改呢?既然不能在窗口关闭以后访问界面控件对象,那么只有在关闭之前来把要访问的控件值提前保存起来了。做如下修改:
public class SettingDialog extends Dialog
{
private Text txtName;
private Text txtCountry;
private String name;
private String country;
...
protected void okPressed()
{
name = txtName.getText();
country = txtCountry.getText();
super.okPressed();
}
public String getName()
{
return name;
}
public String getCountry()
{
return country;
}
}
当单击【确定】按钮的时候,okPressed方法会被调用,我们在调用父类的okPressed之前将控件的值保存起来就可以了,并且改写了get方法,让它返回我们保存的值。
3.1.5 如何知道部件支持哪些style
SWT中的部件的构造函数都有一个int style参数,这个参数表示要创建的部件的风格。
这个参数的类型是整数而非枚举,那么如何确定它支持哪些值呢?答案就是查看JavaDoc。以Text部件为例,查看Text的源码,定位到它的构造函数处,如图3.2所示。

图3.2 构造函数的JavaDoc
JavaDoc中明确地指出支持如下的风格:
SWT.SINGLE、SWT.MULTI、SWT.READ_ONLY、SWT.WRAP。
SWT中定义的常量都是掩码形式的,比如:
public static final int MULTI = 1 << 1; //即二进制的10
public static final int WRAP = 1 << 6; //即二进制的1000000
可以用“或”操作符来进行风格的组合,比如指定文本框为多行并且自动换行,只要如下调用即可:
Text txt = new Text(parent,SWT.MULTI|SWT.WRAP);







