從Java誕生至今已經在太多的領域取得成功然而它卻很少在圖形界面程序上嶄露頭角究其原因Java語言缺省的圖形界面開發包AWT和SWING實在是難脫其究 無論速度和外觀它們都難以讓人接受 如今Eclipse組織編寫的SWT開發包為Java程序員提供了AWT和SWING之外的一個更佳的選擇在本文中對SWT做了簡單但盡可能全面的介紹
Java語言的聲望和它在桌面應用程序(GUI程序)所取得的成就顯然極不相符至今仍然很少能看到非常成功Java桌面程序雖然有JBuilderNetbeanJProbe等大型軟件作為代表但這仍不能證明Java的GUI程序是成功的它們的外觀總是和同一操作系統平台下的其它軟件顯得格格不入對機器配置的需求也似乎永無止境這使得它們只能被一些總是擁有當前最高性能PC的程序員們所容忍或是那些不在乎金錢和時間的專業用戶所接受對絕大多數計算機使用者來說AWT或SWING代表著怪異的界面和無法接受的速度Standard Widget Toolkit(SWT)或許是Java這一噩夢的終結者廣大Java程序員終於可以開發出高效率的GUI程序它們擁有標准的外觀幾乎沒有人能看出你的程序是用Java寫出來的更為重要的是這些程序是跨平台的
SWT本身僅僅是Eclipse組織為了開發Eclipse IDE環境所編寫的一組底層圖形界面 API或許是無心插柳或是有意為之至今為止SWT無論是在性能和外觀上都超越了SUN公司提供的AWT和SWING目前Eclipse IDE已經開發到了版本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路徑下的眾多子目錄下去搜索SWTJAR文件在找到的JAR文件中包含了SWT全部的Java類文件因為SWT應用了JNI技術因此同時也要找到相對應的JNI本地化庫文件由於版本和操作平台的不同本地化庫文件的名稱會有些差別比如SWTWINDLL是Window平台下Eclipse Build 的動態庫而在Unix平台相應版本的庫文件的擴展名應該是so等等注意的是Eclipse是一個開放源代碼的項目因此你也可以在這些目錄中找到SWT的源代碼相信這會對開發很有幫助下面是一段打開空窗口的代碼(只有main方法)
import comeoneexample;
public class OpenShell{
public static void main(String [] args) {
Display display = new Display();
Shell shell = new Shell(display);
shellopen();
// 開始事件處理循環直到用戶關閉窗口
while (!shellisDisposed()) {
if (!displayreadAndDispatch())
displaysleep();
}
displaydispose();
}
}
確信在CLASSPATH中包括了SWTJAR文件先用Javac編譯例子程序編譯無錯後可運行java Djavalibrarypath=${你的SWT本地庫文件所在路徑} comeoneexampleOpenShell比如SWTWINDLL件所在的路徑是C:\swtlib運行的命令應該是java Djavalibrarypath=c:\swtlib comeoneexampleOpenShell成功運行後系統會打開了一個空的窗口
剖析SWT API
下面再讓我們進一步分析SWT API的組成所有的SWT類都用orgeclipseswt做為包的前綴下面為了簡化說明我們用*號代表前綴orgeclipseswt比如*widgets包代表的是orgeclipseswtwidgets包
我們最常用的圖形構件基本都被包括在*widgets包中比如ButtonComboTextLabelSashTable等等其中兩個最重要的構件當數Shell和CompositeShell相當於應用程序的主窗口框架上面的例子代碼中就是應用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接口和相應的事件對象例如常用的鼠標事件監聽接口MouseListenerMouseMoveListener和MouseTrackListener及對應的事件對象MouseEvent
*graphics包中可以找到針對圖片光標字體或繪圖的API比如可通過Image類調用系統中不同類型的圖片文件通過GC類實現對圖片構件或顯示器的繪圖功能
對不同平台Eclipse還開發了一些富有針對性的API例如在Windows平台可以通過*olewin包很容易的調用ole控件這使Java程序內嵌IE浏覽器或WordExcel等程序成為可能!
更復雜的程序
下面讓我們展示一個比上面例子更加復雜一些的程序這個程序擁有一個文本框和一個按鍵當用戶點擊按鍵的時候文本框顯示一句歡迎信息
為了文本框和按鍵有比較合理的大小和布局這裡采用了GradLayout布局方式這種布局是SWT中最常用也是最強大的布局方式幾乎所有的格式都可能通過GradLayout去達到下面的程序也涉及到了如何應用系統資源(Color)以及如何釋放系統資源
private void initShell(Shell shell) {
//為Shell設置布局對象
GridLayout gShellLay = new GridLayout();
shellsetLayout(gShellLay);
//構造一個Composite構件作為文本框和按鍵的容器
Composite panel = new Composite(shellSWTNONE);
//為Panel指定一個布局結構對象
這裡讓Panel盡可能的占滿Shell
也就是全部應用程序窗口的空間
GridData gPanelData = new GridData
(GridDataGRAB_HORIZONTAL|
GridDataGRAB_VERTICAL|GridDataFILL_BOTH);
panelsetLayoutData(gPanelData);
//為Panel也設置一個布局對象
文本框和按鍵將按這個布局對象來顯示
GridLayout gPanelLay = new GridLayout();
panelsetLayout(gPanelLay);
//為Panel生成一個背景色
final Color bkColor = new
Color(DisplaygetCurrent());
panelsetBackground(bkColor);
//生成文本框
final Text text = new
Text(panelSWTMULTI|SWTWRAP);
//為文本框指定一個布局結構對象
這裡讓文本框盡可能的占滿Panel的空間
GridData gTextData = new GridData
(GridDataGRAB_HORIZONTAL|
GridDataGRAB_VERTICAL|GridDataFILL_BOTH);
textsetLayoutData(gTextData);
//生成按鍵
Button butt = new Button(panelSWTPUSH);
buttsetText(Push);
//為按鍵指定鼠標事件
buttaddMouseListener(new MouseAdapter(){
public void mouseDown(MouseEvent e){
//當用戶點擊按鍵的時候顯示信息
textsetText(Hello SWT);
}
});
//當主窗口關閉時會觸發DisposeListener
這裡用來釋放Panel的背景色
shelladdDisposeListener(new DisposeListener(){
public void widgetDisposed(DisposeEvent e) {
bkColordispose();
}
});
}
把這段代碼中的方法initShell()加入到第一個打開空窗口的例子中得到的是一段能成功運行的完整GUI應用程序運行方法可參考第一個例子
系統資源的管理
在一個圖形化的操作系統中開發程序都要調用系統中的資源如圖片字體顏色等通常這些資源都是有限的程序員務必非常小心的使用這些資源當不再使用它們時就請盡快釋放不然操作系統遲早會油盡燈枯不得不重新啟動更嚴重的會導致系統崩潰
SWT是用Java開發的Java語言本身的一大優勢就是JVM的垃圾回收機制程序員通常不用理會變量的釋放內存的回收等問題那麼對SWT而言系統資源的操作是不是也是如此?答案是一個壞消息一個好消息
壞消息是SWT並沒采用JVM的垃圾回收機制去處理操作系統的資源回收問題一個關鍵的因素是因為JVM的垃圾回收機制是不可控的也就是說程序員不能知道也不可能做到在某一時刻讓JVM回收資源!這對系統資源的處理是致命的試想你的程序希望在一個循環語句中去查看數萬張圖片常規的處理方式是每次調入一張查看然後就立即釋放該圖片資源而後在循環調入下一張圖片這對操作系統而言任何時刻程序占用的僅僅是一張圖片的資源但如果這個過程完全交給JVM去處理也許會是在循環語句結束後JVM才會去釋放圖片資源其結果可能是你的程序還沒有運行結束操作系統已經宕掉
但下面的好消息也許會讓這個壞消息變得無關緊要對於SWT只需了解兩條簡單的黃金法則就可以放心的使用系統資源!之所以稱為黃金法則一是因為少只有兩條二是因為它們出奇的簡單第一條是誰占用誰釋放第二條是父構件被銷毀子構件也同時被銷毀第一條原則是一個無任何例外的原則只要程序調用了系統資源類的構造函數程序就應該關心在某一時刻要釋放這個系統資源比如調用了
Font font = new Font (display Courier SWTNORMAL);
那麼就應該在不在需要這個Font的時候調用
fontdispose();
對於第二個原則是指如果程序調用某一構件的dispose()方法那麼所有這個構件的子構件也會被自動調用dispose()方法而銷毀通常這裡指的子構件與父構件的關系是在調用構件的構造函數時形成的比如
Shell shell = new Shell();
Composite parent = new Composite(shellSWTNULL);
Composite child = new Composite(parentSWTNULL);
其中parent的父構件是shell而shell則是程序的主窗口所以沒有相應的父構件同時parent又包括了child子構件如果調用shelldispose()方法應用第二條法則那麼parent和child構件的dispose()方法也會被SWT API自動調用它們也隨之銷毀
線程問題
在任何操作平台的GUI系統中對構件或一些圖形API的訪問操作都要被嚴格同步並串行化例如在一個圖形界面中的按鍵構件可被設成可用狀態(enable)或禁用狀態(disable)正常的處理方式是用戶對按鍵狀態設置操作都要被放入到GUI系統的事件處理隊列中(這意味著訪問操作被串行化)然後依次處理(這意味著訪問操作被同步)想象當按鍵可用狀態的設置函數還沒有執行結束的時候程序就希望再設置該按鍵為禁用狀態勢必會引起沖突實際上這種操作在任何GUI系統都會觸發異常
Java語言本身就提供了多線程機制這種機制對GUI編程來說是不利的它不能保證圖形構件操作的同步與串行化SWT采用了一種簡單而直接的方式去適應本地GUI系統對線程的要求在SWT中通常存在一個被稱為用戶線程的唯一線程只有在這個線程中才能調用對構件或某些圖形API的訪問操作如果在非用戶線程中程序直接調用這些訪問操作那麼SWTExcepiton異常會被拋出但是SWT也在*widgetDisplay類中提供了兩個方法可以間接的在非用戶線程的進行圖形構件的訪問操作這是通過的syncExec(Runnable)和asyncExec(Runnable)這兩個方法去實現的例如
//此時程序運行在一個非用戶線程中並且希望在構件panel上加入一個按鍵
DisplaygetCurrent()asyncExec(new Runnable() {
public void run() {
Button butt = new Button(panelSWTPUSH);
buttsetText(Push);
}
});
方法syncExec()和asyncExec()的區別在於前者要在指定的線程執行結束後才返回而後者則無論指定的線程是否執行都會立即返回到當前線程
SWT的擴展JFace
JFace與SWT的關系好比Microsoft的MFC與SDK的關系JFace是基於SWT開發其API比SWT更加易於使用但功能卻沒SWT來的直接比如下面的代碼應用JFace中的MessageDialog打開一個警告對話框
MessageDialogopenWarning(parentWarningWarning message);
如果只用SWT完成以上功能語句不會少於行!
JFace原本是為更加方便的使用SWT而編寫的一組API其主要目的是為了開發Eclipse IDE環境而不是為了應用到其它的獨立應用程序因此在Eclipse 版本之前很難將JFace API完整的從Eclipse的內核API中剝離出來總是要多多少少導入一些非JFace以外的Eclipse核心代碼類或接口才能得到一個沒有任何編譯錯誤的JFace開發包但目前Eclipse組織似乎已經逐漸意識到了JFace在開發獨立應用程序起到的重要作用在正在開發的版本中JFace也開始變成了和SWT一樣的完整獨立的開發包只是這個開發包還在變動中(筆者寫本文時應用的EclipseM版本)JFace開發包的包前綴是以orgeclipsejface開頭JAR包和源代碼也和SWT一樣也在${你的eclipse安裝路徑}\plugins路徑下去找
對開發人員來說在開發一個圖形構件的時候比較好的方式是先到JFace包去找一找看是不是有更簡潔的實現方法如果沒有再用SWT包去自己實現比如JFace為對話框提供了很好的支持除了各種類型的對話框(比如上面用的MessageDialog或是帶有Title欄的對話框)如要實現一個自定義的對話框也最好從JFace中的Dialog類繼承而不是從SWT中的*widgetDialog繼承
應用JFace中的Preference包中的類很容易為自己的軟件做出一個很專業的配置對話框對於TreeTable等圖形構件它們在顯示的同時也要和數據關聯例如Table中顯示的數據在JFace中的View包中為此類構件提供了ModelView方式的編程方法這種方法使顯示與數據分開更加利於開發與維護JFace中提供最多的功能就是對文本內容的處理可以在orgeclipsejfacetext*包中找到數十個與文本處理相關類
與應用程序更近一步
Java程序通常是以class文件的方式發布的運行class需要JRE或JDK的支持這又是Java GUI程序的另一個致命的弱點想象對一個面向廣大用戶的應用程序來說無論你的程序功能有多簡單或是你的代碼十分的精簡你都不得不讓用戶去下載一個M的JRE那是多麼令人沮喪的一件事而且對程序員來說Class通常意味著源代碼的暴露反編譯的工具讓那些居心叵測的人輕易得到你的源代碼雖然有很多對Class的加密方法但那總是以犧牲性能為代價的好在我們還有其它的方式可用把Class編譯成exe文件!
通過SWT開發包簡單跨平台可靠等這些Java語言本身所具有的優點正漸漸融合到圖形界面的應用程序開發中去因此我相信Java語言的另一扇成功之門正在逐漸打開
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26580.html