概述Swing核心組件
UI Delegate
如果說(shuō)模型接口是最編寫自定義組件最重要的部分,那么UI delegate則是最復(fù)雜的部分。主要問(wèn)題在于:如何編寫繪制邏輯使得他在所有的look and feels上都一致?有時(shí),除非你編寫每個(gè)look and feels所對(duì)應(yīng)的UI delegate(像 SwingX project 就是通過(guò)這種方式),否則是無(wú)法實(shí)現(xiàn)的。但是,在某些情況下你會(huì)發(fā)現(xiàn)你可以通過(guò)組裝現(xiàn)有的Swing核心組件來(lái)達(dá)到這種仿真的效果,在后面的部分,UI delegates代碼將要注意那些平臺(tái)相關(guān)的設(shè)置比如說(shuō)顏色,字體和抗鋸齒。
在" Enhancing Swing Applications" 一文中,作者描述了通過(guò)的繼承定義好的look and feels的簡(jiǎn)單UI delegate實(shí)現(xiàn)的樣板代碼。以install*和unstall*方法開(kāi)始,如果你不打算使用他們,第三方的look and feel 可能會(huì)在基本的功能基礎(chǔ)上增加一些額外的功能(舉個(gè)例子來(lái)說(shuō),在slider上增加鼠標(biāo)滾輪滾動(dòng)功能)
在我們這個(gè)例子中,我們可以看到這個(gè)自定義組件包含一個(gè)slider和一組labels(包括圖標(biāo)和文本),由于所有的 JComponent都是容器類,我們可以輕松地通過(guò)在我們的installComponents方法中增加JSlider和JLabel(每個(gè) label是一個(gè)選項(xiàng)control point)組件來(lái)達(dá)到這種仿真的虛擬界面效果(別忘了在uninstallComponents方法中移除他們)。通過(guò)重用Swing核心組件的方式,我們的自定義組件能夠使其在swing核心LAF和第三方LAF下都保持一致。
當(dāng)我們?cè)黾痈綄俳M件時(shí),為了在創(chuàng)建和縮放時(shí)定位這些附屬組件,我們需要實(shí)現(xiàn)自定義的LayoutManager。這是一個(gè)非常簡(jiǎn)單的工作(甚至有點(diǎn)乏味):這些range在右側(cè)縱向并排排列,相鄰的range則根據(jù)其自身的weight值擁有相應(yīng)空間的垂直區(qū)域,滑標(biāo)則占有全部垂直空間,***個(gè)和***一個(gè)選項(xiàng)(control points)作為slider的起點(diǎn)和終點(diǎn)。
注意這個(gè)特殊的實(shí)現(xiàn)非常的困難并且?guī)缀醪豢赡苁褂脝我坏腢I delegate來(lái)完成,舉例來(lái)說(shuō),一些LAFs使用了native Api 來(lái)繪制各自的控制(像滑標(biāo)的滑道與滑塊)。一些第三方的LAF可能不遵循UIManager中的設(shè)置而是提供自己的顏色和自定義的Api
現(xiàn)在回到我們的實(shí)現(xiàn)(使用JSlider),我們現(xiàn)在面臨一個(gè)有趣的問(wèn)題:滑標(biāo)可以通過(guò)設(shè)置snapToTicks的值來(lái)決定是否自動(dòng)對(duì)齊到最近的滑塊刻度。這個(gè)行為控制通過(guò)在BasicSliderUI delegate中安裝mouse montion監(jiān)聽(tīng)器實(shí)現(xiàn)。我們要怎么做?其中一個(gè)選擇是移除這個(gè)監(jiān)聽(tīng)器并且安裝上我們所提供的實(shí)現(xiàn)。另一個(gè)方法是提供自定義的 BoundedRangeModel實(shí)現(xiàn),當(dāng)設(shè)置非關(guān)聯(lián)的range時(shí)修改它的值(value)。***種方法并非***-你無(wú)法信賴其他LAF下特殊的 SliderUI delegate實(shí)現(xiàn)邏輯,有的實(shí)現(xiàn)甚至不會(huì)去調(diào)用父類的方法。第二種方法稍微好一點(diǎn),不過(guò)我們選擇另一種方法實(shí)現(xiàn),原因稍后說(shuō)明。
我們的實(shí)現(xiàn)對(duì)這些附屬組件使用類似樹(shù)/表的單元格渲染模式(Cell renderer),slider只是用來(lái)渲染并且不獲取任何事件(參考CellRendererPane)。這能使我們從LAF繪圖和鼠標(biāo)自定義事件中獲益。在我們這個(gè)特殊的例子中,如果用戶在slider滑塊外點(diǎn)擊鼠標(biāo)。我們通過(guò)直接設(shè)置相應(yīng)的range的值 (value)來(lái)代替原本的向鼠標(biāo)點(diǎn)擊方向滾動(dòng)一個(gè)”塊”,這就是我們?yōu)槭裁床皇褂们懊嫣岬降牡诙N方法的原因:我們自定義的鼠標(biāo)監(jiān)聽(tīng)器轉(zhuǎn)換鼠標(biāo)點(diǎn)擊轉(zhuǎn)換相應(yīng)的range(相鄰的或非關(guān)聯(lián)的)并且設(shè)置他們的值,一旦這個(gè)唯一的監(jiān)聽(tīng)器被安裝到組件上(指這個(gè)CellRendererPane,而這個(gè) slider 只是一個(gè)”橡皮圖章”),我們可以保證沒(méi)有其他的監(jiān)聽(tīng)器阻礙我們的代碼。
倘若我們使用單元格渲染面板,我們需要覆蓋掉它的paint方法來(lái)繪制真正的滑塊。我們并沒(méi)有繪制那些選項(xiàng)標(biāo)簽因?yàn)樗沁@個(gè)組件“真正的”子組件,注意滑塊的繪制在一個(gè)單獨(dú)的方法中完成,這樣可以允許第三方的LAF只覆蓋的這個(gè)滑塊的繪制邏輯而不是改變整個(gè)繪制邏輯。
- @Override
- publicvoidpaint(Graphicsg,JComponentc){
- super.paint(g,c);
- this.paintSlider(g);
- }
- protectedvoidpaintSlider(Graphicsg){
- RectanglesliderBounds=sliderRendererPane.getBounds();
- this.sliderRendererPane.paintComponent(g,this.slider,
- this.flexiSlider,sliderBounds.x,sliderBounds.y,
- sliderBounds.width,sliderBounds.height,true);
- }
測(cè)試應(yīng)用程序
現(xiàn)在我們擁有了一個(gè)完整的自定義滑標(biāo)組件,是時(shí)候該測(cè)試它了。這個(gè)測(cè)試應(yīng)用程序創(chuàng)建了一個(gè)滑標(biāo),它包含一些相鄰的和非關(guān)聯(lián)的range,并為其注冊(cè)了改變監(jiān)聽(tīng)器(change listener)。一旦發(fā)生改變事件,我們計(jì)算圖標(biāo)的尺寸比例并繪制它(該圖標(biāo)使用Tango Desktop Project的SVG-to-Java2D converte來(lái)轉(zhuǎn)換,具體參考 Transcoding SVG to Pure Java2D code一文)。
顯示了在不同look and feels下的滑標(biāo),從左到右,這些 LAF為:Windows (core), Metal (core), Motif (core), Liquid (third party), 和 Napkin (third party).如你所見(jiàn),這個(gè)新組件提供了和當(dāng)前所設(shè)置的LAF一致的外觀。
現(xiàn)在要何去何從?閱讀Swing核心組件的代碼。下載并學(xué)習(xí)開(kāi)源組件的源代碼(像SwingX 或Flamingo),然后開(kāi)始構(gòu)造你自己夢(mèng)想中組件吧!
【編輯推薦】