转自 http://blog.csdn.net/guijava/article/details/1802680
一、SWT 简介
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已经开发到了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 Build 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编译例子程序。编译无错后可运行java -Djava.library.path=${你的SWT本地库文件所在路径} com.e2one.example.OpenShell,比如SWT-WIN32-2116.DLL件所在的路径是C:/swtlib,运行的命令应该 是java -Djava.library.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,Label,Sash,Table等 等。其中两个最重要的构件当数Shell和Composite。Shell相当于应用程序的主窗口框架,上面的例子代码中就是应用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接口和相应的事件对象,例如常用的鼠标事件监听接口MouseListener,MouseMoveListener和 MouseTrackListener,及对应的事件对象MouseEvent。
*.graphics包中可以找到针对图片、光标、字体或绘图的API。比如可通过Image类调用系统中不同类型的图片文件。通过GC类实现对图片、构件或显示器的绘图功能。
对不同平台,Eclipse还开发了一些富有针对性的API。例如,在Windows平台,可以通过*.ole.win32包很容易的调用ole控件,这使Java程序内嵌IE浏览器或Word、Excel等程序成为可能!
更复杂的程序
下面让我们展示一个比上面例子更加复杂一些的程序。这个程序拥有一个文本框和一个按键,当用户点击按键的时候,文本框显示一句欢迎信息。
为了文本框和按键有比较合理的大小和布局,这里采用了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.GRAB_VERTICALGridData.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.MULTISWT.WRAP);
//为文本框指定一个布局结构对象,
这里让文本框尽可能的占满Panel的空间。
GridData gTextData = new GridData (GridData.GRAB_HORIZONTAL GridData.GRAB_VERTICALGridData.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()方法,应用第二条法则,那么parent和child构件的dispose()方法也会被SWT API自动调用,它们也随之销毁。
线程问题
在任何操作平台的GUI系统中,对构件或一些图形API 的访问操作都要被严格同步并串行化。例如,在一个图形界面中的按键构件可被设成可用状 态(enable)或禁用状态(disable),正常的处理方式是,用户对按键状态设置操作都要被放入到GUI系统的事件处理队列中(这意味着访问操作 被串行化),然后依次处理(这意味着访问操作被同步)。想象当按键可用状态的设置函数还没有执行结束的时候,程序就希望再设置该按键为禁用状态,势必会引 起冲突。实际上,这种操作在任何GUI系统都会触发异常。
Java语言本身就提供了多线程机制,这种机制对GUI编程来说是不 利的,它不能保证图形构件操作的同步与串行化。SWT采用了一种简单而直 接的方式去适应本地GUI系统对线程的要求:在SWT中,通常存在一个被称为"用户线程"的唯一线程,只有在这个线程中才能调用对构件或某些图形API的 访问操作。如果在非用户线程中程序直接调用这些访问操作,那么SWTExcepiton异常会被抛出。但是SWT也在*.widget.Display类 中提供了两个方法可以间接的在非用户线程的进行图形构件的访问操作,这是通过的syncExec(Runnable)和asyncExec (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版本之前,很难将JFace API完整的从Eclipse的内核API中剥离出来,总是要多多少少导入一些非JFace以外的Eclipse核心代码类或接口才能得到一个没有任何编 译错误的JFace开发包。但目前Eclipse组织似乎已经逐渐意识到了JFace在开发独立应用程序起到的重要作用,在正在开发的2.1版本中, JFace也开始变成了和SWT一样的完整独立的开发包,只是这个开发包还在变动中(笔者写本文时,应用的Eclipse2.1M3版本)。JFace开 发包的包前缀是以org.eclipse.jface开头。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文件!
通过SWT开发包,简单、跨平台、可靠等这些Java语言本身所具有的优点正渐渐融合到图形界面的应用程序开发中去。因此,我相信Java语言的另一扇成功之门正在逐渐打开。
二、swt和jface 如何创建简单的 SWT 应用程序
在这篇 SWT 和 JFace 系列的第一篇文章中,可以了解如何使用 Java™、Eclipse 以及 SWT 和 JFace 库创建简单的 SWT 应用程序。 还可以了解如何使用基本的控件和布局创建简单的 SWT GUI。
可 以使用标准窗口小部件工具 箱(Standard Widget Toolkit,SWT)和 JFace 库来开发用于 Eclipse 环境的图形用户界面,而且还可以将它们用于开发单独的 GUI 本机应用程序。在本文中,我将介绍一些基本的 SWT(基本 GUI 对象的名称)类型,并展示如何综合使用它们来创建有用的应用程序。
正如 Eclipse 的 Web 站点上所提到的,Eclipse 是一种通用工具平台。它是一个开放的、可用于任何东西的可扩展 IDE,没什么特别之处,它为工具开发人员提供了灵活性以及对软件技术的控制。
Eclipse 为开发人员提供了生产大量 GUI 驱动的工具和应用程序的基础。而这项功能的基础就是 GUI 库 SWT 和JFace。
SWT 是一个库,它创建了Java 版的本地主机操作系统 GUI 控件。它依赖于本机实现。这意味着基于 SWT 的应用程序具有以下几个关键特性:
- 它们的外观、行为和执行类似于“本机”应用程序。
- 所提供的窗口小部件(widget)反映了主机操作系统上提供的窗口小部件(组件和控件)。
- 主机 GUI 库的任何特殊行为都在 SWT GUI 中得到反映。
这些目标使得 SWT 不同于 Java 技术的 Swing,Swing 的设计目标是消除操作系统的差异。
SWT 库反映了主机操作系统的基本窗口小部件。在许多环境下,这种方法太低级。JFace 库有助于向 SWT 应用程序中添加大量服务。JFace 并没有隐藏 SWT,它只是扩展了 SWT。正如您将在这一系列的后面部分中看到的,SWT 最重要的扩展之一是,将应用程序的数据模型与显示及更改它的 GUI 隔离开来。
|
在开始之前,我需要介绍一些 SWT 术语:
- Widget —— 基本的 SWT GUI 组件(类似于 Java AWT 中的Component 和 Swing 中的 JComponent)。Widget 是一个抽象类。
- Control —— 拥有操作系统的对等物的窗口小部件(换句话说,在操作系统中具有同一身份)。Control 是一个抽象类。
- Composite —— 包含其他控件的控件(类似于 Java AWT 中的 Container 和 Swing 中的 JPanel)。
- Item —— 其他控件包含的窗口小部件(该控件可能不是复合控件),比如列表和表。注意,包含一些项的控件很少包含其他控件,反之亦然。Item 是一个抽象类。
这些窗口小部件被安排在继承层次结构中。参见图 1、图 2 和图 3,了解它们是如何安排的。在图 2 中,Basic1 类是来自本文的类,而其他所有类都是标准的 SWT 窗口小部件。
图 1. SWT Widget 树
图 2. SWT Composite 树
图 3. SWT Item 列表
|
注 意,Eclipse 具有跨平台特性(因此可以在许多操作平台上运行),本文基于 Eclipse 的 Microsoft® Windows® 版本。因此,本文包含的每个例子都应该能够不加任何更改地在其他平台上使用。还要注意的是,本文是基于 Eclipse V3.0 的。Eclipse V3.1 中添加了少许 GUI 窗口小部件类型和特性。
|
几乎所有 SWT GUI 都是从某些基础部分开始创建的。所有 SWT 窗口小部件都可以在 org.eclipse.swt.widget
或 org.eclipse.swt.custom
包中找到。(一些 Eclipse 插件还在其他包中提供了定制的窗口小部件。)窗口小部件包中包含一些基于操作系统控件的控件,而定制包中则包含一些超出操作系统控件集之外的控件。一些定 制的软件包控件类似于窗口小部件包中的控件。为了避免命名冲突,定制控件的名称都是以“C”开始的(例如,比较 CLabel 与 Label)。
在 SWT 中,所有控件(除了一些高级控件,比如 shell,将在后面进行讨论)在创建的时候都必须有一个父控件(一个复合实例)。在创建的时候,这些控件被自动“添加”到父控件中,这与必须明确添加到 父控件中的 AWT/Swing 中的控件有所不同,自动添加产生了一种“自上而下”地构造 GUI 的方法。这样,所有控件都可以采用一个复合父控件(或者一个子类)作为构造函数的参数。
大多数控件都有一些必须在创建时设置的标记选项。因此,大多数控件还有另外一个构造函数参数,我们通常称之为样式,该参数提供了设置这些选项的标记。所有这些参数值都是 static final int
,并且都是在org.eclipse.swt
包的 SWT
类中定义的。如果不需要任何参数,则可以使用 SWT.NONE
值。
|
标签可能是最简单的控件,标签 被用于显示纯文本(没有颜色、特殊字体或样式的文本)或称为图标的小图像。标签不接受焦点(换句话说,用户不能通过 Tab 键或鼠标移动到标签),因此,标签无法产生输入事件。
清单 1 展示了如何创建一个简单的文本标签。
import org.eclipse.swt.widget.*; : Composite parent = ...; : // create a center aligned label Label label = new Label(parent, SWT.CENTER); label.setText("This is the label text"); |
注意,该文本是采用不同于构造函数的单独的方法设置的。这是所有 SWT 控件的一个典型象征。只有父控件和样式是在构造函数中设置的,其他所有属性都是在已创建的对象上设置的。
由于平台的限制,标准标签控件不能同时拥有文本和图标。为了支持同时拥有文本和图标,可以使用 CLabel 控件,如清单 2 中所示。
import com.eclipse.swt.graphics.*; import org.eclipse.swt.widget.*; import org.eclipse.swt.custom.*; : Composite parent = ...; Image image = ...; : // create a left aligned label with an icon CLabel Clabel = new CLabel(parent, SWT.LEFT); label.setText("This is the imaged label text""); label.setImage(image); |
|
在标签显示文本的同时,您时常还想允许用户插入文本。文本 控件就是用于此目的的。文本可以是单行的(一个文本字段),也可以是多行的(一个文本区域)。文本还可以是只读的。文本字段中没有描述,因此,常常通过标 签控件处理它们,以确定它们的用途。文本控件还可以包含一个“工具提示”,提供关于控件用途的信息(所有控件都支持这一特性)。
清单 3 显示了如何使用允许使用的有限数量的特性来创建一个简单的文本字段。选择默认文本是为了便于擦除。
import org.eclipse.swt.widget.*; : Composite parent = ...; : // create a text field Text name = new Text(parent, SWT.SINGLE); name.setText("<none>"); name.setTextLimit(50); name.setToolTipText("Enter your name -- Last, First"); name.selectAll(); // enable fast erase |
|
通常,您希望用户指出应该何时进行某项操作。最常见的做法是使用按钮 控件。存在以下几种样式的按钮:
- ARROW —— 显示为一个指向上、下、左、右方向的箭头。
- CHECK —— 已标记的复选标记。
- FLAT —— 没有凸起外观的按钮。
- PUSH —— 瞬时按钮(最常见的事件源)。
- RADIO —— 具有排他性的粘性标记(sticky mark),其他所有单选按钮都在相同的组中。
- TOGGLE —— 一个粘性按钮。
清单 4 创建了一个“Clear”按钮:
import org.eclipse.swt.widget.*; : Composite parent = ...; : // create a push button Button clear = new Button(parent, SWT.PUSH); clear.setText("Clea&r"); |
名称中的 &
导致利用紧接着的一个字母创建一个加速器,允许通过 Ctrl+<字母> 顺序的方式按下按钮(控件顺序由主机操作系统决定)。
通常,您可能想在选择按钮(特别是某种推式按钮)的时候执行一些操作。您可以通过向该按钮添加一个SelectionListener
(在 org.eclipse.swt.events
包中)做到这一点。当按钮状态发生改变时(通常是按钮被按下),就会生成事件。清单 5 在单击 Clear 按钮时输出一条消息。
import org.eclipse.swt.events.*; : // Clear button pressed event handler clear.addSelectionListener(new SelectionListener() { public void widgetelected(selectionEvent e) { System.out.println("Clear pressed!"); } public void widgetDefaultSelected(selectionEvent e) { widgetelected(e); } }); |
此代码使用了一个匿名的内部类,但您还可以使用指定的内部类或单独的类作为监听器。多数包含两个或更多方法的 ...Listener
类还有一个类似的 ...Adapter
类,这个类提供了一些空的方法实现,并且可以减少您需要编写的代码数量。例如,还有一个 SelectionAdapter
类,这个类实现了 SelectionListener
。
注意,在这些方法中执行的操作必须快速完成(通常不足一秒时间),或者说 GUI 的反应将是迟钝的。更长时间的操作(比如访问文件)需要单独的线程,但那是以后某期文章的主题。
|
至此,我们已经讨论了一些单独的控件。在多数 GUI 中,许多控件被组合在一起以提供丰富的用户体验。在 SWT 中,这种组合是通过 Composite 类实现的。
复 合控件可以在任何级别上进行嵌套,并且可以混合和匹配控件,将它们作为子控件进行组合。这样做可以极大地减少 GUI 开发的复杂性,并为 GUI 代码重用(通过封装内部 GUI)创造了机会。复合控件可以是有边界的,并且这些边界很容易在视觉上产生混淆,或者它们也可以是无边界的,无缝集成到更大的组中。
清单 6. 创建一个有边界的复合控件。
import org.eclipse.swt.widget.*; : Composite parent = ...; : Composite border = new Composite(parent, SWT.BORDER); |
除了边界之外,Group 复合子类还支持标题。在定义排他性按钮集合时,组通常被用来包含单选类型的按钮。
清单 7 创建了一个有边界的组。
import org.eclipse.swt.widget.*; : Composite parent = ...; : Group border = new Group(parent, SWT.SHADOW_OUT); border.setText("Group Description"); |
shell 是一种可能没有父复合控件的复合控件(框架或窗口);此外,它还有一个作为父控件的 Display,这通常也是默认设置。shell 有很多种样式,但最常见的样式是 SWT.SHELL_TRIM
或 SWT.DIALOG_TRIM
。shell 可以是模态的,也可以是非模态的。模态 shell 常常用于对话框,防止父 GUI(如果有的话)在关闭子 shell 之前被处理。
清单 8 创建了一个框架样式的顶级非模态 shell。
import org.eclipse.swt.widget.*; : Shell frame = new Shell(SWT.SHELL_TRIM); : |
shell 可以有子 shell。这些子 shell 是与父 shell 相关的独立桌面窗口(也就是说,如果父 shell 关闭,那么其所有子 shell 也将关闭)。
清单 9 创建了一个对话框样式的子 shell。
: Shell dialog = new Shell(frame, SWT.DIALOG_TRIM); : |
参见图 4 中具有 SWT.SHELL_TRIMSee 的 shell,以及图 5 中具有 SWT.DIALOG_TRIM 的 shell,了解这些值如何影响 shell 的整洁性。
图 4. 具有 SWT.SHELL_TRIM 的 shell
图 5. 具有 SWT.DIALOG_TRIM 的 shell
|
复合控件常常包含多个控件。可以使用以下两种方法安排这些控件:
- 绝对定位 —— 为每个控件设置明确的 X 和 Y 位置,并通过代码设置一定的宽度和高度。
- 托管定位 —— 每个控件的 X、Y、宽度和高度都是通过 LayoutManager 设置的。
在多数情况下,应该选择使用 LayoutManagers,因为很容易调整它们来适应可变大小的 GUI。SWT 也提供了一些布局管理器供您使用;在这一期的系列文章中,我们将讨论两种基本的布局管理器:FillLayout 和GridLayout。在这两种情况下,每当重新设置复合控件的大小,都需要进行定位。
一些布局管理器常常是专为某一个复合控件分配的。一些布局管理器只使用它们自身的参数就可以控制,而另一些布局管理器还需要其他参数 —— LayoutData,该参数是在它们管理的复合控件中的每个控件上指定的。
FillLayout 以行或列的形式安排控件。每个控件所设置的大小将与填充该复合控件所需的宽度和高度相同,在这些控件之间,空间是平均分配的。一种特殊情况是:在仅有一个子控件时,该控件的大小被设置为填充整个父复合控件的大小。
清单 10 使用一个列 FillLayout 创建了一个复合控件。
import org.eclipse.swt.widget.*; import org.eclipse.swt.layouts.*; : Composite composite = ...; FillLayout fillLayout = new FillLayout(SWT.VERTICAL); composite.setLayout(fillLayout); |
GridLayout 提供了一个功能更强大的布局方法,该方法类似于使用 HTML 表的方法。它创建了 2-D 网格的单元格。可以将控件放置在一个或多个单元格中(可以称之为单元格跨越)。单元格的大小可以是相等的,或者是网格宽度或高度的某个给定可变百分比。可 以将控件添加到某一行的下一个可用列中,如果这一行中没有更多的列,那么该控件将移动到下一行的第一列中。
清单 11 创建了一个复合控件,该控件有两行和两个列,其中包含两个已标记的文本字段。这些列可以有不同的宽度。
import org.eclipse.swt.widget.*; import org.eclipse.swt.layouts.*; : Composite composite = ...; GridLayout gridLayout = new GridLayout(2, false); composite.setLayout(gridLayout); Label l1 = new Label(composite, SWT.LEFT); l1.settext("First Name: "); Text first = new Text(composite, SWT.SINGLE); Label l1 = new Label(composite, SWT.LEFT); l2.setText("Last Name: "); Text last = new Text(composite, SWT.SINGLE); |
考虑一下这种情况:您需要指定每个控件如何使用其单元格中的剩余空间。为了给每个单元格提供这种精确控制,添加到 GridLayout 的托管复合控件的控件可以拥有 GridData 实例(LayoutData 的子类)。
清单 12 设置了这些文本字段,以便采用所有可用的剩余空间(根据前面的清单)。
first.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); last.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
|
现在是时候来看一下我们已经在简单的可执行例子 Basic1 中讨论过的所有 SWT 控件了。请参阅 参考资料,以获得该应用程序的完整源代码。
SWT GUI 需要一个已配置好的环境来运行。这个环境是通过一个显示实例提供的,该实例提供了对主机操作系统显示设备的访问。这个显示实例允许您处理每个用户输入(鼠标或键盘)来处理您的 GUI。
清单 13 创建了一个环境和一个 GUI,并显示了这个 GUI。
import org.eclipse.swt.widget.*; : Display display = new Display(); Shell shell = new Shell(display); shell.setText("Shell Title"); // *** construct Shell children here *** shell.open(); // open shell for user access // process all user input events while(!shell.isDisposed()) { // process the next event, wait when none available if(!display.readAndDispatch()) { display.sleep(); } } display.dispose(); // must always clean up |
此代码创建了一个类似于图 6 的窗口。
|
在 SWT 和 JFace 系列的第一期中,我们介绍了大多数基本 SWT 窗口小部件控件:标签、文本、按钮、复合控件和 shell。这些控件,与显示类(display class)相结合,允许创建全功能的 GUI。
本系列的第 2 部分将展示如何创建菜单,以及如何使用其他输入控件和信息显示控件,比如列表、树和表等。
本文主要讨论Java中的GUI图形库之一:SWT/JFace。在本文的第一部分,将解释什么是SWT/JFace以及如何安装SWT/JFace。在本文的第二部分将以实例的方式讨论如何使用SWT/JFace编写GUI程序。
一、 进入SWT/JFace世界
1. 什么是SWT/JFace
Java是一种强大的编程语言。但强大就意味复杂,尤其是和Java相关的名词就象天上的星星一样,数都数不过来。在本文中就涉及到两个比较常用的名词 SWT和JFace。在标题中将SWT和JFace放到一起,并不是说SWT和JFace是一个意思,而是说它们的关系非常紧密。
基 于Java的图形库最主要的有三种,它们分别是Swing、AWT和SWT。其中前两个是Sun随JDK一起发布的,而SWT则是由IBM领导的开源项目 (现在已经脱离IBM了)Eclipse的一个子项目。SWT的执行效率非常高。这是由于SWT的底层是由C编写的。由于SWT通过C直接调用系统层的 GUI API。因此,使用SWT编写GUI程序,在外观上就和使用C++、Delphi(在Windows下)编写的程序完全一样。它的这一点和AWT类似。 AWT在底层也是使用C直接调用系统层的GUI API。但它们是有区别的,最大的区别可能就是一个是Sun提供的,一个是Eclipse自带的。这就意味着如果使用AWT,只要机器上安装了JDK或 JRE,发布软件时无需带其它的库。而如何使用SWT,在发布时必须要自带上SWT的*.dll(Windows版)或*.so(Linux/Unix 版)文件以及相关的*.jar包。还有就是它们所提供的图形接口有一些差异。SWT可能更丰富一些,我们可以看看Eclipse的界面就知道了。但随着 Sun对AWT库的不断更新,AWT的图形表现能力也在不断地提高。
虽然SWT很强大,但它比较底层。也就是说它的一些功能在使用上 还比较低级,不太符合面向对象的特征。因此,在SWT的基础上又开发了JFace。JFace在SWT上进行了一定的扩展。因此,也可说JFace是基于 SWT的,就象在VC中使用MFC来包装Win32 API一样。
2. SWT/Face的安装
在发布使用 SWT/JFace编写的GUI程序时,要随程序带上相应的库文件。对于Windows版的SWT来说,SWT包含有4个dll文件和一个jar文件。它 们是swt-awt-win32-3305.dll、swt-gdip-win32-3305.dll、swt-wgl-win32-3305.dll、 swt-win32-3305.dll和swt.jar。在发布时,要将4个dll文件放到path路径中,或者使用- Djava.library.path设置相应的路径。将swt.jar放到classpath路径中,或使用-classpath设置相应的jar包。 而对于JFace,除了上述的5个文件外,还要带上5个jar包:
org.eclipse.core.runtime_3.1.2.jar org.eclipse.jface_3.1.1.jar org.eclipse.jface.text_3.1.2.jar org.eclipse.osgi_3.1.2.jar org.eclipse.text_3.1.1.jar |
这5个jar包都可以在eclipse的plugins目录中找到,在这5个文件后面的版本号可能会因为eclipse的版本号不同而不同,但前面的部分都是一样的。读者在找这些jar包时应注意这一点。
SWT的开发包可以从http://www.eclipse.org单独下载,也可以从eclipse的plugins目录复制。而JFace的开发包并未提供单独的下载,因此,JFace的开发包必须要从plugins目录得到。
二、 让我们编写第一个程序吧
学习一种新技术的最好方法就是去使用它。下面就让我们来使用SWT和JFace来分别实 现同一个程序。这个程序是一个简单的记事本程序。在上面有三个按纽,分别是"新键"、"打开","保存",下面是一个文本框,用于编辑文本信息。下面让我 们先来看一下使用SWT实现的程序界面:
图1 使用SWT实现的记事本程序界面 |
怎么样,看看上面的界面是不是和用Delphi、VC做的界面完全一样!!
1. 用SWT实现
不论一个程序带不带GUI,都必须有一个入口点,对于Java来说,这个入口点就是main函数。因此,在编写程序之前,我们必须定义一个类,并且这个类中必须有个main函数。
import org.eclipse.swt.widgets.*; import org.eclipse.swt.*; import org.eclipse.swt.events.*; import java.io.*; public class FirstSWT { // 用于记录是否已经打开或保存了一个文件,如果已经打开或保存了一个文件, // 这个变量就是这个文件的名子 private static String fn = ""; public static void main(String[] args) { … … } } |
上面四个import将导入一些在本程序中要用到的jar包,前三个是SWT的包,最后一个是Java的标准输入输出包。
1、建立窗体
任何一个GUI程序,都至少有一个窗体(在本程序中只有一个窗体)。因此,下面我们就在main函数中建立这个窗体。
display = new Display(); shell = new Shell(display, SWT.DIALOG_TRIM); shell.setText("第一个SWT程序"); shell.setSize(400, 300); |
在上面4行代码中涉及到了两个类:Display和Shell。这两个类都是在FirstSWT中定义的私有静态类,之所以定义成全局的,是因为在以后的按钮事件类中要使用它们。下面是它们的定义:
private static Display display; private static Shell shell; |
后面2条语句通过调用Shell类的setText和setSize方法,设置了窗口的标题和尺寸。
下面解释一下Display和Shell类是什么。
SWT在底层实现上分为两层:系统层和用户层。系统层就是直接和操作系统平台打交道,系统层的存在依赖于操作系统平台。在这里,系统层就是 Display类。Display的功能就是在系统和用户之间架起一座桥梁,也就是说使用户访问系统资源透明化。而Shell类是直接和用户打交道,因 此,它属于用户层。通过Shell类可以控制窗体中的控件、窗体本身的属性等。而Shell通过Display这座桥梁访问系统级API。
l 向窗体中添加控件
接下来我们先在这个窗体上建立三个按钮,代码如下:
Button newButton = new Button(shell, SWT.PUSH); newButton.setLocation(2, 5); newButton.setSize(50, 20); newButton.setText("新建"); Button openButton = new Button(shell, SWT.PUSH); openButton.setLocation(60, 5); openButton.setSize(50, 20); openButton.setText("打开"); Button saveButton = new Button(shell, SWT.PUSH); saveButton.setLocation(118, 5); saveButton.setSize(50, 20); saveButton.setText("保存"); |
按钮类是Button,在建立时,Button需要两个参数,一个是Shell对象,另外一个是按钮的类型,在本例中,我们使用SWT.PUSH类型(一般的按钮类型)。
注:和SWT相关的常量都定义在了SWT 中。
后面3条语句分别设置了三个按钮的位置,尺寸和按钮标题。
最后在3个按钮下方建立一个文本框
text = new Text(shell, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.WRAP); text.setLocation(2, 30); text.setSize(shell.getClientArea().width - 4, shell.getClientArea().height - 35); |
文本框的类是Text,和按钮不同的是,由于文本框需要在按钮事件中被访问,因此,文本对象必须定义成全局的。
private static Text text; |
1、添加控件事件代码
现在让我们为三个按钮控件中加入事件代码。和大多数语言不同的是,按钮的单击事件不叫Click,而叫Selection。一般需要将 Selection事件代码放到一个从SelectionAdapter类继承的子类中。然后通过按钮类的addSelectionListener方法 将这个事件类的实例传入按钮类的实例中。但为了简便起见,我们使用隐式建立对象的方法来建立事件类的对象。下面是"新建"按钮的事件代码。
newButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { fn = ""; shell.setText("第一个SWT程序"); text.setText(""); } }); |
由于SelectionAdapter是一个抽象类,它有一个抽象方法widgetSelected,在上述代码被override了。在"新建"按钮中将全局文件名赋成空串,并将窗体的标题赋成初始状态,最后将文本框清空。
接下来让我们看看"打开"按钮的事件代码:
openButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { FileDialog dlg = new FileDialog(shell, SWT.OPEN); String fileName = dlg.open(); try { if (fileName != null) { // 打开指定的文件 FileInputStream fis = new FileInputStream(fileName); text.setText(""); BufferedReader in = new BufferedReader(new InputStreamReader(fis)); String s = null; // 将指定的文件一行一行地加到文本框中 while ((s = in.readLine()) != null) text.append(s + "/r/n"); } if (fileName != null) { fn = fileName; shell.setText(fn); MessageBox successBox = new MessageBox(shell); successBox.setText("信息"); successBox.setMessage("打开文件成功!"); successBox.open(); } } catch (Exception e) { MessageBox errorBox = new MessageBox(shell, SWT.ICON_ERROR); errorBox.setText("错误"); errorBox.setMessage("打开文件失败!"); errorBox.open(); } } }); |
上面代码的基本逻辑是使用打开对话框选择一个文件,使用FileInputStream将这个文件打开,并且将文件中的内容一行一行地加入到文本框中,如果这个过程失败,显示错识对话框,如果成功,将fn变量和窗体的标题栏都赋成这个文件名。
最后让我们实现"保存"按钮事件的代码。
saveButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { try { String fileName = null; if (fn.equals("")) { FileDialog dlg = new FileDialog(shell, SWT.SAVE); fileName = dlg.open(); if(fileName != null) fn = fileName; } if (fn != "") { FileOutputStream fos = new FileOutputStream(fn); OutputStreamWriter out = new OutputStreamWriter(fos); out.write(text.getText()); out.close(); shell.setText(fn); MessageBox successBox = new MessageBox(shell); successBox.setText("信息"); successBox.setMessage("保存文件成功!"); successBox.open(); } } catch (Exception e) { MessageBox errorBox = new MessageBox(shell, SWT.ICON_ERROR); errorBox.setText("错误"); errorBox.setMessage("保存文件失败!"); errorBox.open(); } } }); |
这段代码的基本逻辑是如果当前已经打开一个文件或已经将当前的新建文件保存过了,在点击"保存"按钮时,不再显示保存对话框,而直接将文件保存,否则,将显示一个保存对话框,通过这个对话框可以选择一个文件名,然后再保存。
1、让我们最后画龙点睛吧
程序到这已经基本完成了,但还需要进行最后一步,就是对事件进行监听。在main函数的最后,可以加上如下的代码实现这个功能。
shell.open(); // 显示窗体 while (!shell.isDisposed()) // 当窗体未被关闭时执行循环体内的代码 { // 如果未发生事件,通过sleep方法进行监视事件队列 if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); // 释放底层的资源 |
看看上面的代码,是不是有点象MFC的监听事件代码!!
2、用JFace实现
使用JFace实现GUI程序和SWT的最大的区别就是JFace的窗体类必须从ApplicationWindow继承。
import org.eclipse.jface.window.ApplicationWindow; public class FirstJFace extends ApplicationWindow { public static void main(String[] args) { … … } } |
另外一个不同是在设置窗体上,JFace通过ApplicationWindow类提供一系列的事件函数,通过在这些函数中编写代码,可以很方便地对窗体进行操作。如可以在createContents函数中向窗体中加入控件。
protected Control createContents(Composite parent) { // 这里边的代码就是上述的建立控件的代码,只是要将shell换成parent } |
由于使用JFace操作控件的方式和SWT类似,本文将不再详细讨论,感性趣的读者可以参考本文提供的源代码。使用JFace的程序界面和SWT完全一样,界面如图2所示:
图2使用JFace实现的记事本程序界面
示例1(SWT)的完整代码如下:
import org.eclipse.swt. * ;
import org.eclipse.swt.events. * ;
import java.io. * ;
public class FirstSWT
{
private static Display display;
private static Shell shell;
private static Text text;
private static String fn = "" ;
public static void main(String[] args)
{
display = new Display();
shell = new Shell(display, SWT.DIALOG_TRIM);
shell.setText( " 第一个SWT程序 " );
shell.setSize( 400 , 300 );
// 加入新建、打开和保存按钮
Button newButton = new Button(shell, SWT.PUSH);
newButton.setLocation( 2 , 5 );
newButton.setSize( 50 , 29 );
newButton.setText( " 新建 " );
Button openButton = new Button(shell, SWT.PUSH);
openButton.setLocation( 60 , 5 );
openButton.setSize( 50 , 29 );
openButton.setText( " 打开 " );
Button saveButton = new Button(shell, SWT.PUSH);
saveButton.setLocation( 118 , 5 );
saveButton.setSize( 50 , 29 );
saveButton.setText( " 保存 " );
// 加入一个文本框
text = new Text(shell, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.WRAP);
text.setLocation( 2 , 40 );
text.setSize( 391 ,
227 );
// 加入新建按纽事件
newButton.addSelectionListener( new SelectionAdapter()
{
public void widgetSelected(SelectionEvent event)
{
fn = "" ;
shell.setText( " 第一个SWT程序 " );
text.setText( "" );
}
});
// 加入打开按钮事件
openButton.addSelectionListener( new SelectionAdapter()
{
public void widgetSelected(SelectionEvent event)
{
FileDialog dlg = new FileDialog(shell, SWT.OPEN);
String fileName = dlg.open();
try
{
if (fileName != null )
{
FileInputStream fis = new FileInputStream(fileName);
text.setText( "" );
BufferedReader in = new BufferedReader(
new InputStreamReader(fis));
String s = null ;
while ((s = in.readLine()) != null )
text.append(s + " " );
}
if (fileName != null )
{
fn = fileName;
shell.setText(fn);
MessageBox successBox = new MessageBox(shell);
successBox.setText( " 信息 " );
successBox.setMessage( " 打开文件成功! " );
successBox.open();
}
}
catch (Exception e)
{
MessageBox errorBox = new MessageBox(shell, SWT.ICON_ERROR);
errorBox.setText( " 错误 " );
errorBox.setMessage( " 打开文件失败! " );
errorBox.open();
}
}
});
// 添加保存按钮事件
saveButton.addSelectionListener( new SelectionAdapter()
{
public void widgetSelected(SelectionEvent event)
{
try
{
String fileName = null ;
if (fn.equals( "" ))
{
FileDialog dlg = new FileDialog(shell, SWT.SAVE);
fileName = dlg.open();
if (fileName != null )
fn = fileName;
}
if (fn != "" )
{
FileOutputStream fos = new FileOutputStream(fn);
OutputStreamWriter out = new OutputStreamWriter(fos);
out.write(text.getText());
out.close();
shell.setText(fn);
MessageBox successBox = new MessageBox(shell);
successBox.setText( " 信息 " );
successBox.setMessage( " 保存文件成功! " );
successBox.open();
}
}
catch (Exception e)
{
MessageBox errorBox = new MessageBox(shell, SWT.ICON_ERROR);
errorBox.setText( " 错误 " );
errorBox.setMessage( " 保存文件失败! " );
errorBox.open();
}
}
});
shell.open();
while ( ! shell.isDisposed())
{
if ( ! display.readAndDispatch())
{
display.sleep();
}
}
display.dispose();
}
}
示例2(JFace)的完整代码如下:
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets. * ;
public class FirstJFace extends ApplicationWindow
{
private Text text;
private String fn = "" ;
private static Shell myShell;
public FirstJFace()
{
super ( null );
}
public void show()
{
setBlockOnOpen( true );
setShellStyle(SWT.DIALOG_TRIM);
open();
Display.getCurrent().dispose();
}
protected void configureShell(Shell shell)
{
super .configureShell(shell);
shell.setLayout( null );
shell.setText( " 第一个JFace程序 " );
shell.setSize( 400 , 300 );
myShell = shell;
}
protected Control createContents(Composite parent)
{
// 加入新建、打开和保存按钮
Button newButton = new Button(parent, SWT.PUSH);
newButton.setLocation( 2 , 5 );
newButton.setSize( 50 , 25 );
newButton.setText( " 新建 " );
Button openButton = new Button(parent, SWT.PUSH);
openButton.setLocation( 60 , 5 );
openButton.setSize( 50 , 25 );
openButton.setText( " 打开 " );
Button saveButton = new Button(parent, SWT.PUSH);
saveButton.setLocation( 118 , 5 );
saveButton.setSize( 50 , 25 );
saveButton.setText( " 保存 " );
// 加入一个文本框
text = new Text(parent, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL
| SWT.WRAP);
text.setLocation( 2 , 30 );
text.setSize(parent.getClientArea().width - 4 ,
parent.getClientArea().height - 35 );
// 加入新建按纽事件
newButton.addSelectionListener( new SelectionAdapter()
{
public void widgetSelected(SelectionEvent event)
{
fn = "" ;
myShell.setText( " 第一个JFace程序 " );
text.setText( "" );
}
});
// 加入打开按钮事件
openButton.addSelectionListener( new SelectionAdapter()
{
public void widgetSelected(SelectionEvent event)
{
FileDialog dlg = new FileDialog(myShell, SWT.OPEN);
String fileName = dlg.open();
try
{
if (fileName != null )
{
FileInputStream fis = new FileInputStream(fileName);
text.setText( "" );
BufferedReader in = new BufferedReader(
new InputStreamReader(fis));
String s = null ;
while ((s = in.readLine()) != null )
text.append(s + " " );
}
if (fileName != null )
{
fn = fileName;
myShell.setText(fn);
MessageBox successBox = new MessageBox(myShell);
successBox.setText( " 信息 " );
successBox.setMessage( " 打开文件成功! " );
successBox.open();
}
}
catch (Exception e)
{
MessageBox errorBox = new MessageBox(myShell,
SWT.ICON_ERROR);
errorBox.setText( " 错误 " );
errorBox.setMessage( " 打开文件失败! " );
errorBox.open();
}
}
});
// 添加保存按钮事件
saveButton.addSelectionListener( new SelectionAdapter()
{
public void widgetSelected(SelectionEvent event)
{
try
{
String fileName = null ;
if (fn.equals( "" ))
{
FileDialog dlg = new FileDialog(myShell, SWT.SAVE);
fileName = dlg.open();
if (fileName != null )
fn = fileName;
}
if (fn != "" )
{
FileOutputStream fos = new FileOutputStream(fn);
OutputStreamWriter out = new OutputStreamWriter(fos);
out.write(text.getText());
out.close();
myShell.setText(fn);
MessageBox successBox = new MessageBox(myShell);
successBox.setText( " 信息 " );
successBox.setMessage( " 保存文件成功! " );
successBox.open();
}
}
catch (Exception e)
{
MessageBox errorBox = new MessageBox(myShell,
SWT.ICON_ERROR);
errorBox.setText( " 错误 " );
errorBox.setMessage( " 保存文件失败! " );
errorBox.open();
}
}
});
return null ;
}
public static void main(String[] args)
{
new FirstJFace().show();
}
private void createActions()
{
}
}