Java 世界的人似乎一直都對 Java 的桌面應用程序十分不滿,從 AWT 到 SWING,從默認的 Theme到 第三方的產品,不是太難看(AWT)就是在某些平台有 BUG(SWING,Quaqua--一個Windows平台下的仿Mac 的主題包),再不就是對中文支持不好(某些第三方 LookAndFeel)。於是,如果想要獲得和本機平台一 致的用戶界面和比較穩定的性能,SWT就成了一個不可忽視的選擇。
當然,雖然這個專題名叫"全接觸",但畢竟不可能面面俱到,在一篇文章中兼收並蓄SWT的全部內容也 不現實。但不管怎麼說,我都將盡力展示SWT的使用細節,希望能為那些對SWT感興趣的人提供一些幫助。
1.SWT簡介
SWT-"Standard Widget Toolkit",它是一個Java平台下開放源碼的Native GUI組件庫,也是Eclipse 平台的UI組件之一。從功能上來說,SWT與AWT/SWING是基本等價的。SWT以方便有效的方式提供了便攜式 的(即Write Once,Run Away)帶有本地操作系統觀感的UI組件:
由於widget系統的固有復雜性以及平台之間微妙的差異,即使在理想情況下,能夠達到工業標准的跨 平台的widget類庫也是很難編寫和維護的。最早的AWT組件現在被認為是樣貌丑陋的,而且存在很多問題 ;SWING組件雖然也是缺點多多,但是隨著JDK版本的不斷升高,它仍在不斷進行著改進。我認為,SWT在 功能上與AWT/SWING不相伯仲,但是組件更為豐富,平台表現穩定,BUG也相對較少。如果你的應用程序真 的需要在多個平台上運行,需要更為美觀的界面,又不那麼依賴於其他基於AWT/SWING的圖形庫,那麼SWT 或許是一個比AWT/SWING更好的選擇。
2.SWT起步
2.1 SWT的HelloWorld
一如介紹其他程序的起始,我們都需要來一個HelloWorld來幫助我們入門,SWT的HelloWorld如下:
import org.eclipse.swt.widgets.*;
public class HelloWorld
{
public static void main(String[] args)
{
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Hello World");
shell.setSize(200, 100);
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep ();
}
display.dispose ();
}
}
運行這個程序就會得到如下結果:
下面我講逐一介紹這個程序所包含的內容。
Display
這是一個頂層容器組件,類似於Container或Component的功能,它主要負責與底層的窗口 系統之間的連接。在具體含義上,它代表"屏幕"。
一個Display可以包含多個Shell(也是容器組件, 下面會介紹到)。
通常情況下,一個應用程序只含一個Display,即Display通常是一個單例組件 (Singleton)。
Shell
它表示位於"屏幕"上面的"窗口",是Composite組件和Control組件構成的組件樹的根。
在 我們的HelloWorld程序中,我們可以設置標題(setText()),設置大小(setSize()),然後通過open() 方法來顯示這個窗口。怎麼樣,感覺很像JFrame吧?其實功能上差不多。
Composite
可以包含其它Composite和Control的容器
Control
這是一個重量級(HeavyWeight)系統對象。像按鈕(Button),標簽(Label),表格, 工具欄和樹形結構這些組件都是Control的子類,Conposite和Shell也不例外。
2.1.1 消息循環
我們可以看到,上面的代碼中有這樣的語句:
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep ();
}
如果你像我一樣是由Java語言起步的,那麼你會對這個消息循環的代碼感到比較陌生,畢竟在SWING中 我們主要利用事件驅動模型而不這樣利用類似於Windows程序設計中的消息循環的方法來處理事件。但是 這段代碼意義還算簡單明了,就是反復的讀取和分派(dispatch)事件,並在沒有事件的時候把控制權還 給CPU。
2.1.2 資源的釋放
最後一條語句是display.dispose ();,這告訴我們操作系統的資源是由程序員顯示釋放的。資源的釋 放遵循以下兩條規則:
1. 如果你創建了某個資源,那麼你就有責任釋放它。
2. 釋放父組件資源的同時也釋放了其子組件的資源。
2.1.3 標准構造函數
窗口組件被創建的時候必須伴隨一個他的上層組件,例如,我要建立一個按鈕就可以采用如下方法: Button button = new Button(shell, SWT.PUSH);
其中,Button的父組件Shell是必不可少的,這樣就限定了我們生成組件的順序。
第二個參數被稱為"Style Bit",表示了這個組件的顯示特性,每種特性占一位,如下例所示:
Text test=new Text(group, SWT.SINGLE|SWT.BORDER);
這條代碼生成了一個單一的,有邊框的文本框。這顯然又與習慣了JavaBeans模型,總是用setXXX()來 設置屬性的我們不太適應--畢竟是IBM的東西啊,秉承了其產品不易上手的傳統。
2.1.4 錯誤與異常
SWTError指的是不能修復的錯誤,以及一些操作系統錯誤。
SWTException指的是一些可恢復的錯誤以及無效的線程訪問之類的錯誤。
IllegalArgumentException指可修復的錯誤或參數為null之類的錯誤。
2.1.5 Item
Item類是一個輕量級的系統對象,總是作為基本的單位元素與其他一些類配合使用。比如Tree中的元 素即為TreeItem,Table的單位元素則是TableItem,而MenuItem就是Menu的基本單位元素了。
2.1.6 SWT的類階層體系結構
最後讓我們來整體認識一下整個SWT窗口組件的層次結構,如下所示:以上的部分給我們以整體的認識 ,即一個SWT引用程序應該怎麼創建,其基本的運行規則和相關類的體系結構。我想我就不用再對每一個 控件的API或使用方面費唇舌了,熟悉這些東西是體力勞動,而網上有很多例子可供參考。下面一節我將 詳細介紹有關SWT布局的相關知識。
2.2 SWT的布局管理
相信對於組件的布局(Layout)大家都不會太陌生,它的存在就是提供給我們一種可以在組件位置移 動或更改大小時重新繪制組件的機制。設置組件的布局我們可以采用Composite.setLayout()方法來實現 。
每種布局都有其相應的數據(Layout Data),可以通過Control.setLayoutData()方法來進行關聯。 以下是一些布局類及其顯示效果:
FillLayout:讓所有子組件等大小的"填滿"整個面板空間。
FillLayout是最簡單的一個布局類,它 將所有窗口組件放置到一行或一列中,並強制他們的大小也相等。FillLayout不能外覆(wrap),也不能 定制邊框和距離。很顯然這樣的限制讓這個布局類最適合作類似於計算器面板的布局,或者為Taskbar和 Toolbar上面的按鈕作布局使用。
RowLayout:類似於AWT中的FlowLayout,讓所有組件按行排列,一行排不下就放到下一行。
RowLayout比FillLayout用得更廣泛一些,原因很簡單,就是RowLayout支持FillLayout所部支持的功 能,例如能夠外覆,能夠修改邊框和間距等等。另外,每一個位於RowLayout中的窗口組件都可以通過設 定一個RowData類來指定其在RowLayout中的寬度和高度。
GridLayout: GridLayout是3個標准布局類中最有用的,但同時也是最復雜的--沒辦法,強大的功能 必定伴隨著一定程度的復雜性。通過GridLayout,一個Composite的子窗口組件被放置在一個網格(Grid )之中。GridLayout有很多配置字段,並且和RowLayout一樣,每一個布局於其中的窗口組件都可以有一 個與之相關聯的布局數據類,稱為GridData。GridLayout的強大功能是通過對於每一個窗口組件的 GridData 的靈活控制來實現的。
鑒於GridLayout的復雜性(原本我就懷疑它根本就不是為手工書寫代 碼而設計的),我並不建議各位直接手動書寫GridData,最好借助可視化的工具(如VI)來幫助我們完成 用GridLayout進行的界面設計。這樣我們只需要書寫少量控制代碼,就可以獲得復雜的界面布局了。
FormLayout:如圖所示
StackLayout:幾乎完全等同於CardLayout的功能。
在SWT中,位置和大小的變化並非自動發生的。應用程序既可以在Composite子類的構造函數中指定初 始位置和大小,也可以在一個改變窗口大小的監聽器中用布局類來定位和改變Composite子類的大小。
下面的一幅圖包含了我們將要討論的有關布局的大部分細節。一個Composite類的可顯示區域分為三個 部分,分別是 Location,clientArea和trim。Composite的大小就是clientArea和trim的區域之和。一個 布局類(Layout)的主要功能就是管理Composite子組件的大小和位置。通過布局類,我們可以管理子組 件之間的距離-即間距(Spaceing),子組件與布局邊緣之間的距離-即邊距(margin)。布局的大小同時 也是Composite的clientArea的大小。
至此,關於SWT的基礎部分就告一段落,希望能夠給大家以一個對於SWT的總體認識。下面的部分將主 要介紹SWT的弱項-繪圖。JGraph的一個作者就表達了對SWT/JFace/Draw2D的不滿,認為SWT在執行效率上 並沒有什麼改善,而且缺乏一些有用的API實現。話雖如此,但SWT的基本繪圖功能還是不錯的,如果有足 夠的時間和耐心的話還是可以繪出想要的圖形的。下面就讓我們看看SWT如何繪制2D和3D圖形。
3. 用SWT繪制2D圖形
用SWT繪圖通常由兩種方法,一種是借助Graphics Context,另一種是利用Draw2D。然而Draw2D是一個 基於SWT Composite的輕量級組件,於是在效率上,它無法體現出SWT的Native Code的速度優勢。故其雖 然強大,但僅適用於繪圖工作不是系統瓶頸的應用程序。所以我在這裡只介紹第一種方法。
3.1 Graphics Context
我們可以在任何實現了org.eclipse.swt.graphics.Drawable接口的類上繪制圖形,這包括一個控件, 一幅圖像,一個顯示設備或一個打印設備。類org.eclipse.swt.graphics.GC是一個封裝了所有可執行的 繪圖操作的圖形上下文(Graphics Context)。兩種使用GC的方式我們已經在本節前言中提過,稍後會作 詳細說明。
3.2 在一幅圖像上繪制圖形
下面一段代碼創建了一個帶有圖像的GC並在上面繪制了兩條線:
Image image = new Image(display,"C:/music.gif");
GC gc = new GC(image);
Rectangle bounds = image.getBounds();
gc.drawLine(0,0,bounds.width,bounds.height);
gc.drawLine(0,bounds.height,bounds.width,0);
gc.dispose();
image.dispose();
一旦你創建了一個GC,你就有責任通過它的dispose方法釋放它的資源。一個由應用程序創建的GC需要 立即被繪制,然後盡快釋放掉。這是因為每個GC都需要一個底層的系統資源,而在某些操作系統中這些資 源是稀缺的,像Win98就只允許同時創建五個GC對象。
3.3 在Control上繪圖
類org.eclipse.swt.widgets.Control是可繪制的,所以你可以用像在圖像上一樣的方式來繪制圖形。 而和在圖像上繪制所不同的是,如果你使用GC在一個Control上繪制圖形,你需要知道當操作系統自身要 繪制這個control的時候,它將覆蓋掉你的改動。所以在一個 Control上繪制圖形的正確方法是加入其繪 制事件的監聽器。監聽器類為org.eclipse.swt.events.PaintListener,其回調函數的參數是一個 org.eclipse.swt.events.PaintEvent類的實例。這個PaintEvent實例中包含一個GC的引用,你可以向這 個GC發送消息。下面的代碼示例說明了如何建立這種類型的繪圖:
Shell shell = new Shell(display);
shell.addPaintListener(new PaintListener(){
public void paintControl(PaintEvent e){
Rectangle clientArea = shell.getClientArea();
e.gc.drawLine(0,0,clientArea.width,clientArea.height);
}
});
shell.setSize(150,150)
3.4 剪切(Clipping)
GC的剪切域是可見繪圖發生的部分。在缺省情況下,一個GC是一個被構造的可視部分邊界。改變一個 GC的剪切域可以讓我們構造出各種圖形效果。其中的一個例子是如果你想填充一個缺失了邊緣的矩形。一 種方法是繪制多邊形矩形來組成所需要的圖形,另一種方法就是剪切GC,然後對其剪切部分進行填充。
shell.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
Rectangle clientArea = shell.getClientArea();
int width = clientArea.width;
int height = clientArea.height;
e.gc.setClipping(20,20,width - 40, height - 40);
e.gc.setBackground(display.getSystemColor(SWT.COLOR_CYAN));
e.gc.fillPolygon(new int[] {0,0,width,0,width/2,height});
}
});
這段代碼在Shell上的顯示的過程效果如下:
3.5 畫板(Canvas)
雖然任何Control都可以通過自身的paintEvent來繪制圖形,但其子類 org.eclipse.swt.widgets.Canvas 是專門被設計用來進行圖形操作的特殊的繪圖類。我們既可以使用一 個Canvas,再加入一個繪圖監聽器來實現繪圖,也可以通過繼承來建立一個可重用的自定義Control。 Canvas有很多style bit,可以在繪圖發生時產生作用。
3.6 繪制直線和圖形
我們有很多方法可以在一個GC上畫線,包括在兩點之間,一系列離散的點之間或一個預定義的圖形上 都可以。直線是以GC的前景色來繪制的,我們可以通過GC繪制擁有不同厚度的各式直線。對於一個Paint 事件,GC有著與Control組件一樣的屬性,即激發事件且缺省的直線樣式固定為1個像素寬。
GC.drawLine(int x1, int y1, int x2, int y2);這條語句在可繪制的面板上的兩點間花了一條直線 ,起始點為(x1,y1),終止點為(x2,y2)。終止點包含在畫好的直線中。如果起始點等於終止點的話,將 會有一個獨立的象素點被繪制出來。
GC.drawPolyline(int[] pointArray);這條語句繪制了一系列互相連接的線段,作為參數的數組用於 描述點的位置。語句gc.drawPolyline(new int[] { 25,5,45,45,5,45 });繪制了如下的圖形:
GC.drawPolygon(int[] pointArray);與drawPolyline(int[])是類似的,唯一區別在於最後一個點和 低一個點是連接的。 gc.drawPolygon(new int[] { 25,5,45,45,5,45 });將會獲得與上圖一樣的結果。
GC.drawRectangle(int x, int y, int width, int height);這條語句從左上角的(X,Y)點,用參 數中的寬和高畫出了一個矩形。gc.drawRectangle(5,5,90,45);將會繪制出如下圖形:
GC.drawRoundedRectangle(int x,int y,int width,int height,int arcWidth,int arcHeight);一個 圓矩形與標准矩形的區別就在於其四個角是圓的。圓矩形的每一個角都可以被想象成為1/4個橢圓,並且 arcWidth和arcHeight由完整的橢圓的寬和高決定。gc.drawRoundedRectangle(5,5,90,45,25,15);繪制了 一個左上角位置為5.5的圓矩形,右邊的圖形是放大後的效果:
GC.drawOval(int x, int y, int width, int height);一個橢圓是由其相對應的矩形的左上角的位置 (x,y)來確定繪制位置的,其寬和高即為對應矩形的寬和高。對於圓形來說,只需要另寬和高相等即可 。
GC.drawArc(int x, int y, int width, int height, int startAngle, int endAngle);曲線的繪制 也是與一個相應的矩形有關,即其左上角的位置與寬和高都是相應矩形的屬性。StartAntle是從橫向的X 開始計算的,所以0度指向的是東而不是北。曲線的繪制是從StartAngle到endAngle以逆時針方向執行。 gc.drawArc(5,5,90,45,90,200);所繪制的圖形如下:
GC.setLineStyle(int style);可以設置所繪制曲線的樣式,下面列出了一些曲線樣式常量(在 org.eclipse.swt.SWT中定義)和與之對應的曲線的圖像:
GC.setLineWidth(int width);可以用於指定所要繪制的曲線的寬度。缺省情況下的曲線寬度為 1個像素。
由於直線的樣式和寬度揮作用到所有的繪圖操作上,所以我們可以作出如點矩形或粗線橢圓這 樣的圖形:
3.7 繪制文本
文本可以被繪制在一個GC上,字形是用GC的前景色和字體來繪制的,並且 它所占用的區域是用GC背景色繪制的。要繪制文本,你需要定義要繪制文本的左上角,寬度和高度。有兩 組方法可以用來繪制文本,第一組方法的名字裡都帶有一個Text,並將會處理直線定界符和制表符。第二 組API方法集的名字裡都帶有String,它們沒有制表符或回車的處理,並主要用於控制像Eclipse的Java編 輯器StyledText這樣復雜的Control。
GC.drawText(String text, int x, int y);
Font font = new Font(display,"Arial",14,SWT.BOLD | SWT.ITALIC);
// ...
gc.drawText("Hello World",5,5);
gc.setForeground(display.getSystemColor (SWT.COLOR_BLUE));
gc.setFont(font);
gc.drawText ("Hello\tThere\nWide\tWorld",5,25);
// ...
font.dispose();
drawText API將控制字符\t處理為制表符,將\n處理為回車符。
GC.drawString(String text, int x, int y);
Font font = new Font(display,"Arial",14,SWT.BOLD | SWT.ITALIC);
// ...
gc.drawString("Hello World",5,5);
gc.setForeground(display.getSystemColor(SWT.COLOR_BLUE));
gc.setFont(font);
gc.drawString("Hello\tThere\nWide\tWorld",5,25);
// ...
font.dispose()
當使用drawString時,制表符和回車符將不會被處理。
在一個GC上繪制字符的時候,一個字符串所占用的大小取決於它的內容以及GC的字體。想要確定一個 字符串在被繪制之後所占用的區域可以使用方法:GC.stringExtent(String text), 或 GC.textExtent (String text)。這兩個方法都返回一個Point類,這個Point的X和Y是渲染參數字符串所需要的寬和高。
3.8 圖形填充
直線是用GC前景色繪制的,而圖形的填充用的是GC的背景色。
GC.fillPolygon(int[]);
gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
gc.fillPolygon(new int[] { 25,5,45,45,5,45 })
GC.fillRectangle(int x, int y, int width, int height);
gc.fillRectangle(5,5,90,45);
需要注意的是,當一個矩形被填充的時候,右面和下面的邊緣是不被包括在內的。
GC.fillRoundedRectangle(int x, int y, int width, int height, int arcWidth, int arcHeight);
gc.fillRoundRectangle(5,5,90,45,25,15);
像GC.fillRectangle(...)方法一樣,右面和下面的邊緣不被包含在內,於是右下角的坐標為(94,49 )而不是(95,50)。
GC.fillOval(int x, int y, int width, int height);
gc.fillOval(5,5,90,45);
GC.fillArc(int x, int y, int widt4h., int height, int startAngle, int endAngle);
gc.fillArc(5,5,90,45,90,200);
fillArc()的參數和drawArc()的參數是類似的,偏移量是從右面的軸開始填充,然後沿逆時針方向旋 轉給定的角度(endAngle-startAngle)。
GC.fillGradientRectangle(int x, int y, int width. int height, vertical boolean);
這個方法讓我們可以指定圖形在填充時所用的顏色可以從GC的前景色按梯度變化(漸變)到背景色。 梯度既可以是橫向的也可以是縱向的。
gc.setBackgrouind(display,getSystemColor(SWT.COLOR_BLUE));
gc.fillGradientRectangle(5,5,90,45,false);
上面兩條語句建立了一個使用黑色背景的從左至右的橫向梯度填充。和其他填充方法一樣,左面和下 面的邊緣不被包括在內,所以由下角的位置縮小一個像素。
gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
gc.setForeground(display.getSystemColor(SWT.COLOR_CYAN));
gc.fillGradientRectangle(5,5,90,45,true);
上面這3行代碼的含義為在縱向自頂向下用前cyan(景色)開始,並以藍色(背景色)結束的填充。
3.9 異或(XOR)
如果你設置了GC的XOR模式為true的話,將會發生如下情況:對於每一個像素點,原來被顯示的紅,綠 ,藍的值將被已存在的紅,綠,藍色進行異或操作,所得結果既作為新的目標像素。
shell.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
// ...
gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
gc.fillRectangle(5,5,90,45);
gc.setXORMode(true);
gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
gc.fillRectangle(20,20,50,50);
gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
gc.fillOval(80,20,50,50);
3.10 繪制圖像(Draw Image)
類org.eclipse.swt.graphics.Image被用來表示准備要在像打印機,顯示器這樣的設備上顯示的圖形 。建立一個圖像最簡單的方法就是從組織好的文件格式中裝載它。SWT所支持的圖像格式有:GIF,BMP, JGP,PNG和TIFF。
Image image = new Image(display,"C:/eclipse_lg.gif");
GC.drawImage(Image image, int x, int y);
每幅圖像都有用其邊界決定的尺寸。例如,圖象eclipse_lg.gif的大小為115*164,我們可以通過 image.getBounds()方法來進行設定。當一幅圖像被繪制的時候,它將會以自身定義的邊界作為顯示之後 的寬和高。gc.drawImage(image,5,5);
至此,SWT在2D繪圖方面的講解告一段落,上面所提到的內容涵蓋了SWT的大部分繪圖功能,並在每個 部分都給出了要注意的細節。至於具體實現就要靠各位的聰明才智了。下面讓我們進入最後的部分-SWT的 3D繪圖。
4 SWT與OpenGL編程
相較於Java3D API來說,SWT以前在3D圖形繪制方面一直沒有什麼好的表現。OpenGL的加入會不會使 SWT在3D領域有所作為還尚未可知,不過起碼IBM的程序員們給了SWT機會。當大家了解了這個正處於試驗 階段的組合之後,我們在SWT上繪制3D圖形就不再是噩夢。
OpenGL是一個為創建高性能2D,3D圖形而設計的多平台的標准。其硬件和軟件的實現存在於多個系統 之中,包括Windows,Linux和 MacOS。OpenGL可以用於渲染簡單的2D圖形或復雜的3D游戲圖形(OpenGL最 主要的應用領域就是游戲)。作為一個正在處於事件階段的 Eclipse插件,我將在下面的小節中介紹如何 在SWT窗口組件上用SWT繪制圖形。在Eclipse最新的3.2版中,對OpenGL的支持被集成到 org.eclipse.swt 項目中,所以我們在實現的時候即可以選擇以插件方式進行,也可以直接利用已經集成好的組件來進行圖 形操作。在本節,我們將以插件方式為例對代碼進行說明。
4.1 SWT OpenGL插件
SWT實現了OpenGL1.1全部功能。包括三個核心類和一個數據類。核心類為GLContext,GL和GLU。 GLContext架起了 SWT和OpenGL之間的橋接。一個Context必須用Drawable,通常是用Canvas來創建, OpenGL可以在Drawable上渲染場景。需要注意的是,當context不再被使用的時候就應該將它釋放掉。同 樣,一旦某個context被釋放掉之後,就不應該再次試圖去渲染它。每次 Drawable改變大小的時候, context都需要通過調用其resize方法在通知這一事件。這個方法的調用讓context調整自己的view port 和視圖參數。在下一節中將描述一個處理這一部分任務的類。
當context可用的時候,我們就可以通過定義在GL和GLU的一系列方法調用來繪制場景。一個GL類大概 有超過330條命令。在GL和GLU中定義的這些函數和他們的Native實現幾乎是一一對應的。下圖給出了一個 繪制矩形的例子,我們可以看到用C寫成的API和SWT OpenGL API是何其相似:
4.2 SWT OpenGL編程基礎
在下面的小節中,我將描述一個顯示四幅3D圖像的應用程序。應用程序采用了GLSense,這是一個用於 顯示OpenGL場景的工具類。它和SWT 的Canvas很像,所區別的是它所展現的內容是用OpenGL命令渲染的, 而不是使用GC來繪制。要做到這一點,我們需要將一個GLContext類和一個SWT Canvas相關聯,並且無論 何時,當前上下文中的內容都應該是由在drawScene中定義的命令來渲染的。
查看原圖(大圖)
在構造函數中,一個SWT Canvas被創建出來。這就是那個要和一個GLContext相關聯的Canvas實例。緊 接著,這個Canvas又注冊了兩個監聽器。第一個監聽器的作用是確保這個Canvas無論何時被改變大小,其 相應的GLContex也會收到通知並適當的改變大小。第二個監聽器主要用於確保一旦Canvas被釋放之後,其 相對應的GLContext的也同時被釋放。為了確保渲染區域是一個非零大小的區域,父組件的客戶矩形區被 取出來用於設置該Canvas的初始大小。這個初始大小可以在稍後用布局管理器或用戶Action來修改。
GLScene將Canvas的全部區域用於繪圖。無論Canvas何時調整其尺寸,我們都要獲取客戶區並將 新的寬度和高度傳遞給Contex,而context將根據新的寬度和高度適當的調整視圖。
XML error: The image is not displayed because the width is greater than the maximum of 572 pixels. Please decrease the image width.
GLScene被分割為兩個部分:初始化Context和初始化OpenGL的狀態機。對於Context來說,我們只是簡 單的建立一個新的 GLContext並使它成為當前被使用的Context。OpenGL的渲染總是在當前的context上進 行繪制,因此如果你有超過一個活動的 GLScene,很重要的一點是要在所有繪制動作發生之前將它的 Context設置為當前的Context。initGL方法最開始提供清除顏色緩存顏色,隨後建立了一個深度緩存 (depth buffer).第47行指出了深度值如何進行比較。這一比較函數主要用於拒絕或接受正在引用的像 素。GL.GL_LEQUAL選項指定接受那些在視圖上更接近或有相同距離的像素。第48行啟動了深度測試 (depth test),緊接的一行設定陰影模型為GL.GL_SMOOTH,這一設定的效果是如果表面上的兩個頂點顏 色不同的話,系統將對顏色進行插值。最後,第 50行要求渲染引擎在計算顏色和紋理協調插值運算的時 候起到關鍵的作用。
XML error: The image is not displayed because the width is greater than the maximum of 572 pixels. Please decrease the image width.
GLScene類的最後兩個方法用於處理重繪和場景繪制。當場景何時需要重繪的時候,第一個方法為其他 類提供重繪操作的接口。第二個方法主要用於讓繼承GLScene的子類覆寫。其缺省實現只是簡單的清除了 顏色和深度緩存,通過裝在鑒別矩陣(identify matrix)重新恢復調整系統。
4.3 3D Chart
利用上一節的准備,我們已經將主應用程序進行了劃分。這個圖像顯示了4組數據。每一組數據都是由 相同的固定點所組成,每個點都是從0.0到10.0之間的一個正值。
示例程序運行在一個非常簡單的Eclipse view上,唯一值得注意的是Refresher,這個線程將強迫 OpenGL場景被周期性的重繪。通過這種方法,當視圖被移動或旋轉的時候,component總能進行有效的更 新渲染效果。run()方法調用的時間間隔為100毫秒,所以理論上的圖像速度能達到每秒10幀。
每個數據集合的點的值是用圓柱體來表示的。通過執行3個GLU調用,我們就能夠繪制圓柱體:其中的 兩個用於渲染圓柱體兩頭的圓盤部分,另外一個用於渲染圓柱體的四周。例如,要渲染兩個單元高的圓柱 體,你可以用下面的代碼來實現:
第一行申請了繪制圓盤和圓柱所需的二次曲面。然後整個場景被逆時針旋轉了90度,以便圓柱體可以 被垂直繪制。下一步,底部的圓盤被渲染,然後是圓柱體的四周。在我們能夠繪制頂部圓盤的時候,通過 場景轉換(scene translation),我們可以在Z軸移動兩個單元。最後一個圓盤隨後被繪制出來,調整系 統通過向回移動兩個單元來進行恢復。最後,由第一行申請的二次曲面被釋放掉。
按照上述方法運行程序是很費時間的。當僅繪制一個圓柱體的時候,效率低下不是一個很嚴重的問題 ,但如果要繪制成百個對象的話就會嚴重影響程序的執行性能。對於這種情況,OpenGL給出了一個解決這 個問題的技巧,就是使用顯示列表(display list)。
一個顯示列表是一組已編譯的OpenGL命令。定義命令集合的列表被放在glNewList(int list, int mode) 和 glEndList()方法調用之間。第一個參數必須是一個正整數,可以用來唯一的表示一個被創建的 顯示列表。你可以讓GL用 glGenLists(int n)方法為你生成多個列表標識符。第二個參數用於指定列表是 否被編譯或編譯之後立即被執行。大多數情況下你都需要編譯這個列表。然後,你可以使用 glCallList (int list)方法來顯示整個列表。
5 結束語
至此,有關於SWT與OpenGL圖形有關的粗略功能就介紹完了,有鑒於3D圖形對象和OpenGL的復雜性,一 篇這樣篇幅的文章肯定不能覆蓋其每一個角落,我只能給各位一個動手嘗試機會。希望整篇專題沒有讓你 枯燥得睡著,並因此有了一個不錯的SWT的基礎,我的目的就達到了。