sunfruit[請(qǐng)?jiān)L問(wèn)http://www.fruitres.cn]

          --我相信JAVA能走得更遠(yuǎn) QQ:316228067

          [轉(zhuǎn)發(fā)]AWT和Swing中的繪畫

          Painting in AWT and Swing
          Good Painting Code Is the Key to App Performance
          By Amy Fowler

          在圖形系統(tǒng)中, 窗口工具包(windowing toolkit)通常提供一個(gè)框架以便相對(duì)容易地創(chuàng)建一個(gè)圖形用戶接口(GUI),在正確的時(shí)間、正確的屏幕位置顯示一個(gè)正確的圖像位。

          AWT (abstract windowing toolkit,抽象窗口工具包) 和Swing都提供這種框架。但是實(shí)現(xiàn)這種框架的APIs對(duì)一些開(kāi)發(fā)人員來(lái)講不是很好理解 -- 這就導(dǎo)致一些程序的運(yùn)行達(dá)不到預(yù)期的效果。

          本文詳細(xì)地解釋AWT和Swing的繪畫機(jī)制,目的是幫助開(kāi)發(fā)人員寫出正確的和高率的GUI繪畫代碼。然而,這篇文章只包括一般的畫圖機(jī)制(即,在什么地方和什么時(shí)間去呈現(xiàn)),而不介紹Swing的圖形API怎樣去呈現(xiàn)圖形。想學(xué)習(xí)怎樣去顯示漂亮的圖形,請(qǐng)?jiān)L問(wèn)Java 2D 網(wǎng)站。

          繪畫系統(tǒng)的演變
          當(dāng)最初的、為JDK1.0使用的AWT API發(fā)布時(shí),只有重量級(jí)(heavyweight)部件("重量級(jí)" 的意思是說(shuō)該部件有它自己的、遮光(opaque)的、與生俱來(lái)的窗體)。這樣就使得AWT在很大程度上依賴于本地平臺(tái)的繪畫系統(tǒng)。這樣的安排需要開(kāi)發(fā)人員寫代碼的時(shí)候要考慮到很多細(xì)節(jié)問(wèn)題,象重畫檢測(cè)(damage detection)、剪切(clip)計(jì)算、以及Z軸次序等。隨著JDK 1.1中輕量級(jí)(lightweight)部件的引入("輕量級(jí)" 部件重用了與它最接近的重量級(jí)祖先的本地窗體),需要AWT能在共享的代碼里為輕量級(jí)部件實(shí)現(xiàn)繪畫處理。因此,重量級(jí)和輕量級(jí)部件在它們各自的繪畫處理方法有著微妙的差別。

          在JDK 1.1之后,當(dāng)發(fā)布了Swing工具的時(shí)候,引入了它自己的繪畫風(fēng)格。Swing的繪畫機(jī)制在很大程度上類似并且依賴于AWT,但是,也有它自己的觀點(diǎn),還帶來(lái)了新的API,使得應(yīng)用程序可以容易地定制繪畫工作。


          在AWT中繪畫
          去理解AWT繪畫API怎樣工作,有助于我們搞明白是什么觸發(fā)了窗口環(huán)境中的繪畫操作。AWT中有兩種繪畫操作:系統(tǒng)觸發(fā)的繪畫,和程序觸發(fā)的繪畫

          系統(tǒng)觸發(fā)的繪畫操作
          在系統(tǒng)觸發(fā)的繪畫操作中,系統(tǒng)需要一個(gè)部件顯示它的內(nèi)容,通常是由于下列中的原因:


          部件第一次在屏幕上顯示


          部件的大小改變了


          部件顯示的內(nèi)容受損需要維護(hù)。(比如,先前擋住部件的其它物體移走了,于是部件被擋住的部分曝露出來(lái)。
          程序觸發(fā)的繪畫操作

          在程序觸發(fā)的繪畫操作,是部件自己決定要更新自身的內(nèi)容,因?yàn)椴考?nèi)部的狀態(tài)改變了。(比如,監(jiān)測(cè)到鼠標(biāo)按鈕已經(jīng)按下,那么它就需要去畫出按鈕"被按下"時(shí)的樣子>

          畫圖的方法
          不管是誰(shuí)觸發(fā)了畫圖請(qǐng)求,AWT都是利用"回調(diào)"機(jī)制來(lái)實(shí)現(xiàn)繪畫,這個(gè)機(jī)制對(duì)于“重量級(jí)”和“輕量級(jí)”的部件都是相同的。這就意味著程序應(yīng)該在一個(gè)特定的可覆蓋的方法中放置那些表現(xiàn)部件自身的的代碼,并且在需要繪畫的時(shí)候,工具包就會(huì)調(diào)用這個(gè)方法。這個(gè)可覆蓋的方法在java.awt.Component中聲明:

          ??? public void paint(Graphics g)

          當(dāng)AWT調(diào)用這個(gè)方法時(shí),作為參數(shù)的、負(fù)責(zé)在這個(gè)特定的部件上繪畫的Graphics對(duì)象是在之前已經(jīng)配置了的,擁有恰當(dāng)?shù)臓顟B(tài)值。

          Graphics的顏色 值被設(shè)置為部件的前景。
          Graphics的字體 設(shè)置為部件的字體。
          Graphics的平移(translation) 也給設(shè)定,使用坐標(biāo)(0,0)定位部件的左上角。
          Graphics的裁剪框(clip rectangle)設(shè)置為部件需要畫圖的區(qū)域。
          程序必須使用這個(gè)Graphics(或者其派生類)對(duì)象來(lái)呈現(xiàn)繪畫,并且可以根據(jù)自己的需要任意改變Graphics對(duì)象的屬性值。

          這里是一個(gè)回調(diào)繪畫的簡(jiǎn)單例子,在部件的范圍內(nèi)呈現(xiàn)一個(gè)實(shí)體園:

          ??? public void paint(Graphics g) {
          ??????? // 根據(jù)部件的范圍,動(dòng)態(tài)計(jì)算圓的尺寸信息。
          ??????? Dimension size = getSize();
          ??????? // 直徑
          ??????? int d = Math.min(size.width, size.height);
          ??????? int x = (size.width - d)/2;
          ??????? int y = (size.height - d)/2;

          ??????? // 畫圓(顏色已經(jīng)預(yù)先設(shè)置為部件的前景顏色)
          ??????? g.fillOval(x, y, d, d);
          ??????? g.setColor(Color.black);
          ??????? g.drawOval(x, y, d, d);
          ??? }

          初次接觸AWT的開(kāi)發(fā)人員可以看看PaintDemo example,那里介紹了一個(gè)在AWT程序中怎樣使用畫圖回調(diào)方法的例子。

          一般情況下,程序應(yīng)該避免把繪畫代碼放置在回調(diào)方法paint()的范圍之外。為什么呢?因?yàn)閜aint方法之外的繪畫代碼可能會(huì)在不適合畫圖的時(shí)候被調(diào)用 -- 例如,在部件變?yōu)榭梢?jiàn)之前或者已經(jīng)在使用一個(gè)有效的Graphics。同時(shí),不推薦在程序中直接調(diào)用paint()。

          為了使能夠由程序觸發(fā)繪畫操作,AWT提供了下面的java.awt.Component的方法,這樣程序就可以提出一個(gè)異步的繪畫請(qǐng)求:

          ??? public void repaint()
          ??? public void repaint(long tm)
          ??? public void repaint(int x, int y, int width, int height)
          ??? public void repaint(long tm, int x, int y,
          ?????????????????? int width, int height)

          下面的代碼顯示了一個(gè)簡(jiǎn)單的鼠標(biāo)監(jiān)聽(tīng)器的例子,當(dāng)鼠標(biāo)按下和抬起的時(shí)候,使用repaint()來(lái)觸發(fā)“假想按鈕”的更新操作。


          MouseListener l = new MouseAdapter() {
          ??????????? public void mousePressed(MouseEvent e) {
          ??????????????? MyButton b = (MyButton)e.getSource();
          ??????????????? b.setSelected(true);
          ??????????????? b.repaint();????????????
          ??????????? }

          ??????????? public void mouseReleased(MouseEvent e) {
          ??????????????? MyButton b = (MyButton)e.getSource();
          ??????????????? b.setSelected(false);
          ??????????????? b.repaint();????????????
          ??????????? }
          ??????? };

          如果部件要呈現(xiàn)復(fù)雜的圖形,就應(yīng)該使用帶參數(shù)的repaint()方法,通過(guò)參數(shù)來(lái)指定需要更新的區(qū)域。一個(gè)比較常見(jiàn)的錯(cuò)誤是總是調(diào)用無(wú)參數(shù)的repaint()來(lái)提出重畫請(qǐng)求,這個(gè)方法會(huì)重畫整個(gè)部件,經(jīng)常導(dǎo)致一些不必要的畫圖處理。

          paint() vs. update()
          為什么我們要區(qū)分繪畫操作是"系統(tǒng)觸發(fā)" 還是"程序觸發(fā)"呢?因?yàn)樵凇爸亓考?jí)”部件上,AWT對(duì)這兩種請(qǐng)求的在處理上稍有不同(“輕量級(jí)”的情況將在后面介紹),并且不幸的是與此相關(guān)的代碼非常復(fù)雜,難以更改。

          對(duì)于“重量級(jí)”部件,這兩種方式的繪畫產(chǎn)生于兩條不同的途徑,取決于是“系統(tǒng)觸發(fā)”還是“程序觸發(fā)”。

          系統(tǒng)觸發(fā)的繪畫
          下面介紹“系統(tǒng)觸發(fā)”的繪畫操作是怎么產(chǎn)生的:

          AWT確定是一部分還是整個(gè)部件需要繪畫。


          AWT促使事件分派線程調(diào)用部件的paint()方法。

          程序觸發(fā)的繪畫
          由程序觸發(fā)的繪畫的產(chǎn)生如下所示:

          程序確定是一部分還是全部部件需要重畫以對(duì)應(yīng)內(nèi)部狀態(tài)的改變。
          ?

          程序調(diào)用部件的repaint(),該方法向AWT登記了一個(gè)異步的請(qǐng)求 -- 當(dāng)前部件需要重畫。
          ?

          AWT促使事件分派線程去調(diào)用部件的update() 方法。
          注意: 在最初的重畫請(qǐng)求處理完成之前,如果在該部件上有多次對(duì)repaint()的調(diào)用,那么這些調(diào)用可以被合并成對(duì)update()的一次調(diào)用。決定什么時(shí)候應(yīng)該合并多次請(qǐng)求的運(yùn)算法則取決于具體的實(shí)現(xiàn)。如果多次請(qǐng)求被合并,最終被更新的區(qū)域?qū)⑹撬羞@些請(qǐng)求所要求更新的區(qū)域的聯(lián)合(union)。

          ?

          如果部件沒(méi)有覆蓋(override)update()方法,update()的默認(rèn)實(shí)現(xiàn)會(huì)清除部件背景(如果部件不是“輕量級(jí)”),然后只是簡(jiǎn)單地調(diào)用paint()方法。
          因?yàn)樽鳛槟J(rèn)的最終結(jié)果都是一樣的(paint()方法被調(diào)用),很多開(kāi)發(fā)人員完全不知道一個(gè)分離的update() 方法的意義。確實(shí),默認(rèn)的update()的實(shí)現(xiàn)最終會(huì)轉(zhuǎn)回到對(duì)paint()方法的調(diào)用,然而,如果需要,這個(gè)更新操作的 "鉤子(hook)"可以使程根據(jù)不同的情況來(lái)處理程序觸發(fā)的繪畫。程序必須這么設(shè)想,對(duì)paint()的調(diào)用意味著Graphics的裁剪區(qū)"損壞"了并且必須全部重畫;然而對(duì)update()的調(diào)用沒(méi)有這種含義,它使程序做增量的繪畫。

          如果程序希望只把要增加的內(nèi)容敷蓋于已存在于該部件的像素位之上,那么就使用增量畫圖操作。UpdateDemo example 示范了一個(gè)利用update()的優(yōu)點(diǎn)做增量繪畫的程序。

          事實(shí)上,大多數(shù)GUI部件不需要增量繪畫,所有大部分程序可以忽略u(píng)pdate()方法,并且簡(jiǎn)單地覆蓋(override)paint()來(lái)呈現(xiàn)部件的當(dāng)前狀態(tài)。這就意味著不管“系統(tǒng)觸發(fā)”還是“程序觸發(fā)”,在大多數(shù)部件上的表現(xiàn)從其本質(zhì)上講是是等價(jià)的。

          繪畫與輕量級(jí)部件
          從應(yīng)用開(kāi)發(fā)人員的觀點(diǎn)看,“輕量級(jí)”的繪畫API基本上和“重量級(jí)”一樣(即,你只需要覆蓋paint()方法,同樣,調(diào)用repaint()方法去觸發(fā)繪圖更新)。然而,因?yàn)锳WT的“輕量級(jí)”部件的框架全部使用普通Java代碼實(shí)現(xiàn),在輕量級(jí)部件上繪畫機(jī)制的實(shí)現(xiàn)方式有一些微妙的不同。

          ?

          “輕量級(jí)”部件是怎樣被繪制的
          “輕量級(jí)”部件需要一個(gè)處在容器體系上的“重量級(jí)”部件提供進(jìn)行繪畫的場(chǎng)所。當(dāng)這個(gè)“重量級(jí)”的“祖宗”被告知要繪制自身的窗體時(shí),它必須把這個(gè)繪畫的請(qǐng)求轉(zhuǎn)化為對(duì)其所有子孫的繪畫請(qǐng)求。這是由java.awt.Container的paint()方法處理的,該方法調(diào)用包容于其內(nèi)的所有可見(jiàn)的、并且與繪畫區(qū)相交的輕量級(jí)部件的paint()方法。因此對(duì)于所有覆蓋了paint()方法的Container子類(“輕量級(jí)”或“重量級(jí)”)需要立刻做下面的事情:


          ?? public class MyContainer extends Container {
          ??????? public void paint(Graphics g) {
          ??? // paint my contents first...
          ??? // then, make sure lightweight children paint
          ??? super.paint(g);
          ??????? }
          ??? }

          如果沒(méi)有super.paint(),那么容器(container)的輕量級(jí)子孫類就不會(huì)顯示出來(lái)(這是一個(gè)非常普遍的問(wèn)題,自從JDK1.1初次引進(jìn)“輕量級(jí)”部件之后)。

          這種情況相當(dāng)于注釋掉了默認(rèn)的Container.update()方法的執(zhí)行,從而不能 使用遞歸去調(diào)用其輕量級(jí)子孫類的update()或者paint()方法。這就意味著任何使用update()方法實(shí)現(xiàn)增量繪畫的重量級(jí)Container子類必須確保其輕量級(jí)子孫在需要時(shí),能夠被它的遞歸操作所調(diào)用從而實(shí)現(xiàn)重畫。幸運(yùn)的是,只有少數(shù)幾個(gè)重量級(jí)的容器(Container)需要增量繪圖,所以這個(gè)問(wèn)題沒(méi)有影響到大多數(shù)的程序。

          ?

          輕量級(jí)與系統(tǒng)觸發(fā)型的畫圖
          為輕量級(jí)部件實(shí)現(xiàn)窗體行為(顯示、隱藏、移動(dòng)、改變大小等)的輕量級(jí)框架的代碼全部用Java代碼寫成。經(jīng)常的,在這些功能的Java實(shí)現(xiàn)中,AWT必須明確地吩咐各個(gè)輕量級(jí)部件執(zhí)行繪畫(實(shí)質(zhì)上講這也是系統(tǒng)觸發(fā)的繪畫,盡管它不是源于本地的 操作系統(tǒng))。而輕量級(jí)框架使用repaint()方法來(lái)吩咐部件執(zhí)行繪畫,這是我們前面解釋過(guò)的,將導(dǎo)致一個(gè)update()的調(diào)用而不是直接地對(duì)paint()的調(diào)用。因此,對(duì)于輕量級(jí),系統(tǒng)觸發(fā)型的畫圖操作可以遵循下面的兩種途徑:

          系統(tǒng)觸發(fā)的繪畫要求產(chǎn)生于本地系統(tǒng)(例如,輕量級(jí)的重量級(jí)祖先第一次現(xiàn)身的時(shí)候),這導(dǎo)致對(duì)paint()的直接調(diào)用。
          ?

          系統(tǒng)觸發(fā)型的繪圖要求產(chǎn)生于輕量框架(例如,輕量級(jí)部件的尺寸改變了),這導(dǎo)致對(duì)update()的調(diào)用,該方法進(jìn)而默認(rèn)地調(diào)用paint()。
          簡(jiǎn)單地講,這意味著輕量級(jí)部件在update()和paint()之間沒(méi)有實(shí)質(zhì)的差別,進(jìn)一步講這又意味著“增量的繪圖技術(shù)”不能用到輕量級(jí)部件上。

          輕量級(jí)部件與透明
          因?yàn)檩p量級(jí)部件"借用"了本屬于其“重量級(jí)”祖先的屏幕,所以它們支持“透明”的特征。這樣做是因?yàn)檩p量級(jí)部件是從底往上繪畫,因此如果輕量級(jí)部件遺留一些或者全部它們祖先的像素位而沒(méi)有畫,底層的部件就會(huì)"直接顯示。"出來(lái)。這也是對(duì)于輕量級(jí)部件,update()方法的在默認(rèn)實(shí)現(xiàn)將不再清除背景的原因。

          LightweightDemo 例程示范了輕量級(jí)部件的透明特征。

          "靈活巧妙地"繪畫方法
          當(dāng)AWT嘗試著使呈現(xiàn)部件的處理盡可能高效率時(shí),部件自身paint()的實(shí)現(xiàn)可能對(duì)整體性能有重大的影響。影響這個(gè)處理過(guò)程的兩個(gè)關(guān)鍵領(lǐng)域是:

          使用裁剪區(qū)來(lái)縮小需要呈現(xiàn)的范圍。


          應(yīng)用內(nèi)部的版面布局信息來(lái)縮小對(duì)子部件的籠罩范圍(僅適用于輕量級(jí)).。
          如果你的部件很簡(jiǎn)單 -- 比如,如果是一個(gè)按鈕 -- 那么就不值得花費(fèi)氣力去改善它的呈現(xiàn)屬性,使它僅僅去繪畫與修剪區(qū)相交的部分;不理會(huì)Graphics的裁剪區(qū)直接繪制整個(gè)部件反而更劃算。然而,如果你創(chuàng)建的部件界面很復(fù)雜,比如文本部件,那么迫切需要你的代碼使用裁剪信息來(lái)縮小需要繪圖的范圍。

          更進(jìn)一步講,如果你寫了一個(gè)容納了很多部件的復(fù)雜的輕量級(jí)容器,其中的部件和容器的布局管理器,或者只是容器的布局管理器擁有布局的信息,那么就值得使用所知道的布局信息來(lái)更靈活地確定哪個(gè)子部件需要繪畫。Container.paint()的默認(rèn)實(shí)現(xiàn)只是簡(jiǎn)單地按順序遍歷子部件,檢查它是否可見(jiàn)、是否與重?fù)Q區(qū)域相交 -- 對(duì)于某幾個(gè)布局管理這種操作就顯得不必要的羅嗦。比如,如果容器在100*100的格子里布置部件,那么格子的信息就可以用來(lái)更快得確定這10,000個(gè)部件中哪個(gè)與裁剪框相交,哪個(gè)就確實(shí)需要繪制。


          AWT繪畫準(zhǔn)則
          AWT為繪制部件提供了一個(gè)簡(jiǎn)單的回調(diào)API。當(dāng)你使用它是,要遵循下面的原則:

          對(duì)于大多數(shù)程序,所有的客戶區(qū)繪畫代碼應(yīng)該被放置在部件的paint()方法中。
          ?

          通過(guò)調(diào)用repaint()方法,程序可以觸發(fā)一個(gè)將來(lái)執(zhí)行的paint()調(diào)用,不能直接調(diào)用paint()方法。
          ?

          對(duì)于界面復(fù)雜的部件,應(yīng)該觸發(fā)帶參數(shù)的repaint()方法,使用參數(shù)定義實(shí)際需要更新的區(qū)域;而不帶參數(shù)調(diào)用會(huì)導(dǎo)致整個(gè)部件被重畫。
          ?

          因?yàn)閷?duì)repaint()的調(diào)用會(huì)首先導(dǎo)致update()的調(diào)用,默認(rèn)地會(huì)促成paint()的調(diào)用,所以重量級(jí)部件應(yīng)該覆蓋update()方法以實(shí)現(xiàn)增量繪制,如果需要的話(輕量級(jí)部件不支持增量繪制) 。
          ?

          覆蓋了paint()方法的java.awt.Container子類應(yīng)當(dāng)在paint()方法中調(diào)用super.paint()以保證子部件能被繪制。
          ?

          界面復(fù)雜的部件應(yīng)該靈活地使用裁剪區(qū)來(lái)把繪畫范圍縮小到只包括與裁剪區(qū)相交的范圍。

          在Swing中的繪畫

          Swing起步于AWT基本繪畫模式,并且作了進(jìn)一步的擴(kuò)展以獲得最大化的性能以及改善可擴(kuò)展性能。象AWT一樣,Swing支持回調(diào)繪畫以及使用repaint()促使部件更新。另外,Swing提供了內(nèi)置的雙緩沖(double-buffering)并且作了改變以支持Swing的其它結(jié)構(gòu)(象邊框(border)和UI代理)。最后,Swing為那些想更進(jìn)一步定制繪畫機(jī)制的程序提供了RepaintManager API。

          對(duì)雙緩沖的支持
          Swing的最引人注目的特性之一就是把對(duì)雙緩沖的支持整個(gè)兒的內(nèi)置到工具包。通過(guò)設(shè)置javax.swing.JComponent的"doubleBuffered"屬性就可以使用雙緩沖:

          ???? public boolean isDoubleBuffered()
          ??? public void setDoubleBuffered(boolean o)
          當(dāng)緩沖激活的時(shí)候,Swing的雙緩沖機(jī)制為每個(gè)包容層次(通常是每個(gè)最高層的窗體)準(zhǔn)備一個(gè)單獨(dú)的屏外緩沖。并且,盡管這個(gè)屬性可以基于部件而設(shè)置,對(duì)一個(gè)特定的容器上設(shè)置這個(gè)屬性,將會(huì)影響到這個(gè)容器下面的所有輕量級(jí)部件把自己的繪畫提交給屏外緩沖,而不管它們各自的"雙緩沖"屬性值

          默認(rèn)地,所有Swing部件的該屬性值為true。不過(guò)對(duì)于JRootPane這種設(shè)置確實(shí)有些問(wèn)題,因?yàn)檫@樣就使所有位于這個(gè)上層Swing部件下面的所有部件都使用了雙緩沖。對(duì)于大多數(shù)的Swing程序,不需要作任何特別的事情就可以使用雙緩沖,除非你要決定這個(gè)屬性是開(kāi)還是關(guān)(并且為了使GUI能夠平滑呈現(xiàn),你需要打開(kāi)這個(gè)屬性)。Swing保證會(huì)有適宜的Graphics對(duì)象(或者是為雙緩沖使用的屏外映像的Graphics,或者是正規(guī)的Graphics)傳遞給部件的繪畫回調(diào)函數(shù),所以,部件需要做的所有事情僅僅就是使用這個(gè)Graphics畫圖。本文的后面,在繪制的處理過(guò)程這一章會(huì)詳細(xì)解釋這個(gè)機(jī)制。

          其他的繪畫屬性
          為了改善內(nèi)部的繪畫算法性能,Swing另外引進(jìn)了幾個(gè)JComponent的相互有關(guān)聯(lián)的屬性。引入這些屬性為的是處理下面兩個(gè)問(wèn)題,這兩個(gè)問(wèn)題有可能導(dǎo)致輕量級(jí)部件的繪畫成本過(guò)高:

          透明(Transparency): 當(dāng)一個(gè)輕量級(jí)部件的繪畫結(jié)束時(shí),如果該部件的一部分或者全部透明,那么它就可能不會(huì)把所有與其相關(guān)的像素位都涂上顏色;這就意味著不管它什么時(shí)候重畫,它底層的部件必須首先重畫。這個(gè)技術(shù)需要系統(tǒng)沿著部件的包容層次去找到最底層的重量級(jí)祖先,然后從它開(kāi)始、從后向前地執(zhí)行繪畫。
          重疊的部件(Overlapping components): 當(dāng)一個(gè)輕量級(jí)部件的繪畫結(jié)束是,如果有一些其他的輕量級(jí)部件部分地疊加在它的上方;就是說(shuō),不管最初的輕量級(jí)部件什么時(shí)候畫完,只要有疊加在它上面的其它部件(裁剪區(qū)與疊加區(qū)相交),這些疊加的部件必須也要部分地重畫。這需要系統(tǒng)在每次繪畫時(shí)要遍歷大量的包容層次,以檢查與之重疊的部件。
          遮光性
          ?

          在一般情況下部件是不透明的,為了提高改善性能,Swing增加了讀寫javax.swing.JComponent的遮光(opaque)屬性的操作:

          ??? public boolean isOpaque()
          ??? public void setOpaque(boolean o)

          這些設(shè)置是:

          true:部件同意在它的矩形范圍包含的里所有像素位上繪畫。
          false:部件不保證其矩形范圍內(nèi)所有像素位上繪畫。
          遮光(opaque)屬性允許Swing的繪圖系統(tǒng)去檢測(cè)是否一個(gè)對(duì)指定部件的重畫請(qǐng)求會(huì)導(dǎo)致額外的對(duì)其底層祖先的重畫。每個(gè)標(biāo)準(zhǔn)Swing部件的默認(rèn)(遮光)opaque屬性值由當(dāng)前的視-感UI對(duì)象設(shè)定。而對(duì)于大多數(shù)部件,該值為true。

          部件實(shí)現(xiàn)中的一個(gè)最常見(jiàn)的錯(cuò)誤是它們?cè)试S遮光(opaque)屬性保持其默認(rèn)值true,卻又不完全地呈現(xiàn)它們所轄的區(qū)域,其結(jié)果就是沒(méi)有呈現(xiàn)的部分有時(shí)會(huì)造成屏幕垃圾。當(dāng)一個(gè)部件設(shè)計(jì)完畢,應(yīng)該仔細(xì)的考慮所控制的遮光(opaque)屬性,既要確保透的使用是明智的,因?yàn)樗鼤?huì)花費(fèi)更多的繪畫時(shí)間,又要確保與繪畫系統(tǒng)之間的協(xié)約履行。

          遮光(opaque)屬性的意義經(jīng)常被誤解。有時(shí)候被用來(lái)表示“使部件的背景透明”。然而這不是Swing對(duì)遮光的精確解釋。一些部件,比如按鈕,為了給部件一個(gè)非矩形的外形可能會(huì)把“遮光”設(shè)置為false,或者為了短時(shí)間的視覺(jué)效果使用一個(gè)矩形框圍住部件,例如焦點(diǎn)指示框。在這些情況下,部件不遮光,但是其背景的主要部分仍然需要填充。

          如先前的定義,遮光屬性的本質(zhì)是一個(gè)與負(fù)責(zé)重畫的系統(tǒng)之間訂立的契約。如果一個(gè)部件使用遮光屬性去定義怎樣使部件的外觀透明,那么該屬性的這種使用就應(yīng)該備有證明文件。(一些部件可能更合適于定義額外的屬性控制外觀怎樣怎樣增加透明度。例如,javax.swing.AbstractButton提供ContentAreaFilled屬性就是為了達(dá)到這個(gè)目的。)

          另一個(gè)毫無(wú)價(jià)值的問(wèn)題是遮光屬性與Swing部件的邊框(border)屬性有多少聯(lián)系。在一個(gè)部件上,由Border對(duì)象呈現(xiàn)的區(qū)域從幾何意義上講仍是部件的一部分。就是說(shuō)如果部件遮光,它就有責(zé)任去填充邊框所占用的空間。(然后只需要把邊框放到該不透明的部件之上就可以了)。

          如果你想使一個(gè)部件允許其底層部件能透過(guò)它的邊框范圍而顯示出來(lái) -- 即,通過(guò)isBorderOpaque()判斷border是否支持透明而返回值為false -- 那么部件必須定義自身的遮光屬性為false并且確保它不在邊框的范圍內(nèi)繪圖。

          "最佳的"繪畫方案
          部件重疊的問(wèn)題有些棘手。即使沒(méi)有直接的兄弟部件疊加在該部件之上,也總是可能有非直系繼承關(guān)系(比如"堂兄妹"或者"姑嬸")的部件會(huì)與它交疊。這樣的情況下,處于一個(gè)復(fù)雜層次中的每個(gè)部件的重畫工作都需要一大堆的樹(shù)遍歷來(lái)確保'正確地'繪畫。為了減少不必要的遍歷,Swing為javax.swing.JComponent增加一個(gè)只讀的isOptimizedDrawingEnabled屬性:

          ??? public boolean isOptimizedDrawingEnabled()
          這些設(shè)置是:


          true:部件指示沒(méi)有直接的子孫與其重疊。

          false: 部件不保證有沒(méi)有直接的子孫與之交疊。
          通過(guò)檢查isOptimizedDrawingEnabled屬性,Swing在重畫時(shí)可以快速減少對(duì)交疊部件的搜索。

          因?yàn)閕sOptimizedDrawingEnabled屬性是只讀的,于是部件改變默認(rèn)值的唯一方法是在其子類覆蓋(override)這個(gè)方法來(lái)返回所期望的值。除了JLayeredPane,JDesktopPane,和JViewPort外,所有標(biāo)準(zhǔn)Swing部件對(duì)這個(gè)屬性返回true。

          繪畫方法
          適應(yīng)于AWT的輕量級(jí)部件的規(guī)則同樣也適用于Swing部件 -- 舉一個(gè)例子,在部件需要呈現(xiàn)的時(shí)候就會(huì)調(diào)用paint() -- 只是Swing更進(jìn)一步地把paint()的調(diào)用分解為3個(gè)分立的方法,以下列順序依次執(zhí)行:

          ???? protected void paintComponent(Graphics g)
          ??? protected void paintBorder(Graphics g)
          ??? protected void paintChildren(Graphics g)
          Swing程序應(yīng)該覆蓋paintComponent()而不是覆蓋paint()。雖然API允許這樣做,但通常沒(méi)有理由去覆蓋paintBorder()或者paintComponents()(如果你這么做了,請(qǐng)確認(rèn)你知道你到底在做什么!)。這個(gè)分解使得編程變得更容易,程序可以只覆蓋它們需要擴(kuò)展的一部分繪畫。例如,這樣就解決先前在AWT中提到的問(wèn)題,因?yàn)檎{(diào)用super.paint()失敗而使得所有輕量級(jí)子孫都不能顯示。

          SwingPaintDemo例子程序舉例說(shuō)明了Swing的paintComponent()回調(diào)方法的簡(jiǎn)單應(yīng)用。

          繪畫與UI代理
          大多數(shù)標(biāo)準(zhǔn)Swing部件擁有它們自己的、由分離的觀-感(look-and-feel)對(duì)象(叫做"UI代理")實(shí)現(xiàn)的觀-感。這意味著標(biāo)準(zhǔn)部件把大多數(shù)或者所有的繪畫委派給UI代理,并且出現(xiàn)在下面的途徑:

          paint()觸發(fā)paintComponent()方法。
          如果ui屬性為non-null,paintComponent()觸發(fā)ui.update()。
          如果部件的遮光屬性為true,ui.udpate()方法使用背景顏色填充部件的背景并且觸發(fā)ui.paint()。
          ui.paint()呈現(xiàn)部件的內(nèi)容。
          這意味著Swing部件的擁有UI代理的子類(相對(duì)于JComponent的直系子類),應(yīng)該在它們所覆蓋的paintComponent方法中觸發(fā)super.paintComponent()。


          ??? public class MyPanel extends JPanel {
          ??????? protected void paintComponent(Graphics g) {
          ??? // Let UI delegate paint first
          ??? // (including background filling, if I'm opaque)
          ??? super.paintComponent(g);
          ??? // paint my contents next....
          ??????? }
          ??? }

          如果因?yàn)槟承┰虿考臄U(kuò)展類不允許UI代理去執(zhí)行繪畫(是如果,例如,完全更換了部件的外觀),它可以忽略對(duì)super.paintComponent()的調(diào)用,但是它必須負(fù)責(zé)填充自己的背景,如果遮光(opaque)屬性為true的話,如前面在遮光(opaque)屬性一章講述的。

          繪畫的處理過(guò)程
          Swing處理"repaint"請(qǐng)求的方式與AWT有稍微地不同,雖然對(duì)于應(yīng)用開(kāi)發(fā)人員來(lái)講其本質(zhì)是相同的 -- 同樣是觸發(fā)paint()。Swing這么做是為了支持它的RepaintManager API (后面介紹),就象改善繪畫性能一樣。在Swing里的繪畫可以走兩條路,如下所述:

          (A) 繪畫需求產(chǎn)生于第一個(gè)重量級(jí)祖先(通常是JFrame、JDialog、JWindow或者JApplet):

          事件分派線程調(diào)用其祖先的paint()
          ?

          Container.paint()的默認(rèn)實(shí)現(xiàn)會(huì)遞歸地調(diào)用任何輕量級(jí)子孫的paint()方法。
          ?

          當(dāng)?shù)竭_(dá)第一個(gè)Swing部件時(shí),JComponent.paint()的默認(rèn)執(zhí)行做下面的步驟:
          如果部件的雙緩沖屬性為true并且部件的RepaintManager上的雙緩沖已經(jīng)激活,將把Graphics對(duì)象轉(zhuǎn)換為一個(gè)合適的屏外Graphics。
          調(diào)用paintComponent()(如果使用雙緩沖就把屏外Graphics傳遞進(jìn)去)。
          調(diào)用paintChildren()(如果使用雙緩沖就把屏外Graphics傳遞進(jìn)去),該方法使用裁剪并且遮光和optimizedDrawingEnabled等屬性來(lái)嚴(yán)密地判定要遞歸地調(diào)用哪些子孫的paint()。
          如果部件的雙緩沖屬性為true并且在部件的RepaintManager上的雙緩沖已經(jīng)激活,使用最初的屏幕Graphics對(duì)象把屏外映像拷貝到部件上。


          注意:JComponent.paint()步驟#1和#5在對(duì)paint()的遞歸調(diào)用中被忽略了(由于paintChildren(),在步驟#4中介紹了),因?yàn)樗性趕wing窗體層次中的輕量級(jí)部件將共享同一個(gè)用于雙緩沖的屏外映像。

          (B) 繪畫需求從一個(gè)javax.swing.JComponent擴(kuò)展類的repaint()調(diào)用上產(chǎn)生:

          JComponent.repaint()注冊(cè)一個(gè)針對(duì)部件的RepaintManager的異步的重畫需求,該操作使用invokeLater()把一個(gè)Runnable加入事件隊(duì)列以便稍后執(zhí)行在事件分派線程上的需求。
          ?

          該Runnable在事件分派線程上執(zhí)行并且導(dǎo)致部件的RepaintManager調(diào)用該部件上paintImmediately(),該方法執(zhí)行下列步驟:


          使用裁剪框以及遮光和optimizedDrawingEnabled屬性確定“根”部件,繪畫一定從這個(gè)部件開(kāi)始(處理透明以及潛在的重疊部件)。
          如果根部件的雙緩沖屬性為true,并且根部件的RepaintManager上的雙緩沖已激活,將轉(zhuǎn)換Graphics對(duì)象到適當(dāng)?shù)钠镣釭raphics。
          調(diào)用根部件(該部件執(zhí)行上述(A)中的JComponent.paint()步驟#2-4)上的paint(),導(dǎo)致根部件之下的、與裁剪框相交的所有部件被繪制。
          如果根部件的doubleBuffered屬性為true并且根部件的RepaintManager上的雙緩沖已經(jīng)激活,使用原始的Graphics把屏外映像拷貝到部件。


          注意:如果在重畫沒(méi)有完成之前,又有發(fā)生多起對(duì)部件或者任何一個(gè)其祖先的repaint()調(diào)用,所有這些調(diào)用會(huì)被折疊到一個(gè)單一的調(diào)用 回到paintImmediately() on topmostSwing部件 on which 其repaint()被調(diào)用。例如,如果一個(gè)JTabbedPane包含了一個(gè)JTable并且在其包容層次中的現(xiàn)有的重畫需求完成之前兩次發(fā)布對(duì)repaint()的調(diào)用,其結(jié)果將變成對(duì)該JTabbedPane部件的paintImmediately()方法的單一調(diào)用,會(huì)觸發(fā)兩個(gè)部件的paint()的執(zhí)行。

          這意味著對(duì)于Swing部件來(lái)說(shuō),update()不再被調(diào)用。

          雖然repaint()方法導(dǎo)致了對(duì)paintImmediately()的調(diào)用,它不考慮"回調(diào)"繪圖,并且客戶端的繪畫代碼也不會(huì)放置到paintImmediately()方法里面。實(shí)際上,除非有特殊的原因,根本不需要超載paintImmediately()方法。

          同步繪圖
          象我們?cè)谇懊嬲鹿?jié)所講述的,paintImmediately()表現(xiàn)為一個(gè)入口,用來(lái)通知Swing部件繪制自身,確認(rèn)所有需要的繪畫都能適當(dāng)?shù)禺a(chǎn)生。這個(gè)方法也可能用來(lái)安排同步的繪圖需求,就象它的名字所暗示的,即一些部件有時(shí)候需要保證它們的外觀實(shí)時(shí)地與其內(nèi)部狀態(tài)保持一致(例如,在JScrollPane執(zhí)行滾定操作的時(shí)候確實(shí)需要這樣并且也做到了)。

          程序不應(yīng)該直接調(diào)用這個(gè)方法,除非有合理實(shí)時(shí)繪畫需要。這是因?yàn)楫惒降膔epaint()可以使多個(gè)重復(fù)的需求得到有效的精簡(jiǎn),反之直接調(diào)用paintImmediately()則做不到這點(diǎn)。另外,調(diào)用這個(gè)方法的規(guī)則是它必須由事件分派線程中的進(jìn)程調(diào)用;它也不是為能以多線程運(yùn)行你的繪畫代碼而設(shè)計(jì)的。關(guān)于Swing單線程模式的更多信息,參考一起歸檔的文章"Threads and Swing."

          RepaintManager
          Swing的RepaintManager類的目的是最大化地提高Swing包容層次上的重畫執(zhí)行效率,同時(shí)也實(shí)現(xiàn)了Swing的'重新生效'機(jī)制(作為一個(gè)題目,將在其它文章里介紹)。它通過(guò)截取所有Swing部件的重畫需求(于是它們不再需要經(jīng)由AWT處理)實(shí)現(xiàn)了重畫機(jī)制,并且在需要更新的情況下維護(hù)其自身的狀態(tài)(我們已經(jīng)知道的"dirty regions")。最后,它使用invokeLater()去處理事件分派線程中的未決需求,如同在"Repaint Processing"一節(jié)中描述的那樣(B選項(xiàng)).

          對(duì)于大多數(shù)程序來(lái)講,RepaintManager可以看做是Swing的內(nèi)部系統(tǒng)的一部分,并且甚至可以被忽略。然而,它的API為程序能更出色地控制繪畫中的幾個(gè)要素提供了選擇。

          ?

          "當(dāng)前的"RepaintManager
          RepaintManager設(shè)計(jì) is designed to be dynamically plugged, 雖然 有一個(gè)單獨(dú)的接口。下面的靜態(tài)方法允許程序得到并且設(shè)置"當(dāng)前的"RepaintManager:

          ???? public static RepaintManager currentManager(Component c)
          ??? public static RepaintManager currentManager(JComponent c)
          ??? public static void
          ???????? setCurrentManager(RepaintManager aRepaintManager)
          更換"當(dāng)前的"RepaintManager
          總的說(shuō)來(lái),程序通過(guò)下面的步驟可能會(huì)擴(kuò)展并且更換RepaintManager:

          ??? RepaintManager.setCurrentManager(new MyRepaintManager());
          你也可以參考RepaintManagerDemo ,這是個(gè)簡(jiǎn)單的舉例說(shuō)明RepaintManager加載的例子,該例子將把有關(guān)正在執(zhí)行重畫的部件的信息打印出來(lái)。

          擴(kuò)展和替換RepaintManager的一個(gè)更有趣的動(dòng)機(jī)是可以改變對(duì)重畫的處理方式。當(dāng)前,默認(rèn)的重畫實(shí)現(xiàn)所使用的來(lái)跟蹤dirty regions的內(nèi)部狀態(tài)值是包內(nèi)私有的并且因此不能被繼承類訪問(wèn)。然而,程序可以實(shí)現(xiàn)它們自己的跟蹤dirty regions的機(jī)制并且通過(guò)超載下面的方法對(duì)重畫需求的縮減:

          ???? public synchronized void
          ????? addDirtyRegion(JComponent c, int x, int y, int w, int h)
          ??? public Rectangle getDirtyRegion(JComponent aComponent)
          ??? public void markCompletelyDirty(JComponent aComponent)
          ??? public void markCompletelyClean(JComponent aComponent) {
          addDirtyRegion()方法是在調(diào)用Swing部件的repaint()的之后被調(diào)用的,因此可以用作鉤子來(lái)捕獲所有的重畫需求。如果程序超載了這個(gè)方法(并且不調(diào)用super.addDirtyRegion()),那么它改變了它的職責(zé),而使用invokeLater()把Runnable放置到EventQueue ,該隊(duì)列將在合適的部件上調(diào)用paintImmediately()(translation: not for the faint of heart).

          從全局控制雙緩沖
          RepaintManager提供了從全局中激活或者禁止雙緩沖的API:

          ???? public void setDoubleBufferingEnabled(boolean aFlag)
          ??? public boolean isDoubleBufferingEnabled()
          這個(gè)屬性在繪畫處理的時(shí)候,在JComponent的內(nèi)部檢查過(guò)以確定是否使用屏外緩沖顯示部件。這個(gè)屬性默認(rèn)為true,但是如果程序希望在全局范圍為所有Swing部件關(guān)閉雙緩沖的使用,可以按照下面的步驟做:

          ??? RepaintManager.currentManager(mycomponent).
          ????????????????? setDoubleBufferingEnabled(false);
          注意:因?yàn)镾wing的默認(rèn)實(shí)現(xiàn)要初始化一個(gè)單獨(dú)的RepaintManager實(shí)例,mycomponent參數(shù)與此不相關(guān)。


          Swing繪畫準(zhǔn)則
          Swing開(kāi)發(fā)人員在寫繪畫代碼時(shí)應(yīng)該理解下面的準(zhǔn)則:

          對(duì)于Swing部件,不管是系統(tǒng)-觸發(fā)還是程序-觸發(fā)的請(qǐng)求,總會(huì)調(diào)用paint()方法;而update()不再被Swing部件調(diào)用。
          ?

          程序可以通過(guò)repaint()觸發(fā)一個(gè)異步的paint()調(diào)用,但是不能直接調(diào)用paint()。
          ?

          對(duì)于復(fù)雜的界面,應(yīng)該調(diào)用帶參數(shù)的repaint(),這樣可以僅僅更新由該參數(shù)定義的區(qū)域;而不要調(diào)用無(wú)參數(shù)的repaint(),導(dǎo)致整個(gè)部件重畫。
          ?

          Swing中實(shí)現(xiàn)paint()的3個(gè)要素是調(diào)用3個(gè)分離的回調(diào)方法:
          paintComponent()
          paintBorder()
          paintChildren()
          Swing部件的子類,如果想執(zhí)行自己的繪畫代碼,應(yīng)該把自己的繪畫代碼放在paintComponent()方法的范圍之內(nèi)。(不要放在paint()里面)。
          ?

          Swing引進(jìn)了兩個(gè)屬性來(lái)最大化的改善繪畫的性能:
          opaque: 部件是否要重畫它所占據(jù)范圍中的所有像素位?
          optimizedDrawingEnabled: 是否有這個(gè)部件的子孫與之交疊?
          ?

          如故Swing部件的(遮光)opaque屬性設(shè)置為true,那就表示它要負(fù)責(zé)繪制它所占據(jù)的范圍內(nèi)的所有像素位(包括在paintComponent()中清除它自己的背景),否則會(huì)造成屏幕垃圾。
          把一個(gè)部件設(shè)置為遮光(opaque)同時(shí)又把它的optimizedDrawingEnabled屬性設(shè)置為false,將導(dǎo)致在每個(gè)繪畫操作中要執(zhí)行更多的處理,因此我們推薦的明智的方法是同時(shí)使用透明并且交疊部件。

          使用UI代理(包括JPanel)的Swing部件的擴(kuò)展類的典型作法是在它們自己的paintComponent()的實(shí)現(xiàn)中調(diào)用super.paintComponent()。因?yàn)閁I代理可以負(fù)責(zé)清除一個(gè)遮光部件的背景,這將照顧到#5.

          Swing通過(guò)JComponent的doubleBuffered屬性支持內(nèi)置的雙緩沖,所有的Swing部件該屬性默認(rèn)值是true,然而把Swing容器的遮光設(shè)置為true有一個(gè)整體的構(gòu)思,把該容器上的所有輕量級(jí)子孫的屬性打開(kāi),不管它們各自的設(shè)定。

          強(qiáng)烈建議為所有的Swing部件使用雙緩沖。

          界面復(fù)雜的部件應(yīng)該靈活地運(yùn)用剪切框來(lái),只對(duì)那些與剪切框相交的區(qū)域進(jìn)行繪畫操作,從而減少工作量。

          總結(jié)
          不管AWT還是Swing都提供了方便的編程手段使得部件內(nèi)容能夠正確地顯示到屏幕上。雖然對(duì)于大多數(shù)的GUI需要我們推薦使用Swing,但是理解AWT的繪畫機(jī)制也會(huì)給我們帶來(lái)幫助,因?yàn)镾wing建立在它的基礎(chǔ)上。

          關(guān)于AWT和Sing的特點(diǎn)就介紹到這里,應(yīng)用開(kāi)發(fā)人員應(yīng)該盡力按照本文中介紹的準(zhǔn)則來(lái)撰寫代碼,充分發(fā)揮這些API功能,使自己的程序獲得最佳性能。

           
          ?

          posted on 2006-08-01 09:37 sunfruit 閱讀(454) 評(píng)論(1)  編輯  收藏

          評(píng)論

          # re: [轉(zhuǎn)發(fā)]AWT和Swing中的繪畫 2009-08-10 11:12 無(wú)

          真強(qiáng)大啊,不頂不行  回復(fù)  更多評(píng)論   


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 濮阳县| 锦屏县| 恭城| 峨山| 南郑县| 万安县| 来宾市| 鹿泉市| 吉首市| 高邑县| 千阳县| 垫江县| 英超| 云安县| 光泽县| 佛冈县| 武平县| 寿阳县| 从江县| 淮北市| 临汾市| 广汉市| 郴州市| 永吉县| 铜梁县| 长沙市| 务川| 揭西县| 连南| 沙洋县| 新丰县| 淮北市| 临清市| 乐昌市| 平度市| 淳化县| 成都市| 宜川县| 新化县| 隆回县| 平度市|