天天看點

第一個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()方法還是由于它的父控件被釋放了,我們不用管他。