在所有SWT組件中,Button幾乎是最常用的,其功能在對(duì)于一般的情況來(lái)說(shuō)也足夠豐富了。你可以為Button組件設(shè)置要顯示在其中的文本或者圖像、設(shè)定ToolTip,甚至只要修改一個(gè)風(fēng)格樣式就能得到一個(gè)看上去相當(dāng)不錯(cuò)的方向箭頭按鈕。
然而,我對(duì)Button組件還是不能感到滿(mǎn)意。最大的遺憾就是:對(duì)它的外觀,所能做的工作也就僅限于此了。如果你想讓按鈕擁有一個(gè)漂亮的、漸變色的背景和一些特殊的文字效果,怎么辦呢?答案是沒(méi)有辦法。Button類(lèi)里面似乎沒(méi)有任何方法提供我想要的功能。
我曾嘗試過(guò)的第一個(gè)想法是用Button.addPaintListener來(lái)修改按鈕的外觀。但是,結(jié)果令人失望——雖然它顯示出來(lái)的時(shí)候的確按照預(yù) 想進(jìn)行繪制了,但是當(dāng)你用鼠標(biāo)去按它的時(shí)候,馬上又變回了原本灰頭土臉的樣子。顯然,在按下按鈕的時(shí)候,它并不是觸發(fā)paint事件,而是按照自己的想法 畫(huà)出原本的按鈕,于是我的工作全部白費(fèi)了。
如果嘗試為按鈕設(shè)定圖像會(huì)怎么樣呢?這也不是一個(gè)好主意。首先,不管你選擇什么樣的圖像, 都沒(méi)辦法去掉按鈕四周的邊框,而正是這些邊框嚴(yán)重破壞了圖像的和諧感;其次,如果你的程序有幾十甚至上百個(gè)按鈕,為每個(gè)按鈕都維護(hù)一幅圖像(甚至更多—— 理論上每個(gè)按鈕在普通狀態(tài)和被按下、禁用的狀態(tài)下,甚至當(dāng)鼠標(biāo)移進(jìn)移出按鈕的時(shí)候,都應(yīng)當(dāng)顯示不同的圖像)明顯是在浪費(fèi)系統(tǒng)資源;如果你們的美工聽(tīng)說(shuō)需要 做幾百個(gè)圖片,大概也不會(huì)給你好臉色看。此外,圖像有一個(gè)嚴(yán)重的缺點(diǎn)是:它所擁有的像素?cái)?shù)目是固定的,難以隨著界面的放大和縮小同時(shí)變化。如果強(qiáng)制進(jìn)行縮 放的話(huà),會(huì)出現(xiàn)明顯的鋸齒和失真,最終讓你精心設(shè)計(jì)的窗口變得慘不忍睹。最好還是放棄這個(gè)想法。
如果以Canvas為基礎(chǔ),設(shè)計(jì)一個(gè) 偽裝的按鈕組件又如何呢?聽(tīng)起來(lái)好像很不錯(cuò),因?yàn)椴捎眠@種辦法的話(huà),我們對(duì)如何繪制組件的表面就有了完整的控制權(quán)。不過(guò)這也意味著你必須對(duì)按鈕的狀態(tài)進(jìn)行 手工維護(hù)。雖然Button本身是一個(gè)很簡(jiǎn)單的組件,但是重復(fù)去做標(biāo)準(zhǔn)按鈕已經(jīng)作好的工作似乎還是有點(diǎn)無(wú)謂。還有一件事情是應(yīng)當(dāng)考慮的:我們知道, JFace中的Action機(jī)制可以將標(biāo)準(zhǔn)按鈕、菜單項(xiàng)和工具欄按鈕這三種界面組件納入一個(gè)統(tǒng)一的事件處理體系。然而,如果我們從Canvas派生去模擬 一個(gè)按鈕的話(huà),不論你模擬到多么相似的地步,它畢竟不是一個(gè)真正的Button,Action也不會(huì)給它同等的待遇。也就是說(shuō)手工制作的按鈕無(wú)法和 JFace Action體系協(xié)同工作——除非你去修改Action的處理方法,讓它去接納新的按鈕對(duì)象。這可不是一件輕松的工作。
如果上面的方法都行不通的話(huà),應(yīng)當(dāng)怎么辦呢?我們知道,和Swing這樣的框架不同,SWT中的按鈕其實(shí)就是操作系統(tǒng)底層所實(shí)現(xiàn)的按鈕(這一點(diǎn)也可以用 SPY++或者Winsight32之類(lèi)的工具證實(shí))。同時(shí)我們也知道,操作系統(tǒng)——至少是Windows系統(tǒng),對(duì)按鈕已經(jīng)提供了自我繪制的機(jī)制,這就是 所謂的Owner Draw(稱(chēng)為所有者繪制的原因是因?yàn)槟J(rèn)情況下繪制消息是發(fā)送給按鈕的父窗口處理的,但是父窗口也可以把這個(gè)皮球再踢回給按鈕,讓它自己解決)。在 Win32 API中,凡是使用BS_OWNERDRAW風(fēng)格創(chuàng)建、并且能夠(通過(guò)消息反射)響應(yīng)WS_DRAWITEM消息的按鈕,都可以獲得這種定制的能力。 了解這一點(diǎn),接下來(lái)的任務(wù)就是研究Button組件有沒(méi)有開(kāi)放這個(gè)接口供我們修改了。對(duì)Button組件的源代碼進(jìn)行粗略的瀏覽后,我發(fā)現(xiàn)了如下的方法:
其中DRAWITEMSTRUCT結(jié)構(gòu)的出現(xiàn)是一個(gè)明顯的提示:這里就是WM_DRAWITEM消息的響應(yīng)函數(shù),很幸運(yùn)它沒(méi)有聲明為final的,只要重載它并提供自己的實(shí)現(xiàn)就行了。
看起來(lái)是個(gè)小case,實(shí)際上也是。不過(guò),還有一處小麻煩需要克服。注意wmDrawChild方法沒(méi)有使用任何訪問(wèn)限定符,這意味著它是 package friendly的——同一個(gè)包中的對(duì)象可以訪問(wèn)和重載此方法,其他包中的對(duì)象就沒(méi)有這個(gè)權(quán)力了。也就是說(shuō),要定制按鈕對(duì)象,我們新建的對(duì)象也需要放在同 一個(gè)包(org.eclipse.swt.widgets)中。看起來(lái)有點(diǎn)像在使用Hack手段,不過(guò)為了突破SWT給我們的限制,眼下也只好稍稍將就一 下。好在swt的包沒(méi)有密封(Sealed),不然我就不得不再次宣稱(chēng)此路不通了。
既然障礙已經(jīng)掃清,接下來(lái)我們可以來(lái)實(shí)現(xiàn)前面的想 法了。這里我做了一個(gè)決定,在上述包中只加入一個(gè)抽象類(lèi),目的是把必要的接口暴露出來(lái);至于如何繪制按鈕,則留給具體的按鈕對(duì)象根據(jù)應(yīng)用程序的需求來(lái)決 定。這樣,不管你希望實(shí)現(xiàn)Windows XP風(fēng)格的按鈕、還是卡通風(fēng)格的按鈕、或是平面樣式的,總之不論什么千奇百怪的風(fēng)格,只要繼承一個(gè)類(lèi)并重載一個(gè)繪制方法就行了,而不必每次都要和 Button類(lèi)的內(nèi)部打交道。
基于這種考慮,實(shí)現(xiàn)自繪按鈕的抽象類(lèi)如下:
注意這個(gè)抽象類(lèi)所作的工作。在構(gòu)造函數(shù)中,它調(diào)用操作系統(tǒng)方法為自己加入了BS_OWNERDRAW風(fēng)格。如果沒(méi)有這一步,那么操作系統(tǒng)將不 會(huì)把這個(gè)按鈕視為自繪的按鈕,也不會(huì)向其發(fā)送任何繪制消息。接下來(lái)是WM_DRAWITEM消息的響應(yīng)函數(shù)。在這個(gè)函數(shù)中,我們簡(jiǎn)單的把必要的繪制參數(shù)提 取出來(lái),然后調(diào)用抽象方法ownerDraw去進(jìn)行實(shí)際的繪制工作。任何從OwnerDrawButton類(lèi)派生的按鈕對(duì)象必須重載此ownerDraw 方法,來(lái)決定如何繪制自身。
作為一個(gè)例子,我實(shí)現(xiàn)了一個(gè)具體的按鈕類(lèi)。這個(gè)按鈕用從上至下的漸變色背景添充整個(gè)按鈕,然后繪制出按鈕的文字。 如果當(dāng)前按鈕被按下,該類(lèi)還調(diào)整了一下文字的位置,以顯示出“按下”的外觀效果。代碼稍微有些長(zhǎng),這是因?yàn)橄⒑瘮?shù)所提供的是一個(gè)操作系統(tǒng)才了解的原生 HDC對(duì)象,而不是我們所熟悉的GC類(lèi),因此也需要相應(yīng)的用原生API進(jìn)行處理。不過(guò),其原理是相當(dāng)簡(jiǎn)單的——你只需要在給出的HDC上畫(huà)出你想要的任何 效果就行了。
如果你使用的是JDK 1.4或者更低的版本,請(qǐng)把@Override標(biāo)記去掉以后才能編譯,因?yàn)檫@是一個(gè)Java 5.0中才有的特性。此外,我重載了checkSubclass方法并提供了一個(gè)空的實(shí)現(xiàn);如果不這么做的話(huà),那么SWT在默認(rèn)情況下是不允許你從Button類(lèi)繼承的。
這個(gè)地方請(qǐng)?jiān)试S我稍稍跑一下題。上面代碼中的fillGradientRectangle方法——從它的名字你大概可以猜到,這個(gè)方法的作用是畫(huà)出一個(gè) 漸變色的矩形區(qū)域。我是從GC.fillGradientRectangle中“偷”來(lái)的代碼,針對(duì)按鈕類(lèi)作了一些修改就可以了。讓我感到訝異的是,在整 理這段代碼的時(shí)候,我發(fā)現(xiàn)從SWT中調(diào)用Win32 API實(shí)在是太方便了——比我原先猜想的還要容易得多。即便是微軟的P/Invoke也要比這麻煩。當(dāng)然,這很大程度上要?dú)w功于SWT將系統(tǒng)函數(shù)很好的封 裝在了一個(gè)OS靜態(tài)類(lèi)中。(如果你不知道P/Invoke是什么的話(huà),簡(jiǎn)單的說(shuō)它就是微軟在.Net平臺(tái)中提供的、用來(lái)調(diào)用系統(tǒng)API和自定義DLL中的 方法的技術(shù))。
上面那些繪圖的代碼基本上是Windows SDK的編程風(fēng)格。因?yàn)槲冶救擞泻芏噙@方面的開(kāi)發(fā)經(jīng)驗(yàn),所以這些代碼對(duì)我來(lái)說(shuō)是相當(dāng)清晰且直觀的。不過(guò)我估計(jì)純粹的Java程序員或許對(duì)這段代碼不會(huì)有很 大的好感。理論上講,我可以把這些代碼用更加OO的方式包裝起來(lái),從而看上去能好看一些。不過(guò),本文的目的在于講述實(shí)現(xiàn)技術(shù),用包裝的話(huà)反而會(huì)破壞效果。 如果你感興趣的話(huà),也可以嘗試自己來(lái)包裝一下。
需要講解的地方到這里就全部結(jié)束了。為了完整起見(jiàn),我把程序框架類(lèi)的代碼也列在下面,但是不做什么說(shuō)明——基本上每個(gè)SWT程序中這段代碼都是大同小異的。
下面是程序運(yùn)行的界面。盡管這遠(yuǎn)遠(yuǎn)算不上完美——真正的按鈕還應(yīng)該考慮,是否能夠和用戶(hù)的任何配置下,特別是有窗口主題的時(shí)候也能正常工作? 完美的按鈕實(shí)現(xiàn)可能需要至少數(shù)百行的代碼才行。不過(guò)對(duì)本文的目的來(lái)說(shuō),這樣已經(jīng)足夠了。可惜的是按下按鈕的效果無(wú)法從圖中體現(xiàn);你可以自己運(yùn)行一下這個(gè)程 序來(lái)體驗(yàn)一下實(shí)際的感覺(jué)。
然而,我對(duì)Button組件還是不能感到滿(mǎn)意。最大的遺憾就是:對(duì)它的外觀,所能做的工作也就僅限于此了。如果你想讓按鈕擁有一個(gè)漂亮的、漸變色的背景和一些特殊的文字效果,怎么辦呢?答案是沒(méi)有辦法。Button類(lèi)里面似乎沒(méi)有任何方法提供我想要的功能。
我曾嘗試過(guò)的第一個(gè)想法是用Button.addPaintListener來(lái)修改按鈕的外觀。但是,結(jié)果令人失望——雖然它顯示出來(lái)的時(shí)候的確按照預(yù) 想進(jìn)行繪制了,但是當(dāng)你用鼠標(biāo)去按它的時(shí)候,馬上又變回了原本灰頭土臉的樣子。顯然,在按下按鈕的時(shí)候,它并不是觸發(fā)paint事件,而是按照自己的想法 畫(huà)出原本的按鈕,于是我的工作全部白費(fèi)了。
如果嘗試為按鈕設(shè)定圖像會(huì)怎么樣呢?這也不是一個(gè)好主意。首先,不管你選擇什么樣的圖像, 都沒(méi)辦法去掉按鈕四周的邊框,而正是這些邊框嚴(yán)重破壞了圖像的和諧感;其次,如果你的程序有幾十甚至上百個(gè)按鈕,為每個(gè)按鈕都維護(hù)一幅圖像(甚至更多—— 理論上每個(gè)按鈕在普通狀態(tài)和被按下、禁用的狀態(tài)下,甚至當(dāng)鼠標(biāo)移進(jìn)移出按鈕的時(shí)候,都應(yīng)當(dāng)顯示不同的圖像)明顯是在浪費(fèi)系統(tǒng)資源;如果你們的美工聽(tīng)說(shuō)需要 做幾百個(gè)圖片,大概也不會(huì)給你好臉色看。此外,圖像有一個(gè)嚴(yán)重的缺點(diǎn)是:它所擁有的像素?cái)?shù)目是固定的,難以隨著界面的放大和縮小同時(shí)變化。如果強(qiáng)制進(jìn)行縮 放的話(huà),會(huì)出現(xiàn)明顯的鋸齒和失真,最終讓你精心設(shè)計(jì)的窗口變得慘不忍睹。最好還是放棄這個(gè)想法。
如果以Canvas為基礎(chǔ),設(shè)計(jì)一個(gè) 偽裝的按鈕組件又如何呢?聽(tīng)起來(lái)好像很不錯(cuò),因?yàn)椴捎眠@種辦法的話(huà),我們對(duì)如何繪制組件的表面就有了完整的控制權(quán)。不過(guò)這也意味著你必須對(duì)按鈕的狀態(tài)進(jìn)行 手工維護(hù)。雖然Button本身是一個(gè)很簡(jiǎn)單的組件,但是重復(fù)去做標(biāo)準(zhǔn)按鈕已經(jīng)作好的工作似乎還是有點(diǎn)無(wú)謂。還有一件事情是應(yīng)當(dāng)考慮的:我們知道, JFace中的Action機(jī)制可以將標(biāo)準(zhǔn)按鈕、菜單項(xiàng)和工具欄按鈕這三種界面組件納入一個(gè)統(tǒng)一的事件處理體系。然而,如果我們從Canvas派生去模擬 一個(gè)按鈕的話(huà),不論你模擬到多么相似的地步,它畢竟不是一個(gè)真正的Button,Action也不會(huì)給它同等的待遇。也就是說(shuō)手工制作的按鈕無(wú)法和 JFace Action體系協(xié)同工作——除非你去修改Action的處理方法,讓它去接納新的按鈕對(duì)象。這可不是一件輕松的工作。
如果上面的方法都行不通的話(huà),應(yīng)當(dāng)怎么辦呢?我們知道,和Swing這樣的框架不同,SWT中的按鈕其實(shí)就是操作系統(tǒng)底層所實(shí)現(xiàn)的按鈕(這一點(diǎn)也可以用 SPY++或者Winsight32之類(lèi)的工具證實(shí))。同時(shí)我們也知道,操作系統(tǒng)——至少是Windows系統(tǒng),對(duì)按鈕已經(jīng)提供了自我繪制的機(jī)制,這就是 所謂的Owner Draw(稱(chēng)為所有者繪制的原因是因?yàn)槟J(rèn)情況下繪制消息是發(fā)送給按鈕的父窗口處理的,但是父窗口也可以把這個(gè)皮球再踢回給按鈕,讓它自己解決)。在 Win32 API中,凡是使用BS_OWNERDRAW風(fēng)格創(chuàng)建、并且能夠(通過(guò)消息反射)響應(yīng)WS_DRAWITEM消息的按鈕,都可以獲得這種定制的能力。 了解這一點(diǎn),接下來(lái)的任務(wù)就是研究Button組件有沒(méi)有開(kāi)放這個(gè)接口供我們修改了。對(duì)Button組件的源代碼進(jìn)行粗略的瀏覽后,我發(fā)現(xiàn)了如下的方法:
package org.eclipse.swt.widgets; public class Button extends Control { … LRESULT wmDrawChild (int wParam, int lParam) { if ((style & SWT.ARROW) == 0) return super.wmDrawChild (wParam, lParam); DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT (); .... |
其中DRAWITEMSTRUCT結(jié)構(gòu)的出現(xiàn)是一個(gè)明顯的提示:這里就是WM_DRAWITEM消息的響應(yīng)函數(shù),很幸運(yùn)它沒(méi)有聲明為final的,只要重載它并提供自己的實(shí)現(xiàn)就行了。
看起來(lái)是個(gè)小case,實(shí)際上也是。不過(guò),還有一處小麻煩需要克服。注意wmDrawChild方法沒(méi)有使用任何訪問(wèn)限定符,這意味著它是 package friendly的——同一個(gè)包中的對(duì)象可以訪問(wèn)和重載此方法,其他包中的對(duì)象就沒(méi)有這個(gè)權(quán)力了。也就是說(shuō),要定制按鈕對(duì)象,我們新建的對(duì)象也需要放在同 一個(gè)包(org.eclipse.swt.widgets)中。看起來(lái)有點(diǎn)像在使用Hack手段,不過(guò)為了突破SWT給我們的限制,眼下也只好稍稍將就一 下。好在swt的包沒(méi)有密封(Sealed),不然我就不得不再次宣稱(chēng)此路不通了。
既然障礙已經(jīng)掃清,接下來(lái)我們可以來(lái)實(shí)現(xiàn)前面的想 法了。這里我做了一個(gè)決定,在上述包中只加入一個(gè)抽象類(lèi),目的是把必要的接口暴露出來(lái);至于如何繪制按鈕,則留給具體的按鈕對(duì)象根據(jù)應(yīng)用程序的需求來(lái)決 定。這樣,不管你希望實(shí)現(xiàn)Windows XP風(fēng)格的按鈕、還是卡通風(fēng)格的按鈕、或是平面樣式的,總之不論什么千奇百怪的風(fēng)格,只要繼承一個(gè)類(lèi)并重載一個(gè)繪制方法就行了,而不必每次都要和 Button類(lèi)的內(nèi)部打交道。
基于這種考慮,實(shí)現(xiàn)自繪按鈕的抽象類(lèi)如下:
package org.eclipse.swt.widgets; import org.eclipse.swt.internal.win32.*; public abstract class OwnerDrawButton extends Button { public OwnerDrawButton( Composite parent, int style ) { super( parent, style ); int osStyle = OS.GetWindowLong( handle, OS.GWL_STYLE ); osStyle |= OS.BS_OWNERDRAW; OS.SetWindowLong( handle, OS.GWL_STYLE, osStyle ); } LRESULT wmDrawChild( int wParam, int lParam ) { super.wmDrawChild( wParam, lParam ); DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT(); OS.MoveMemory( struct, lParam, DRAWITEMSTRUCT.sizeof ); ownerDraw( struct ); return null; } protected abstract void ownerDraw( DRAWITEMSTRUCT dis ); } |
注意這個(gè)抽象類(lèi)所作的工作。在構(gòu)造函數(shù)中,它調(diào)用操作系統(tǒng)方法為自己加入了BS_OWNERDRAW風(fēng)格。如果沒(méi)有這一步,那么操作系統(tǒng)將不 會(huì)把這個(gè)按鈕視為自繪的按鈕,也不會(huì)向其發(fā)送任何繪制消息。接下來(lái)是WM_DRAWITEM消息的響應(yīng)函數(shù)。在這個(gè)函數(shù)中,我們簡(jiǎn)單的把必要的繪制參數(shù)提 取出來(lái),然后調(diào)用抽象方法ownerDraw去進(jìn)行實(shí)際的繪制工作。任何從OwnerDrawButton類(lèi)派生的按鈕對(duì)象必須重載此ownerDraw 方法,來(lái)決定如何繪制自身。
作為一個(gè)例子,我實(shí)現(xiàn)了一個(gè)具體的按鈕類(lèi)。這個(gè)按鈕用從上至下的漸變色背景添充整個(gè)按鈕,然后繪制出按鈕的文字。 如果當(dāng)前按鈕被按下,該類(lèi)還調(diào)整了一下文字的位置,以顯示出“按下”的外觀效果。代碼稍微有些長(zhǎng),這是因?yàn)橄⒑瘮?shù)所提供的是一個(gè)操作系統(tǒng)才了解的原生 HDC對(duì)象,而不是我們所熟悉的GC類(lèi),因此也需要相應(yīng)的用原生API進(jìn)行處理。不過(guò),其原理是相當(dāng)簡(jiǎn)單的——你只需要在給出的HDC上畫(huà)出你想要的任何 效果就行了。
import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.*; import org.eclipse.swt.internal.win32.*; import org.eclipse.swt.widgets.*; public class TestButton extends OwnerDrawButton { TestButton( Composite parent ) { super( parent, SWT.PUSH ); } @Override protected void ownerDraw( DRAWITEMSTRUCT dis ) { Rectangle rc = new Rectangle( dis.left, dis.top, dis.right - dis.left,dis.bottom - dis.top ); Color clr1 = new Color( getDisplay(), 0, 255, 128 ); Color clr2 = new Color( getDisplay(), 0, 128, 255 ); fillGradientRectangle( dis.hDC, rc, true, clr1, clr2 ); clr1.dispose(); clr2.dispose(); SIZE size = new SIZE(); String text = getText(); char[] chars = text.toCharArray(); int oldFont = OS.SelectObject( dis.hDC, getFont().handle ); OS.GetTextExtentPoint32W( dis.hDC, chars, chars.length, size ); RECT rcText = new RECT(); rcText.left = rc.x; rcText.top = rc.y; rcText.right = rc.x + rc.width; rcText.bottom = rc.y + rc.height; if ( (dis.itemState & OS.ODS_SELECTED) != 0 ) OS.OffsetRect( rcText, 1, 1 ); OS.SetBkMode( dis.hDC, OS.TRANSPARENT ); OS.DrawTextW( dis.hDC, chars, -1, rcText, OS.DT_SINGLELINE | OS.DT_CENTER | OS.DT_VCENTER ); OS.SelectObject( dis.hDC, oldFont ); } private void fillGradientRectangle( int handle, Rectangle rc,boolean vertical, Color clr1, Color clr2 ) { final int hHeap = OS.GetProcessHeap(); final int pMesh = OS.HeapAlloc( hHeap, OS.HEAP_ZERO_MEMORY,GRADIENT_RECT.sizeof + TRIVERTEX.sizeof * 2 ); final int pVertex = pMesh + GRADIENT_RECT.sizeof; GRADIENT_RECT gradientRect = new GRADIENT_RECT(); gradientRect.UpperLeft = 0; gradientRect.LowerRight = 1; OS.MoveMemory( pMesh, gradientRect, GRADIENT_RECT.sizeof ); TRIVERTEX trivertex = new TRIVERTEX(); trivertex.x = rc.x; trivertex.y = rc.y; trivertex.Red = (short)(clr1.getRed() << 8); trivertex.Green = (short)(clr1.getGreen() << 8); trivertex.Blue = (short)(clr1.getBlue() << 8); trivertex.Alpha = -1; OS.MoveMemory( pVertex, trivertex, TRIVERTEX.sizeof ); trivertex.x = rc.x + rc.width; trivertex.y = rc.y + rc.height; trivertex.Red = (short)(clr2.getRed() << 8); trivertex.Green = (short)(clr2.getGreen() << 8); trivertex.Blue = (short)(clr2.getBlue() << 8); trivertex.Alpha = -1; OS.MoveMemory( pVertex + TRIVERTEX.sizeof, trivertex, TRIVERTEX.sizeof ); boolean success = OS.GradientFill( handle, pVertex, 2, pMesh, 1,vertical ? OS.GRADIENT_FILL_RECT_V : OS.GRADIENT_FILL_RECT_H ); OS.HeapFree( hHeap, 0, pMesh ); if ( success ) return; } @Override protected void checkSubclass() {} } |
如果你使用的是JDK 1.4或者更低的版本,請(qǐng)把@Override標(biāo)記去掉以后才能編譯,因?yàn)檫@是一個(gè)Java 5.0中才有的特性。此外,我重載了checkSubclass方法并提供了一個(gè)空的實(shí)現(xiàn);如果不這么做的話(huà),那么SWT在默認(rèn)情況下是不允許你從Button類(lèi)繼承的。
這個(gè)地方請(qǐng)?jiān)试S我稍稍跑一下題。上面代碼中的fillGradientRectangle方法——從它的名字你大概可以猜到,這個(gè)方法的作用是畫(huà)出一個(gè) 漸變色的矩形區(qū)域。我是從GC.fillGradientRectangle中“偷”來(lái)的代碼,針對(duì)按鈕類(lèi)作了一些修改就可以了。讓我感到訝異的是,在整 理這段代碼的時(shí)候,我發(fā)現(xiàn)從SWT中調(diào)用Win32 API實(shí)在是太方便了——比我原先猜想的還要容易得多。即便是微軟的P/Invoke也要比這麻煩。當(dāng)然,這很大程度上要?dú)w功于SWT將系統(tǒng)函數(shù)很好的封 裝在了一個(gè)OS靜態(tài)類(lèi)中。(如果你不知道P/Invoke是什么的話(huà),簡(jiǎn)單的說(shuō)它就是微軟在.Net平臺(tái)中提供的、用來(lái)調(diào)用系統(tǒng)API和自定義DLL中的 方法的技術(shù))。
上面那些繪圖的代碼基本上是Windows SDK的編程風(fēng)格。因?yàn)槲冶救擞泻芏噙@方面的開(kāi)發(fā)經(jīng)驗(yàn),所以這些代碼對(duì)我來(lái)說(shuō)是相當(dāng)清晰且直觀的。不過(guò)我估計(jì)純粹的Java程序員或許對(duì)這段代碼不會(huì)有很 大的好感。理論上講,我可以把這些代碼用更加OO的方式包裝起來(lái),從而看上去能好看一些。不過(guò),本文的目的在于講述實(shí)現(xiàn)技術(shù),用包裝的話(huà)反而會(huì)破壞效果。 如果你感興趣的話(huà),也可以嘗試自己來(lái)包裝一下。
需要講解的地方到這里就全部結(jié)束了。為了完整起見(jiàn),我把程序框架類(lèi)的代碼也列在下面,但是不做什么說(shuō)明——基本上每個(gè)SWT程序中這段代碼都是大同小異的。
import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.*; public class Application { public static void main( String[] args ) { Display display = Display.getDefault(); Shell shell = new Shell( display ); init( shell ); shell.pack(); shell.open(); while ( !shell.isDisposed() ) { if ( !display.readAndDispatch() ) display.sleep(); } } private static void init( Shell shell ) { shell.setText( "Owner Draw Button Test" ); FillLayout layout = new FillLayout(); layout.marginWidth = layout.marginHeight = 8; shell.setLayout( layout ); Button btn = new TestButton( shell ); btn.setText( "Owner Draw Button" ); btn.setToolTipText( "Hello, I'm a OwnerDraw Button!" ); } } |
下面是程序運(yùn)行的界面。盡管這遠(yuǎn)遠(yuǎn)算不上完美——真正的按鈕還應(yīng)該考慮,是否能夠和用戶(hù)的任何配置下,特別是有窗口主題的時(shí)候也能正常工作? 完美的按鈕實(shí)現(xiàn)可能需要至少數(shù)百行的代碼才行。不過(guò)對(duì)本文的目的來(lái)說(shuō),這樣已經(jīng)足夠了。可惜的是按下按鈕的效果無(wú)法從圖中體現(xiàn);你可以自己運(yùn)行一下這個(gè)程 序來(lái)體驗(yàn)一下實(shí)際的感覺(jué)。