<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    sunfruit[請訪問http://www.fruitres.cn]

    --我相信JAVA能走得更遠 QQ:316228067

    [轉發]AWT和Swing中的繪畫

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

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

    AWT (abstract windowing toolkit,抽象窗口工具包) 和Swing都提供這種框架。但是實現這種框架的APIs對一些開發人員來講不是很好理解 -- 這就導致一些程序的運行達不到預期的效果。

    本文詳細地解釋AWT和Swing的繪畫機制,目的是幫助開發人員寫出正確的和高率的GUI繪畫代碼。然而,這篇文章只包括一般的畫圖機制(即,在什么地方和什么時間去呈現),而不介紹Swing的圖形API怎樣去呈現圖形。想學習怎樣去顯示漂亮的圖形,請訪問Java 2D 網站。

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

    在JDK 1.1之后,當發布了Swing工具的時候,引入了它自己的繪畫風格。Swing的繪畫機制在很大程度上類似并且依賴于AWT,但是,也有它自己的觀點,還帶來了新的API,使得應用程序可以容易地定制繪畫工作。


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

    系統觸發的繪畫操作
    在系統觸發的繪畫操作中,系統需要一個部件顯示它的內容,通常是由于下列中的原因:


    部件第一次在屏幕上顯示


    部件的大小改變了


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

    在程序觸發的繪畫操作,是部件自己決定要更新自身的內容,因為部件內部的狀態改變了。(比如,監測到鼠標按鈕已經按下,那么它就需要去畫出按鈕"被按下"時的樣子>

    畫圖的方法
    不管是誰觸發了畫圖請求,AWT都是利用"回調"機制來實現繪畫,這個機制對于“重量級”和“輕量級”的部件都是相同的。這就意味著程序應該在一個特定的可覆蓋的方法中放置那些表現部件自身的的代碼,并且在需要繪畫的時候,工具包就會調用這個方法。這個可覆蓋的方法在java.awt.Component中聲明:

    ??? public void paint(Graphics g)

    當AWT調用這個方法時,作為參數的、負責在這個特定的部件上繪畫的Graphics對象是在之前已經配置了的,擁有恰當的狀態值。

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

    這里是一個回調繪畫的簡單例子,在部件的范圍內呈現一個實體園:

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

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

    初次接觸AWT的開發人員可以看看PaintDemo example,那里介紹了一個在AWT程序中怎樣使用畫圖回調方法的例子。

    一般情況下,程序應該避免把繪畫代碼放置在回調方法paint()的范圍之外。為什么呢?因為paint方法之外的繪畫代碼可能會在不適合畫圖的時候被調用 -- 例如,在部件變為可見之前或者已經在使用一個有效的Graphics。同時,不推薦在程序中直接調用paint()。

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

    ??? 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)

    下面的代碼顯示了一個簡單的鼠標監聽器的例子,當鼠標按下和抬起的時候,使用repaint()來觸發“假想按鈕”的更新操作。


    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();????????????
    ??????????? }
    ??????? };

    如果部件要呈現復雜的圖形,就應該使用帶參數的repaint()方法,通過參數來指定需要更新的區域。一個比較常見的錯誤是總是調用無參數的repaint()來提出重畫請求,這個方法會重畫整個部件,經常導致一些不必要的畫圖處理。

    paint() vs. update()
    為什么我們要區分繪畫操作是"系統觸發" 還是"程序觸發"呢?因為在“重量級”部件上,AWT對這兩種請求的在處理上稍有不同(“輕量級”的情況將在后面介紹),并且不幸的是與此相關的代碼非常復雜,難以更改。

    對于“重量級”部件,這兩種方式的繪畫產生于兩條不同的途徑,取決于是“系統觸發”還是“程序觸發”。

    系統觸發的繪畫
    下面介紹“系統觸發”的繪畫操作是怎么產生的:

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


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

    程序觸發的繪畫
    由程序觸發的繪畫的產生如下所示:

    程序確定是一部分還是全部部件需要重畫以對應內部狀態的改變。
    ?

    程序調用部件的repaint(),該方法向AWT登記了一個異步的請求 -- 當前部件需要重畫。
    ?

    AWT促使事件分派線程去調用部件的update() 方法。
    注意: 在最初的重畫請求處理完成之前,如果在該部件上有多次對repaint()的調用,那么這些調用可以被合并成對update()的一次調用。決定什么時候應該合并多次請求的運算法則取決于具體的實現。如果多次請求被合并,最終被更新的區域將是所有這些請求所要求更新的區域的聯合(union)。

    ?

    如果部件沒有覆蓋(override)update()方法,update()的默認實現會清除部件背景(如果部件不是“輕量級”),然后只是簡單地調用paint()方法。
    因為作為默認的最終結果都是一樣的(paint()方法被調用),很多開發人員完全不知道一個分離的update() 方法的意義。確實,默認的update()的實現最終會轉回到對paint()方法的調用,然而,如果需要,這個更新操作的 "鉤子(hook)"可以使程根據不同的情況來處理程序觸發的繪畫。程序必須這么設想,對paint()的調用意味著Graphics的裁剪區"損壞"了并且必須全部重畫;然而對update()的調用沒有這種含義,它使程序做增量的繪畫。

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

    事實上,大多數GUI部件不需要增量繪畫,所有大部分程序可以忽略update()方法,并且簡單地覆蓋(override)paint()來呈現部件的當前狀態。這就意味著不管“系統觸發”還是“程序觸發”,在大多數部件上的表現從其本質上講是是等價的。

    繪畫與輕量級部件
    從應用開發人員的觀點看,“輕量級”的繪畫API基本上和“重量級”一樣(即,你只需要覆蓋paint()方法,同樣,調用repaint()方法去觸發繪圖更新)。然而,因為AWT的“輕量級”部件的框架全部使用普通Java代碼實現,在輕量級部件上繪畫機制的實現方式有一些微妙的不同。

    ?

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


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

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

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

    ?

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

    系統觸發的繪畫要求產生于本地系統(例如,輕量級的重量級祖先第一次現身的時候),這導致對paint()的直接調用。
    ?

    系統觸發型的繪圖要求產生于輕量框架(例如,輕量級部件的尺寸改變了),這導致對update()的調用,該方法進而默認地調用paint()。
    簡單地講,這意味著輕量級部件在update()和paint()之間沒有實質的差別,進一步講這又意味著“增量的繪圖技術”不能用到輕量級部件上。

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

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

    "靈活巧妙地"繪畫方法
    當AWT嘗試著使呈現部件的處理盡可能高效率時,部件自身paint()的實現可能對整體性能有重大的影響。影響這個處理過程的兩個關鍵領域是:

    使用裁剪區來縮小需要呈現的范圍。


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

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


    AWT繪畫準則
    AWT為繪制部件提供了一個簡單的回調API。當你使用它是,要遵循下面的原則:

    對于大多數程序,所有的客戶區繪畫代碼應該被放置在部件的paint()方法中。
    ?

    通過調用repaint()方法,程序可以觸發一個將來執行的paint()調用,不能直接調用paint()方法。
    ?

    對于界面復雜的部件,應該觸發帶參數的repaint()方法,使用參數定義實際需要更新的區域;而不帶參數調用會導致整個部件被重畫。
    ?

    因為對repaint()的調用會首先導致update()的調用,默認地會促成paint()的調用,所以重量級部件應該覆蓋update()方法以實現增量繪制,如果需要的話(輕量級部件不支持增量繪制) 。
    ?

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

    界面復雜的部件應該靈活地使用裁剪區來把繪畫范圍縮小到只包括與裁剪區相交的范圍。

    在Swing中的繪畫

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

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

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

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

    其他的繪畫屬性
    為了改善內部的繪畫算法性能,Swing另外引進了幾個JComponent的相互有關聯的屬性。引入這些屬性為的是處理下面兩個問題,這兩個問題有可能導致輕量級部件的繪畫成本過高:

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

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

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

    這些設置是:

    true:部件同意在它的矩形范圍包含的里所有像素位上繪畫。
    false:部件不保證其矩形范圍內所有像素位上繪畫。
    遮光(opaque)屬性允許Swing的繪圖系統去檢測是否一個對指定部件的重畫請求會導致額外的對其底層祖先的重畫。每個標準Swing部件的默認(遮光)opaque屬性值由當前的視-感UI對象設定。而對于大多數部件,該值為true。

    部件實現中的一個最常見的錯誤是它們允許遮光(opaque)屬性保持其默認值true,卻又不完全地呈現它們所轄的區域,其結果就是沒有呈現的部分有時會造成屏幕垃圾。當一個部件設計完畢,應該仔細的考慮所控制的遮光(opaque)屬性,既要確保透的使用是明智的,因為它會花費更多的繪畫時間,又要確保與繪畫系統之間的協約履行。

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

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

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

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

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

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


    true:部件指示沒有直接的子孫與其重疊。

    false: 部件不保證有沒有直接的子孫與之交疊。
    通過檢查isOptimizedDrawingEnabled屬性,Swing在重畫時可以快速減少對交疊部件的搜索。

    因為isOptimizedDrawingEnabled屬性是只讀的,于是部件改變默認值的唯一方法是在其子類覆蓋(override)這個方法來返回所期望的值。除了JLayeredPane,JDesktopPane,和JViewPort外,所有標準Swing部件對這個屬性返回true。

    繪畫方法
    適應于AWT的輕量級部件的規則同樣也適用于Swing部件 -- 舉一個例子,在部件需要呈現的時候就會調用paint() -- 只是Swing更進一步地把paint()的調用分解為3個分立的方法,以下列順序依次執行:

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

    SwingPaintDemo例子程序舉例說明了Swing的paintComponent()回調方法的簡單應用。

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

    paint()觸發paintComponent()方法。
    如果ui屬性為non-null,paintComponent()觸發ui.update()。
    如果部件的遮光屬性為true,ui.udpate()方法使用背景顏色填充部件的背景并且觸發ui.paint()。
    ui.paint()呈現部件的內容。
    這意味著Swing部件的擁有UI代理的子類(相對于JComponent的直系子類),應該在它們所覆蓋的paintComponent方法中觸發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....
    ??????? }
    ??? }

    如果因為某些原因部件的擴展類不允許UI代理去執行繪畫(是如果,例如,完全更換了部件的外觀),它可以忽略對super.paintComponent()的調用,但是它必須負責填充自己的背景,如果遮光(opaque)屬性為true的話,如前面在遮光(opaque)屬性一章講述的。

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

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

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

    Container.paint()的默認實現會遞歸地調用任何輕量級子孫的paint()方法。
    ?

    當到達第一個Swing部件時,JComponent.paint()的默認執行做下面的步驟:
    如果部件的雙緩沖屬性為true并且部件的RepaintManager上的雙緩沖已經激活,將把Graphics對象轉換為一個合適的屏外Graphics。
    調用paintComponent()(如果使用雙緩沖就把屏外Graphics傳遞進去)。
    調用paintChildren()(如果使用雙緩沖就把屏外Graphics傳遞進去),該方法使用裁剪并且遮光和optimizedDrawingEnabled等屬性來嚴密地判定要遞歸地調用哪些子孫的paint()。
    如果部件的雙緩沖屬性為true并且在部件的RepaintManager上的雙緩沖已經激活,使用最初的屏幕Graphics對象把屏外映像拷貝到部件上。


    注意:JComponent.paint()步驟#1和#5在對paint()的遞歸調用中被忽略了(由于paintChildren(),在步驟#4中介紹了),因為所有在swing窗體層次中的輕量級部件將共享同一個用于雙緩沖的屏外映像。

    (B) 繪畫需求從一個javax.swing.JComponent擴展類的repaint()調用上產生:

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

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


    使用裁剪框以及遮光和optimizedDrawingEnabled屬性確定“根”部件,繪畫一定從這個部件開始(處理透明以及潛在的重疊部件)。
    如果根部件的雙緩沖屬性為true,并且根部件的RepaintManager上的雙緩沖已激活,將轉換Graphics對象到適當的屏外Graphics。
    調用根部件(該部件執行上述(A)中的JComponent.paint()步驟#2-4)上的paint(),導致根部件之下的、與裁剪框相交的所有部件被繪制。
    如果根部件的doubleBuffered屬性為true并且根部件的RepaintManager上的雙緩沖已經激活,使用原始的Graphics把屏外映像拷貝到部件。


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

    這意味著對于Swing部件來說,update()不再被調用。

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

    同步繪圖
    象我們在前面章節所講述的,paintImmediately()表現為一個入口,用來通知Swing部件繪制自身,確認所有需要的繪畫都能適當地產生。這個方法也可能用來安排同步的繪圖需求,就象它的名字所暗示的,即一些部件有時候需要保證它們的外觀實時地與其內部狀態保持一致(例如,在JScrollPane執行滾定操作的時候確實需要這樣并且也做到了)。

    程序不應該直接調用這個方法,除非有合理實時繪畫需要。這是因為異步的repaint()可以使多個重復的需求得到有效的精簡,反之直接調用paintImmediately()則做不到這點。另外,調用這個方法的規則是它必須由事件分派線程中的進程調用;它也不是為能以多線程運行你的繪畫代碼而設計的。關于Swing單線程模式的更多信息,參考一起歸檔的文章"Threads and Swing."

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

    對于大多數程序來講,RepaintManager可以看做是Swing的內部系統的一部分,并且甚至可以被忽略。然而,它的API為程序能更出色地控制繪畫中的幾個要素提供了選擇。

    ?

    "當前的"RepaintManager
    RepaintManager設計 is designed to be dynamically plugged, 雖然 有一個單獨的接口。下面的靜態方法允許程序得到并且設置"當前的"RepaintManager:

    ???? public static RepaintManager currentManager(Component c)
    ??? public static RepaintManager currentManager(JComponent c)
    ??? public static void
    ???????? setCurrentManager(RepaintManager aRepaintManager)
    更換"當前的"RepaintManager
    總的說來,程序通過下面的步驟可能會擴展并且更換RepaintManager:

    ??? RepaintManager.setCurrentManager(new MyRepaintManager());
    你也可以參考RepaintManagerDemo ,這是個簡單的舉例說明RepaintManager加載的例子,該例子將把有關正在執行重畫的部件的信息打印出來。

    擴展和替換RepaintManager的一個更有趣的動機是可以改變對重畫的處理方式。當前,默認的重畫實現所使用的來跟蹤dirty regions的內部狀態值是包內私有的并且因此不能被繼承類訪問。然而,程序可以實現它們自己的跟蹤dirty regions的機制并且通過超載下面的方法對重畫需求的縮減:

    ???? 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()方法是在調用Swing部件的repaint()的之后被調用的,因此可以用作鉤子來捕獲所有的重畫需求。如果程序超載了這個方法(并且不調用super.addDirtyRegion()),那么它改變了它的職責,而使用invokeLater()把Runnable放置到EventQueue ,該隊列將在合適的部件上調用paintImmediately()(translation: not for the faint of heart).

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

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

    ??? RepaintManager.currentManager(mycomponent).
    ????????????????? setDoubleBufferingEnabled(false);
    注意:因為Swing的默認實現要初始化一個單獨的RepaintManager實例,mycomponent參數與此不相關。


    Swing繪畫準則
    Swing開發人員在寫繪畫代碼時應該理解下面的準則:

    對于Swing部件,不管是系統-觸發還是程序-觸發的請求,總會調用paint()方法;而update()不再被Swing部件調用。
    ?

    程序可以通過repaint()觸發一個異步的paint()調用,但是不能直接調用paint()。
    ?

    對于復雜的界面,應該調用帶參數的repaint(),這樣可以僅僅更新由該參數定義的區域;而不要調用無參數的repaint(),導致整個部件重畫。
    ?

    Swing中實現paint()的3個要素是調用3個分離的回調方法:
    paintComponent()
    paintBorder()
    paintChildren()
    Swing部件的子類,如果想執行自己的繪畫代碼,應該把自己的繪畫代碼放在paintComponent()方法的范圍之內。(不要放在paint()里面)。
    ?

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

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

    使用UI代理(包括JPanel)的Swing部件的擴展類的典型作法是在它們自己的paintComponent()的實現中調用super.paintComponent()。因為UI代理可以負責清除一個遮光部件的背景,這將照顧到#5.

    Swing通過JComponent的doubleBuffered屬性支持內置的雙緩沖,所有的Swing部件該屬性默認值是true,然而把Swing容器的遮光設置為true有一個整體的構思,把該容器上的所有輕量級子孫的屬性打開,不管它們各自的設定。

    強烈建議為所有的Swing部件使用雙緩沖。

    界面復雜的部件應該靈活地運用剪切框來,只對那些與剪切框相交的區域進行繪畫操作,從而減少工作量。

    總結
    不管AWT還是Swing都提供了方便的編程手段使得部件內容能夠正確地顯示到屏幕上。雖然對于大多數的GUI需要我們推薦使用Swing,但是理解AWT的繪畫機制也會給我們帶來幫助,因為Swing建立在它的基礎上。

    關于AWT和Sing的特點就介紹到這里,應用開發人員應該盡力按照本文中介紹的準則來撰寫代碼,充分發揮這些API功能,使自己的程序獲得最佳性能。

     
    ?

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

    評論

    # re: [轉發]AWT和Swing中的繪畫 2009-08-10 11:12

    真強大啊,不頂不行  回復  更多評論   


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 偷自拍亚洲视频在线观看 | 亚洲精品97久久中文字幕无码| 麻豆国产人免费人成免费视频| 亚洲免费视频播放| 黄页网站免费在线观看| 亚洲人成激情在线播放| 午夜毛片不卡免费观看视频| 亚洲乱妇熟女爽到高潮的片| 免费观看日本污污ww网站一区| 四虎影视久久久免费| 一二三四视频在线观看中文版免费| 亚洲一区中文字幕在线电影网 | 香蕉视频免费在线播放| 免费黄色网址网站| 亚洲色爱图小说专区| 豆国产96在线|亚洲| 亚洲中文无韩国r级电影| 中文在线观看免费网站| 午夜无遮挡羞羞漫画免费| 无遮挡a级毛片免费看| 免费爱爱的视频太爽了| 日韩电影免费在线观看网址 | 亚洲精品亚洲人成在线观看| 国产一区二区三区免费| 国产精品亚洲美女久久久| a级成人毛片免费视频高清| 亚洲欧洲精品国产区| 国产美女精品视频免费观看 | 18未年禁止免费观看| 亚洲国产精品无码中文lv| 97av免费视频| 亚洲免费网站观看视频| 亚洲小说区图片区另类春色| 曰批全过程免费视频网址| 白白色免费在线视频| 久久久久亚洲AV无码专区首| 精品国产免费一区二区三区| 亚洲午夜精品久久久久久浪潮| 久久免费看少妇高潮V片特黄| 中文字幕精品亚洲无线码二区 | 青娱乐在线免费观看视频|