使用Eclipse进行SWT编程
1. 为什么要使用SWT?
SWT是IBM开发一套跨平台的GUI开发框架。为什么IBM要创建另一种GUI呢?为什么他们不使用现有的Java GUI框架呢?要回答这些问题,我需要回到Java的早期时代。
Sun已经创建了一套跨平台的GUI框架 AWT (Abstract Windowing Toolkit)。 这个AWT框架使用了本地窗口组件(native widgets)不过它存在LCD问题. 这个LCD问题导致了它失去主要的平台特性。换句话说,如果平台A有窗口组件1-40而平台B有窗口组件20-25,那么这个跨平台的AWT框架只能提供这两个集合的交集。
为了解决这个问题,Sun创建了一个新的框架使用模拟窗口组件(emulated widgets)来代替本地窗口组件(native widgets)。这个方法解决了LCD问题同时提供了丰富的窗口组件,不过也产生了其他的问题。例如,Swing应用程序不再和本地程序在样子上一致。虽然JVM有了很大的改善, 但是Swing应用程序仍然存在它们本地配对物所没有的性能问题。而且,Swing应用程序消耗了太多的内存,故不适合用于PDA和移动电话等小型设备。
IBM发现这些方法都无法满足他们的需求。因此,IBM创建了新的GUI库叫做SWT,它解决了AWT和Swing框架中的问题。SWT框架使用JNI访问本地窗口组件(native widgets),如果一个窗口组件在主机平台上不能获得,那么SWT会模拟这个不能获取的窗口组件。
2. 一个SWT应用程序的基础材料
Display, Shell和Widgets是一个SWT应用程序的基础材料。Displays用于管理事件循环(event loops)和控制UI线程和其他线程之间的通讯。Shell是应用程序中由操作系统窗体管理器来管理的窗体。任何SWT应用程序都需要至少一个Display实例和1个或更多的Shell实例。
图 1.不同视角看的SWT程序
图1 说明了一个SWT应用程序的不同看法。第一幅图是简化的UI对象的继承图。第二幅图是UI对象的包含结构。第三幅图就是创建了的UI。
如果一个应用程序使用多个线程,每个线程使用自己的Display对象实例。那么你可以通过使用静态的Display.getCurent()方法得到当前活跃的Display对象实例。
一个Shell表示一个特别操作系统中的一个窗口。一个Shell可以最大化,正常化,和最小化。有两种类型的shell。一种是顶层(top-level)Shell作为Display的主窗口创建的,另一种是依赖于其他shell的对话shell。
Shell的类型是由传给Shell构造函数的style位决定的。Shell的默认值是对话Shell。也就是说,如果没有传递任何值给构造函数参数,那么创建的是默认的对话Shell。如果一个Display对象作为参数,那么它是顶层(top-level)Shell。
有些窗口组件(widget)的属性必须在创建时设定。这些窗口组件(widget)属性叫做style bits。Style bits是在SWT类中定义的常量。如Button button = new Button( shell, <styleBits> )。当然可以通过或操作|来使用多个style bit。例如,要使用一个有边的按钮,你需要使用SWT.PUSH | SWT.BORDER作为style bit参数。
3. 环境设置
开发一个SWT应用程序与开发一个Swing应用程序不同。为了可以开始一个SWT应用程序的开发,你需要把SWT库加到classpath中,同时设置好对应的必要的环境变量。
第一个需要的库是swt.jar文件,它位于ECLIPSE_HOME/eclipse/plugins/org.eclipse.swt.win32_2.1.0/ws/win32目录。根据你使用的Eclipse版本,你可能需要使用不同的目录。这个swt.jar文件必须加到你的classpath中,为此到Project->Properies->JavaBuildPath->Libraries->Add Variable -> Eclipse Home ->Extend并按上述路径选择swt.jar库,然后单击OK。
然后,你可以编译SWT应用程序,但是由于抛出下边所示的运行时异常,无法运行它,因为swt.jar使用了本地库。你需要设置java.library.path环境变量来在Java中使用本地库。
Console output |
java.lang.UnsatisfiedLinkError: no swt-win32-2133 in java.library.path |
要设置java.library.path变量,到Run-> Run...-> Java Applicaton-> New ->Arguments -> VM Arguments。然后,如何需要,如下修改path,把它粘贴到VM Arguments部分。-Djava.library.path=c:/eclipse/plugins/org.eclipse.swt.win32_2.1.0/os/win32/x86
装载本地库 |
|
如果你需要装载应用程序使用的任何本地库,你可以使用Runtime.getPlatform.loadLibrary("libraryname")方法。 |
完成这些步骤,你就可以在你的eclipse环境下运行SWT程序了。
4. 你的第一个SWT应用程序
创建一个典型的SWT应用程序需要一下步骤:
- 创建一个Display
- 创建一个或多个Shells
- 设置Shell的Layout manager
- 创建Shell中的widgets
- 开启Shell窗口
- 写一个事件转发循环
- 销毁display
你可以使用下边的代码模板来快速的运行本文中的代码片断。你可以复制粘贴这些代码至合适的区域。如源代码1所示:
源代码 1. SWT应用程序模板 |
import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell;
public class SliderExample { public static void main(String args[]) { Display display = new Display(); Shell shell = new Shell(display); shell.setLayout( new RowLayout()); // ------------------------ // Your code comes to here. // ------------------------ shell.pack(); shell.open(); while( !shell.isDisposed()) { if(!display.readAndDispatch()) display.sleep(); } display.dispose(); } } |
这个例子显示了一个空的窗口。你可以加widget到上边的模板。任何SWT应用程序需要 一个Display和一个或多个Shells。这个Shell是个合成对象;它可以容纳其他的合成对象。如果没有设置shell的 layout,加到Shell的widget是不能看见的。Shell窗口必须 打开才能显示。 事件处理循环读取并转发GUI事件。如果没有事件处理循环应用程序窗口是无法显示的。即使通过open()方法来打开Shell窗口。让后,需要在Shell被丢弃的时候销毁Display。
导入需要的库 |
|
你可以使用Source->Organize Imports菜单或者Ctrl+Shift+O来自动导入需要的库。 |
5. 在Eclipse外运行SWT应用程序
要在不使用Eclipse的环境下运行SWT应用程序,swt.jar库必须在classpath中,并且java.library.path环境变量必须正确设置。根据主机平台,必须有合适的本地库存在。对于Windows平台,你可以根据如下来完成本地库的配置:
- 把swt.dll放在和程序相同的文件夹下。
- 把swt.dll放在JAVA_HOME/bin/文件夹下。
- 把swt.dll放在c:/windows/system32文件夹下。
javac -classpath c:/swt/swt.jar HelloWorld.java
Java -classpath c:/swt/swt.jar;. -Djava.library.path=c:/swt HelloWorld
java.library.path是JNI必需的环境变量。如果你没有设置这个环境变量。你的DLL 类是不能访问的。在这种情况下,应用程序不能正常的运行,并会抛出异常。
SWT库 |
|
Swt库存在于Eclipse的plug-in目录下。如果你想不下载整个Eclipse包而获得SWT库,你可以在http://www.eclipse.org/downloads目录下单独下载这个SWT库。 |
6. SWT包(Packages)
SWT主要由下列包组成。这些包的定义是从Eclipse的API文档上得到的。你可以在Eclipse的网站上获得整个API文档。
org.eclipse.swt: 包含了定义了SWT需要使用的常量和异常的类。这个包由三个类组成:SWT, SWTException和SWTError。SWT类可能是最受欢迎的类,因为它包含了SWT库所需要的常量如键盘、错误、颜色、布局、文本样式、按钮等常量。
org.eclipse.swt.widgets: 包含了大多数核心SWT窗口组件,包括支持的接口和类。
org.eclipse.swt.events: 定义了SWT组件使用的typed events, listeners和events。这个包有三组不同的类:Listener接口,Adapter类和Event类。
org.eclipse.swt.dnd: 包含了对SWT窗口组件拖放(drag-and-drop)支持的类。
org.eclipse.swt.layout: 包含了提供对SWT窗口组件自动安置和大小控制的类。
org.eclipse.swt.print: 包含了对SWT窗口组件提供打印支持的类。
org.eclipse.swt.graphics: 这个包提供了实现基本绘图操作需要的点,长方形、区域,颜色,光标,字体,图像上下文(GC)等的类和包含了显示图像代码和载入/保存他们的公有API的图像类。
7. 对话框
对话框的实现是本地的。也就是说,对话框像窗口组件一样是平台组件。SWT的Dialogs是从Dialog抽象类继承的。对话框不是窗口组件但是他可以容纳窗口组件。
图 2. Dialog类层次图.
SWT有不同类型的对话框。有些对话框具有特殊的属性。Dialog类可以如源代码 2中使用。
源代码2. MessageBox例子 |
MessageBox messageBox = new MessageBox(shell, SWT.OK|SWT.CANCEL); if (messageBox.open() == SWT.OK) { System.out.println("Ok is pressed."); } |
每个对话框的open()方法返回不同的类型。例如, MessageBox对话框从open()方法返回int类型。因此,需要编写不同的条件来处理每一个对话框的返回值。
ColorDialog显示一个色彩选择调色板。它从return方法返回一个RGB对象。
DirectoryDialog使你可以选择一个目录。它从open()方法返回一个字符串。返回值就是选择的目录。当然,也可以设置额外的过滤器来过滤有些目录。
FontDialog使用户能够从系统所有的字体中选择一种字体。它从open()方法返回一个FontData对象。
FileDialog使用户能够选择一个文件。另外,可以设置扩展名过滤器,路径过滤器和文件名过滤器。对话框有如表1所示的样式:
表1. SWT对话框style bit常量 |
|
SWT.OPEN |
Shows Open button in the dialog |
SWT.SAVE |
Shows Save button in the dialog |
PrintDialog使用户在打印之前选择打印机。它从open()方法返回一个PrinterData对象。
MessageBox用于给用户回馈信息。你可以使用或(|)操作来联合不同的样式,如源代码3所示:
源代码3. MessageBox例子 |
MessageBox messageBox = new MessageBox(shell, SWT.OK| SWT.CANCEL| SWT.ICON_WARNING); messageBox.setMessage("www.korayguclu.de"); messageBox.open(); |
图3就是上边例子的运行消息框。
图 3. MessageBox对话框
可用的按钮常量如下所列。使或操作可以完成不同按钮的联合。SWT框架按照style bits创建对话框。按钮常量有:SWT.ABORT, SWT.OK, SWT.CANCEL, SWT.RETRY, SWT.IGNORE,SWT.YES和SWT.NO。
表2 显示了对话框可以使用的图标。
表2. SWT图标style bit常量 |
|
SWT.ICON_ERROR |
|
SWT.ICON_INFORMATION |
|
SWT.ICON_QUESTION |
|
SWT.ICON_WARNING |
|
SWT.ICON_WORKING |
8. Widgets
SWT GUI对象是从Widget和Control类继承而来的。Widget对象是基类定义了GUI类的通用方法。Control类是所有窗口GUI类的基类也就是说从Control类继承的组件需要一个窗口或者对话框来显示。
Menu对象也需要一个窗口来显示,但是这个需求间接得到了满足。一个Menu对象需要一个Control对象。
图4. Widget类层次图
图 4 显示了Widget的类层次图。Widget,Item,ScrollBar和Control类是抽象类。
8.1. Widget事件
Widget事件总结表3。简化起见,表中只包含了事件名称。我们可以很容易的使用<EventName>Event 想出事件类的名称。同样地,使用<Listener Name>Listener得到相关联地Listener地名称。并不是每一事件都有相配地Adapter类。所以,有adapter地事件用粗体表明。我们可以通过<EventName>Adaptor 来得到对应地adapter名字。
例子:
事件名称是Control,事件类就是ControlEvent,listener类是ControlListener,adaptor 类是ControlAdaptor。
表3. SWT Events |
||
事件名称 |
窗口组件 |
产生的时间 |
Arm |
MenuItem |
一个菜单项加亮时 |
Control |
Control, TableColumn, Tracker |
一个控件被改变大小或者移动时 |
Dispose |
Widget |
窗口组件被销毁时 |
Focus |
Control |
一个控件得到或者失去焦点时 |
Help |
Control, Menu, MenuItem |
用户需要帮助时(例如按F1键) |
Key |
Control |
当控件得到键盘焦点并且一个键按下或者释放时 |
Menu |
Menu |
菜单显示或者隐藏时 |
Modify |
Combo, Text |
窗口组件的文本被修改时 |
Mouse |
Control |
在控件范围内鼠标被按下,释放或者双击时 |
MouseMove |
Control |
鼠标越过控件 |
MouseTrack |
Control |
鼠标进入,离开或者盘旋在控件上方 |
Paint |
Control |
控件需要被重绘时 |
Selection |
Button, Combo, CoolItem, List, MenuItem, Sash, Scale, ScrollBar, Slider, StyledText, TabFolder, Table, TableColumn, TableTree, Text, ToolItem, Tree |
一个Item在控件中被选择时 |
Shell |
Shell |
Shell被最小化,最大化,激活,钝化或者关闭时 |
Traverse |
Control |
控件被遍历(tabbed)时 |
Tree |
Tree, TableTree |
一个tree item被收缩或者展开时 |
Verify |
Text, StyledText |
窗口组件的文本将要被修改时 |
8.2. 有用的窗口组件
图 5. Control类层次图
所有的Control类都可以有边框。你可以使用SWT.BORDER常量来给控件类增加边框。
SWT style常量 |
|
一般都需要标明样式常量 (style bit).如果你不知道用哪个常量或者你不想要标明它,你可以使用SWT.NULL。 |
8.2.1. 按钮
按钮可以具有不同的样式。按钮的样式由style bit决定。表4显示了按钮列表和他们的样式常量。
Table 4. SWT按钮style bit常量和例子 |
||
常量 |
例子 |
描述 |
SWT.ARROW |
一个按钮用于显示弹出对话框。箭头的方向由alignment常量决定。 |
|
SWT.CHECK |
选择框,可以是图像。 |
|
SWT.PUSH |
一个按钮 |
|
SWT.RADIO |
在group中可以使用的单选按钮 |
|
SWT.TOGGLE |
同SWT.PUSH类似, 但是它可以保持按下状态直到第二次单击。 |
8.2.2. 滑块(Slider), 标尺(Scale)和进度条(ProgressBar)窗口组件
标尺(Scale)表示一段可选择的连续值。范围可以用Scale类的setMinimum() 和setMaximum()方法设定。并可以使用getSelection()方法得到所选择的值。标尺在一次只有一个选择的值。也就是说,没有多选的可能。
图 6. 包含在一起的滑块和标尺
根据传递给构造函数的参数值的不同,我们可以创建不同的滑块和标尺。滑块和标尺常量如表5所示:
表 5. SWT滑块和标尺的样式常量 |
|
SWT.HORIZONTAL |
显示水平或者垂直组件 |
随意地,你可以使用SWT.BORDER常量来在标尺周围创建边框。这个常量对滑块没有作用。
Source 4. Slider widget example |
final Slider slider = new Slider(shell,SWT.VERTICAL); slider.setMinimum(0); slider.setMaximum(100); slider.setIncrement(5); slider.setPageIncrement(10); slider.setSelection(25); slider.addSelectionListener( new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { System.out.println("Selection:"+ slider.getSelection()); } } ); |
进度条组件类似于滑块和标尺组件,但是它不能被选择。它用于显示一个任务的进度。你可以对进度条组件使用SWT.SMOOTH and SWT.INTERMINATE常量。
8.2.3. 文本窗口组件
一个文本窗口组件可以用于显示或者编辑文本。另外,你可以使用StyledText窗口组件来用不同的字体和颜色显示文本。StyledText窗口组件允许设置前景、背景色和指定范围文本块的字体。
图 7. Text窗口组件
我们可以使用表6所列的常量来创建Text窗口组件。因此,SWT.H_SCROLL和SWT.V_SCROLL常量可用于对Text窗口组件增加滚动条。
表 6. SWT Text style bit常量 |
|
SWT.MULTI |
显示单行还是多行窗口组件 |
SWT.READ_ONLY |
创建只读的组件 |
SWT.WRAP |
文本自动换行 |
源代码 5是一个简单的使用Text组件的例子.
Source 5. Text widget example |
Text text = new Text(shell, SWT.MULTI|SWT.WRAP); |
8.2.4. 列表(List)窗口组件
List窗口组件可用于显示可选择的字符串列表。在选择的情况下,List对象会发送事件通告给它的listeners。这种类型的选择可以是单选,也可以是多选。选择的类型是由SWT.SINGLE和SWT.MULTI常量决定的。List窗口组件是可滚动的组件。因此SWT.H_SCROLL和SWT.V_SCROLL常量可用于给Text窗口组件增加滚动条。
图 8. List 窗口组件
下边的代码片断显示了一个简单的List窗口组件。
Source 6. List example |
final List list = new List(shell,SWT.MULTI); for (int i = 1; i < 11; i++) { list.add(i+".)www.korayguclu.de"); } list.addSelectionListener( new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { List list = (List) e.getSource(); String[] str = list.getSelection(); for (int i = 0; i < str.length; i++) { System.out.println("Selection: "+str[i]); } } } ); |
8.2.5. 窗框组件
窗框组件可用于在可改变的区域里显示组合窗口组件。下图就是一个窗框组件例子。
图 9. 窗框组件
下边是一个基本的窗框组件例子:
Source 7. Sash example |
Button button = new Button(shell,SWT.PUSH); Sash sash = new Sash(shell, SWT.VERTICAL); Button button1 = new Button(shell,SWT.PUSH); |
8.3. 组合窗口组件Composite Widgets
组合窗口组件可以容纳其他的组合窗口组件。Composite类是组合窗口组件的父类。
图 10. Composite可以容纳其他的组合类
组合类可以容纳其他的组合类。这种容纳关系是用一个组合窗口组件类的构造函数来建造的。与Swing相比,SWT没有add() 方法;作为代替,你必须使用构造函数来建立包容关系结构。
正如图10中所看到的,Shell类也是一个组合类。也就是说,Shell对象,可以容纳其他的组合类。
组合类是可以卷起的,也就是说可以使用SWT.H_SCROLL和SWT.V_SCROLL常量给组合窗口组件加上滚动条。
8.3.1. Table窗口组件
Table窗口组件可以显示一批字符串项或者图片。与其他的组合窗口组件相比,不能给table窗口组件增加组合控件。Table窗口组件的子构件必须是TableItem类型的。
图 11. Table窗口组件
表7中的常量可以用于table窗口组件
表 7. SWT Table style bit常量 |
|
SWT.MULTI |
使能够进行单一或者多项选择 |
SWT.FULL_SELECTION |
使能够进行行全选 |
SWT.CHECK |
在每一行的开始显示一个选择框 |
源代码 8 中的代码片断表明了含有两列的table组件的使用方法
源代码 8. Table窗口组件例子 |
final Table table = new Table(shell,SWT.SINGLE); TableColumn col1 = new TableColumn(table,SWT.LEFT); col1.setText("Coloumn 1"); col1.setWidth(80); TableColumn col2 = new TableColumn(table,SWT.LEFT); col2.setText("Coloumn 2"); col2.setWidth(80);
TableItem item1 = new TableItem(table,0); item1.setText(new String[]{"a","b"}); TableItem item2 = new TableItem(table,0); item2.setText(new String[]{"a","b"});
table.setHeaderVisible(true); table.setLinesVisible(true); |
8.3.2. Combo(组合框)窗口组件
Combo窗口组件允许用户从值列表中选择一个值或者随意输入一个新值。组合框(combo)类似于列表组件,却使用了有限的空间。
虽然组合框是组合性质的,但是对它增加子元素是没有意义的。它的元素必须是String类型。一元素可以使用Combo类中定义的add(String element)方法来添加到组合框中。
图 12. 不同样式的组合框
下边的SWT常量可以用于Combo窗口组件:
Table 8. SWT Combo style bit constants |
|
SWT.DROP_DOWN |
下拉式组合框 |
SWT.READ_ONLY |
只读组合框 |
SWT.SIMPLE |
简单的组合框(非下拉式组合框)。如图11所示 |
下边的例子说明了Combo组件的使用:
Source 9. Combo example |
final Combo combo = new Combo(shell,SWT.DROP_DOWN); for (int i = 1; i < 11; i++) { combo.add(i+".) element "); } combo.setText("Text"); combo.addSelectionListener( new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { System.out.println("Selection:"+ combo.getText()); } } ); |
8.3.3. Tree窗口组件
Tree窗口组件描绘了树中项的可选择层次结构。虽然Tree类是组合的,但是不能对它增加组合类对象。Tree类的子项必须是ThreeItem类型。
图 13. 不同样式的Tree窗口组件
下表是Tree组件常量的列表。
表 9. SWT Tree style bit 常量 |
|
SWT.SINGLE |
允许单选或者多选 |
SWT.CHECK |
在每个节点的开始显示一个选择框。 |
下边是一个简单的Tree组件例子。
源代码 10. Tree例子 |
final Tree tree = new Tree(shell,SWT.SINGLE); for (int i = 1; i < 11; i++) { final TreeItem item1 = new TreeItem(tree,SWT.NULL); item1.setText("node "+i); for (int j = 1; j < 6; j++) { final TreeItem item11 = new TreeItem(item1,SWT.NULL); item11.setText("node "+i+"."+j); } }
tree.addSelectionListener( new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { System.out.println("Selection:"+ tree.getSelection()[0]); } } ); |
8.3.4. TabFolder
TabFolder组件允许用户从一些页面中选择一页。虽然它是组合的,但是它不能增加其他的组合窗口组件。加到TabFolder的组件必须是TabItem类型。Tab的内容可以使用TabItem的setControl(Control control) 方法来设置。
图 14. TabFolder窗口组件
一个简单的TabFolder例子:
源代码 11. TabFolder例子 |
final TabFolder tabFolder = new TabFolder( shell, SWT.BORDER); for (int i=1; i<5; i++) { // create a TabItem TabItem item = new TabItem( tabFolder, SWT.NULL); item.setText( "TabItem " + i); // create a control Label label = new Label( tabFolder, SWT.BORDER); label.setText( "Page " + i); // add a control to the TabItem item.setControl( label ); } |
8.3.5. CoolBar窗口组件
CoolBar窗口组件提供了一个可以在动态摆放空间上增加项的区域。你可以增加一个或多个ToolBar组件到CoolBar上。一个CoolBar可一容纳一个或多个CoolItems。虽然是一个组合窗口组件,但是不能把其他组合类加到它上。CoolBar的子元素必须是CoolItem类型。
图 15. Coolbar窗口组件
CoolBar窗口组件使用的例子:
Source 12. CoolBar example |
CoolBar coolBar = new CoolBar(shell, SWT.BORDER); coolBar.setLayoutData( new FillLayout()); // create a tool bar which it // the control of the coolItem for (int k = 1; k <3; k++) { ToolBar toolBar = new ToolBar(coolBar, SWT.FLAT); for (int i = 1; i < 5; i++) { ToolItem item = new ToolItem(toolBar, SWT.NULL); item.setText("B"+k+"."+i); } // Add a coolItem to a coolBar CoolItem coolItem = new CoolItem(coolBar, SWT.NULL); // set the control of the coolItem coolItem.setControl(toolBar); // You have to specify the size Point size = toolBar.computeSize( SWT.DEFAULT, SWT.DEFAULT); Point coolSize = coolItem.computeSize (size.x, size.y); coolItem.setSize(coolSize); } |
8.4. 具有子项控件的概述
有些控件接受子组件作为子项。如,一个组合组件接受组合组件。有些组件只需要item(项)这样的组件如表10所列:
表 10. 有项的组件 |
||
窗口组件 |
项 |
描述 |
CoolBar |
CoolItem |
项是可选择的,动态的摆放在CoolBar的区域上。 |
Menu |
MenuItem |
项是菜单下的选择。 |
TabFolder |
TabItem |
项是TabFolder中的Tab |
Table |
TableItem |
项是表中的行。 |
ToolBar |
ToolItem |
项是工具栏上的按钮。 |
Tree |
TreeItem |
项是树上的节点。 |
结论
SWT是Eclipse用户接口的核心部分。Eclipse平台基于SWT库。要扩展你的SWT知识,你可以下载SWT网站上的SWT例子。