張亞利 吳彥國(guó)
摘要:在進(jìn)行Java GUI編程時(shí),程序利用paint(Graphics g)方法來(lái)實(shí)現(xiàn)組件的繪制;然而Java的繪圖機(jī)制是怎樣的,paint方法又是何時(shí)調(diào)用、如何調(diào)用,且每次調(diào)用時(shí)paint方法的執(zhí)行次數(shù)是怎樣的?輕量級(jí)和重量級(jí)組件調(diào)用paint有哪些區(qū)別等,本文作者將針對(duì)這些問(wèn)題,用案例分析的方法來(lái)一一解答。
關(guān)鍵詞:繪圖機(jī)制;paint;調(diào)用
學(xué)過(guò)Java程序設(shè)計(jì)語(yǔ)言的朋友都知道,在學(xué)習(xí)Java GUI編程時(shí),我們都知道paint方法是用來(lái)繪制圖形的。有了paint方法,我們可以在Java中繪制各種漂亮的組件,或開(kāi)發(fā)漂亮炫酷的游戲。但是paint是如何調(diào)用的?程序啟動(dòng)時(shí),paint方法執(zhí)行了一次還是多次,原理是什么?另外Swing組件和AWT組件對(duì)super.paint方法是如何相應(yīng)的,組件在處理動(dòng)畫(huà)時(shí),對(duì)KeyListner又是如何響應(yīng)的呢?關(guān)于這些細(xì)節(jié)性的問(wèn)題,許多初學(xué)者總是模棱兩可。筆者經(jīng)過(guò)查找資料,并寫(xiě)程序進(jìn)行驗(yàn)證,得出一些結(jié)果,以供分享。
問(wèn)題1:Java的繪圖機(jī)制是怎樣的?
AWT是使用回調(diào)機(jī)制來(lái)處理繪畫(huà)的,并且這種機(jī)制對(duì)輕量級(jí)和重量級(jí)組件相同。也就是說(shuō)我們需要把渲染組件的代碼寫(xiě)在一個(gè)可覆蓋的特殊的方法中,這個(gè)方法是public void paint(Graphics g);其中 Graphics是圖形上下文對(duì)象,用來(lái)完成具體的繪制工作。paint方法由AWT框架調(diào)用或者由程序自身通過(guò)repaint方法來(lái)調(diào)用。
問(wèn)題2:畫(huà)圖時(shí),paint是何時(shí)調(diào)用,如何調(diào)用,調(diào)用幾次的問(wèn)題。
Java繪圖時(shí),用到的paint方法繼承自AWT中的Component方法,該方法在對(duì)象加載時(shí)自動(dòng)調(diào)用,用來(lái)繪制該組件內(nèi)部的所有圖形圖像。使用repaint()方法,可以再次調(diào)用paint方法,實(shí)現(xiàn)組件的重繪。但是paint方法是什么時(shí)候開(kāi)始執(zhí)行,執(zhí)行了幾次呢?下面我們用例子,來(lái)加以分析。
public class Test extends Applet//引入包的代碼省略
{Image img = null;int t =0;
public void init(){System.out.print("初始化====");
img = getImage(getCodeBase(),"1.jpg");}
public void paint(Graphics g)
{t++;System.out.print("我是paint===="+t+"");
g.fillRect(50,50,80,80);//代碼段1,繪制矩形
// g.drawImage(img,50,50,this); //代碼段2,繪制圖形}}
執(zhí)行代碼段1的結(jié)果:
初始化====我是paint====1
執(zhí)行代碼段2的結(jié)果:
初始化====我是paint====1我是paint====2……我是paint====47
從上面結(jié)果可以發(fā)現(xiàn),啟動(dòng)瀏覽器時(shí),系統(tǒng)自動(dòng)調(diào)用init()方法進(jìn)行程序初始化,接著系統(tǒng)自動(dòng)找到paint()方法對(duì)圖像進(jìn)行繪制。而且paint()方法調(diào)用的次數(shù),跟繪制的內(nèi)容有關(guān),圖形簡(jiǎn)單時(shí),可能執(zhí)行一次,圖像復(fù)雜時(shí),可能會(huì)自動(dòng)調(diào)用很多次,直到圖形圖像完全繪制成功。另外還有當(dāng)組件大小發(fā)生改變,或組件有壞點(diǎn)(damage)需要被修復(fù)時(shí),系統(tǒng)都會(huì)自發(fā)地調(diào)用repaint方法,進(jìn)行組件的重繪。
問(wèn)題3:Java程序繪圖時(shí),JPanel和Panel對(duì)super.paint()的響應(yīng)問(wèn)題。
Java通過(guò)調(diào)用paint()方法來(lái)繪制組件,但是 AWT框架中輕量級(jí)組件與重量級(jí)組件實(shí)現(xiàn)代碼還是有區(qū)別的。
輕量級(jí)組件的繪制依賴與包含關(guān)系階層中的重量級(jí)祖先組件,當(dāng)這個(gè)祖先組件被通知繪制時(shí),它將把繪制請(qǐng)求轉(zhuǎn)化為繪制自身上任何可見(jiàn)的子孫組件,這個(gè)方法是由java.awt.Container中的 paint() 方法來(lái)完成的,因此任何Container的子類(lèi),在覆蓋paint方法時(shí)一定要記得調(diào)用super.paint()來(lái)保證,它上面的輕量級(jí)子孫組件都被繪制到了。代碼可以這樣來(lái)完成:
public class MyContainer extends Container {
public void paint(Graphics g) { // 先繪制自身內(nèi)容, 然后確保輕量級(jí)子組件被繪制
super.paint(g);}}
下面我們將通過(guò)案例,來(lái)觀察:
class myPanel extends JPanel implements KeyListener //重要代碼:繼承JPanel/Panel
{int x=50;public myPanel(){addKeyListener(this);} //為面板增加鍵盤(pán)監(jiān)聽(tīng)器
public void paint(Graphics g){super.paint(g);//重要代碼段:super.paint
g.setColor(Color.orange); g.fillRect(x, 50, 60, 60);}//設(shè)置畫(huà)筆顏色,填充矩形
public void keyPressed(KeyEvent e){x=x+10;repaint();//釋放鍵盤(pán),矩形向右移動(dòng)10像素;}
public void keyTyped(KeyEvent e){}
public void keyReleased(KeyEvent e){}}
創(chuàng)建myPanel對(duì)象p,設(shè)置背景色為青色,同時(shí)在面板p上添加標(biāo)簽“測(cè)試super.paint()”,程序?qū)⒊霈F(xiàn)四種情況,對(duì)應(yīng)3種效果。
(1)面板繼承JPanel,且paint方法中調(diào)用super.paint(),所得結(jié)果如圖2。
(2)面板繼承JPanel,且paint方法中沒(méi)有調(diào)用super.paint(),所得結(jié)果如圖1。
(3)面板繼承Panel,且paint方法中沒(méi)有調(diào)用super.paint(),所得結(jié)果如圖3。
(4)面板繼承Panel,且paint方法中調(diào)用super.paint(),所得結(jié)果如圖2。
問(wèn)題4:處理動(dòng)畫(huà)時(shí),JPanel和JApplet等Swing組件對(duì)KeyListner無(wú)響應(yīng)的問(wèn)題。
為面板增加鍵盤(pán)監(jiān)聽(tīng)器KeyListner,若面板繼承java.awt.Panel,那么當(dāng)程序運(yùn)行時(shí),每敲擊一次鍵盤(pán),上述案例中的矩形方塊開(kāi)始向右移動(dòng)10個(gè)像素的距離;但是若面板繼承javax.swing.JPanel,敲擊鍵盤(pán),則無(wú)任何相應(yīng)動(dòng)作。這又是什么原因引起的呢?
經(jīng)過(guò)實(shí)驗(yàn)分析,筆者發(fā)現(xiàn)JPanel等Swing組件在渲染完成后,無(wú)法獲取focus,所以我們只需要上層容器setvisible(true)之前,先設(shè)置 p.requestFocus();為面板對(duì)象獲取焦點(diǎn)即可。
以上分析,希望對(duì)愛(ài)好Java GUI編程的學(xué)習(xí)者提供幫助。