天天看点

第一个SWT程序

刚开始的程序员都渴望使用程序来同这个世界打招呼;这一章指导你创建SWT程序-不可避免的“Hello world”。它揭示了SWT如何工作,带领你领略SWT中主要的对象。讨论了SWT窗口小工具的生命周期。

SWT的“Hello World”

对前面章节的BlankWindow程序作一些小的修改,使它成为一个规范的“Hello World”程序。需要注意的是,需要创建一个org.eclipse.swt.widgets.Label对象的实例,设定他的首选文本框,把它添加到你的面板中。以下的代码反映出来改变。

Code:

import org.eclipse.swt.widgets.Display;

import org.eclipse.swt.widgets.Shell;

import org.eclipse.swt.widgets.Label;

import org.eclipse.swt.SWT;

public class HelloWorld

{

public static void main(String[] args)

{

Display display = new Display();

Shell shell = new Shell(display);

Label label = new Label(shell, SWT.CENTER);

label.setText("Hello, World");

label.setBounds(shell.getClientArea());

shell.open();

while (!shell.isDisposed())

{

if (!display.readAndDispatch())

{

display.sleep();

}

}

display.dispose();

}

}

[Ctrl+A Select All]

编译运行程序

按照前面章节的介绍编译HelloWorld.java。从现在可是,我们不会明确的给出编译和运行的步骤,除非和前面章节介绍的例子有所不同。

在命令行编译和运行你的程序会变得乏味和易错的。为了解决这个问题,我们建议你使用Ant构建工具。编译和运行你的程序,只需要复制build.xml文件到源码所在文件夹下,在main.class属性栏指明程序的main类。例如,编译和运行HelloWorld,键入:

ant –Dmain.class=HelloWorld

为了编译你的程序,你可以省略main.class属性,但要指明编译目标,像这样:

ant compile

Listing 3-1包括了Ant build文件,可以在以后使用。

Listing 3-1:build.xml

Code:

>?xml version="1.0" encoding="ISO-8859-1"?<

>project name="GenericSwtApplication" default="run" basedir="."<

>description<

Generic SWT Application build and execution file

>/description<

>property name="main.class" value=""/<

>property name="src" location="."/<

>property name="build" location="."/<

>!-- Update location to match your eclipse home directory --<

>property name="ecl.home" location="c:/eclipse"/<

>!-- Update value to match your windowing system (win32, gtk, motif, etc.) --<

>property name="win.sys" value="win32"/<

>!-- Update value to match your os (win32, linux, etc.) --<

>property name="os.sys" value="win32"/<

>!-- Update value to match your architecture --<

>property name="arch" value="x86"/<

>!-- Update value to match your SWT version --<

>property name="swt.ver" value="3.0.0"/<

>!-- Do not edit below this line --<

>property name="swt.subdir"

location="${ecl.home}/plugins/org.eclipse.swt.${win.sys}_${swt.ver}"/<

>property name="swt.jar.lib" location="${swt.subdir}/ws/${win.sys}"/<

>property name="swt.jni.lib" location="${swt.subdir}/os/${os.sys}/${arch}"/<

>path id="project.class.path"<

>pathelement path="${build}"/<

>fileset dir="${swt.jar.lib}"<

>include name="**/*.jar"/<

>/fileset<

>/path<

>target name="compile"<

>javac srcdir="${src}" destdir="${build}"<

>classpath refid="project.class.path"/<

>/javac<

>/target<

>target name="run" depends="compile"<

>java classname="${main.class}" fork="true" failοnerrοr="true"<

>jvmarg value="-Djava.library.path=${swt.jni.lib}"/<

>classpath refid="project.class.path"/<

>/java<

>/target<

>/project<

[Ctrl+A Select All]

你需要更新复制的build.xml文件,更新Eclipse的根文件夹,窗口系统,操作系统,构架和SWT版本。

Ant是什么?

Ant,是Apache Jakarta工程的一部分( http://jakarta.apache.org/),是java的“make”工具。获得Java Pro 2003读者选择的最有价值的java部署工具,它简化了java程序的构建过程,成为了java界的标准构件工具。

同传统的“make”工具相比,Ant使用XML作为构建的配置文件。创建一个java程序,需要创建一个相应的XML文件,依赖的构件法则,然后运行Ant来检测XML文件。默认情况下,ant寻找一个名字为build.xml文件,但是你也可以告诉Ant使用其他的文件名。也可以同时指明目标和Ant的属性。

要了解更多信息,下在Ant,可以查看Ant的官方主页 http://ant.apache.org/。

运行这个程序显示了一个如下的窗口,参看Figure 3-1。

第一个SWT程序

Figure 3-1:SWT的“Hello World”

程序解释

下面的代码给出了合适的类型导入:

import org.eclipse.swt.widgets.Display;

import org.eclipse.swt.widgets.Shell;

import org.eclipse.swt.widgets.Label;

import org.eclipse.swt.SWT;

大部分使用SWT的类都需要导入SWT对象和一部分的swt.widgets包。

下面的代码创建了一个Display对象和Shell对象:

Display display = new Display();

Shell shell = new Shell(display);

在高级别,Display对象代表了底层了窗口系统。Shell对象是一个创建Display对象后的顶层窗口的抽象。更多关于Display和Shell的介绍在本章的后面部分。

接下来,创建一个label部件:

Label label = new Label(shell, SWT.CENTER);

label.setText(“Hello, World”);

label.setBounds(shell.getClientArea());

Label对象可以显示简单的文本,就像在这里使用的一样,或者图像。这个控件创建的时候将一个Shell对象应用作为参数,shell是Composite类的一个间接子类。Composite类可以容纳其他的控件。当SWT遇到这样的代码,他知道创建一个底层窗口系统的label实现来辅助Composite类。

使得窗口显示出来,需要下面的代码:

shell.open();

这个指示底层系统使得当前的shell变得可见,(如果存在一个按钮)将把焦点设在按钮上,并使得窗口可用。这样就可以使得窗口可以从底层系统接受事件。

以下是程序的主要循环:

while(!shell.isDisposed())

{

If(!display.readAndDispatch())

{

Display.sleep();

}

}

在你所有的SWT程序里都需要有一个类似的循环。在这个循环里,首先确保用户没有关闭主窗口。因为如果窗口如果是开着的,就可以检查事件消息队列是否有窗口系统或者程序其他部分可能产生的消息。如果没有消息的话,程序就休眠,等待下一个事件得到达。当下一个事件到达后,重复循环,确保事件没有关闭主要窗口。

最后,调用如下:

display.dispose();

因为窗口被释放掉了(用户关闭),所以不再需要窗口系统显示图形化的控件。作为一个计算机的好公民,我们将这些资源换给系统。

了解SWT之后的设计

正如第一章学到的,SWT使用底层系统提供的本地部件库。提供了java薄板来同你的程序进行对话。Java控件对象的生命周期映射到本地控件的生命周期。当你创建了一个java控件,相应的本地控件也被创建,同样当java控件被销毁,本地控件也就被销毁了。这样就避免了调用一个代码对象的时候,底层系统部件没有被创建的问题,这样的问题会导致控件生命周期比匹配。

比如,同Microsoft Foundation Classes (MFC)的两步创建过程相比。如果想创建一个按钮,要如下写代码:

CButton button; // Construct the C++ object on the stack

Button.Create(>parameters<); //Create the Windows widget

看一下在C++对象的构建代码和依赖的本地窗口工具代码之间插入代码:

CButton button; // Construct the C++ object on the stack

CString str = _T(“Hi”); // Create a CString to hold the button text

button.SetWindowText(str); // Set the button text—PROBLEM!

button.Create(>parameters<); // Create the Window widget

代码编译时没有任何问题,但是不能如期望运行。调试版本会引起一个断言,发布版本的行为是不确定的。

父控件

很多GUIs在创建一个新控件前需要指明他的父控件,这个控件的生命周期是和他的父控件相关的。父控件的生命时间就是子控件的生命时间。在有就是,很多本地控件都有他们的个性,或者说是“style”,必须在创建时指明。举例,一个按钮可以是点击的,或者复选的。因为SWT控件在构造时创建相应的本地控件,所以必须将这些信息传递给他的构造函数。SWT控件一般都是带有两个参数的:父控件和形式。父控件一般都是org.eclipse.swt.widgets.Widget或者他的一个子类。形式是在SWT类中定义的一些整形常量;可以付给一个类型,或者使用或运算符添加几个类型。我们将在后面章节针对相应的控件指明它的类型。

释放控件

Swing的开发者可能会嘲笑这一点,作为证明SWT的缺点。Java开发者可能在这里也会感到讨厌和不适,而在这一点要说的是:在之后要清理自己的。要知道,这是对java程序员的负责,对垃圾回收的侮辱。

为什么要释放对象?Java的垃圾回收可以很好的做到,但是GUI资源管理操作却承受巨大的约束。GUI资源是非常有限的,在很多平台上,是一种系统级的有限。由于SWT需要底层的图形资源来工作,每个SWT资源消耗着一个GUI资源,资源的延时释放不但对你的SWT程序要好处,同样对正在运行的GUI程序有好处。Java的垃圾回收没有时间的保证,会使得对SWT的图形资源的管理变得很糟。所以,作为程序员的你必须承担起这个责任。

这个工作有多繁重?实际上,并没有多大的工作量。在SWT的一系列文章上,Carolyn Macleod和Steve Northover给出了两个简单的法则来指导你( www.eclipse.org/articles/swt-design-2/swt-design-2.html):

•如果你创建了他,那么就要释放他

•释放了父控件,子控件也被释放

法则一:如果你创建了他,那么就要释放他

在本章节的前一部分,你知道了创建SWT控件,相应的本地控件也被创建了。也就是说,调用了SWT的构造函数,底层的本地资源也被创建了。所以如果你写下如下代码,那么你就创建了一个SWT颜色对象,那么也就在系统的底层资源里分配了一个颜色资源:

Color color = new Color(display, 255, 0, 0); // Create a red Color

法则一说明了如果你创建了他,那么你就要在使用完时释放他,像下面一样:

color.dispose(); // I create it, so I dispose it

但是,如果你没有使用构造函数来申请资源,你就不能显式的释放他。比如,考虑下面的代码:

Color color = display.getSystemColor(SWT.COLOR_RED); // Create a red Color

再一次,获得了底层平台的颜色资源,但是没有分配他。法则一就说不能释放他。为什么不呢?因为这个不属于你-你只是借用了这个资源,其他的对象有可能正在使用它或者将要使用它。释放这个资源会变得非常严重的。

法则二:释放了父控件,子控件也被释放

对每一个使用new创建的控件都使用dispose()会变得非常繁琐,会使得SWT很快被抛弃。但是,SWT的设计者意识到了这一点,创建了一种逻辑上层叠的自动释放机制。这意味着,当一个Shell被释放了,所有与Shell有关的控件都被自动释放了。你会发现,永远也不会使用label.dispose()在“Hello World”程序里,甚至你使用了new来创建一个Label对象。当用户关闭了Shell,Label控件自动的释放了。

也许你在想,你永远不需要调用dispose(),这部分代码只是在浪费空间。实际上,有可能写一些程序,所有的资源都只有一个父类,并且这些资源都会自动释放。但是,考虑一下下面的改变Text中字体的代码。如下:

Text text = new Text(shell, SWT.BORDER); //Create the text field

Font font = new Font(display, ”Arial”, 4, SWT.BOLD); //Create the new font

text.setFont(font); //Set the font into the text field

这个字体对象创建时没有父类,所以不会被自动释放,甚至当Shell被关闭了,Text对象调用了dispose()。也许你会对使用font这个负担而发怒,但是考虑到text没有理由要对font负责-font不属于他。实际上,你也许会使用这个Font对象在各种各样的控件上,自动释放会引发一系列的问题。

忽略释放对象

聪明的读者也许会发现在生命周期映射上有一个漏洞:如果Java包裹的本地控件依然在活动,而他所属于的Shell被释放了,将发生什么情况呢?或者手动调用控件的dispose方法会怎么样呢?本地控件会被释放吗?我们是否可以调用java对象而他的底层本地控件不存在?

答案当然是:可以!如果你调用一个对象而他的本地控件已经释放,会引出一些麻烦。一旦一个控件被释放了,甚至他依然在活动,对他做什么都没有用。是的,Java对象依然可用,但是底层的对应点已经释放了。那么就会得到一个SWTException,显示为“Widget has been disposed”。考虑Listing 3-2的代码。

Listing 3-2:Broken.java

Code:

import org.eclipse.swt.*;

import org.eclipse.swt.layout.*;

import org.eclipse.swt.widgets.*;

public class Broken

{

public static void main(String[] args)

{

Display display = new Display();

Shell shell = new Shell(display);

shell.setLayout(new RowLayout());

Text text = new Text(shell, SWT.BORDER);

shell.open();

while (!shell.isDisposed())

{

if (!display.readAndDispatch())

{

display.sleep();

}

}

System.out.println(text.getText()); // PROBLEM!

display.dispose();

}

}

[Ctrl+A Select All]

  代码可以编译和运行,但是在关闭了主窗口后控制台会打印出以下的错误:

Org.eclipse.swt.SWTException: Widget is disposed

at org.eclipse.swt.SWT.error(SWT.java:2332)

at org.eclipse.swt.SWT.error(SWT.java:2262)

at org.eclipse.swt.widgets.Widget.error(Widget.java:385)

at org.eclipse.swt.widgets.Control.getDisplay(Control.java:735)

at org.eclipse.swt.widgets.Widget.isValidThread(Widget.java:593)

at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:315)

at org.eclipse.swt.widgets.Text.getText(Text.java:705)

at Broken.main(Verison.java:24)

需要注意的,当你在Windows XP上运行这个程序,你会得到一个对话框显示javaw.exe出现了问题,需要关闭,你是否愿意发送错误报告到Microsoft?

这一课是比较简单的:一旦一个对象被释放了,是否是显式的调用dispose()方法还是由于它的父控件被释放了,我们不用管他。