MFC重繪原理的關鍵理解

findumars發表於2013-07-28

// ====================Windows重繪訊息與函式==========================

得到桌面視窗的控制程式碼,然後再繪圖
HWND GetDesktopWindow(VOID);

當需要更新或重新繪製視窗的外觀時,應用程式就會傳送WM_PAINT訊息對視窗進行重新繪製。


Invalidate()是強制系統進行重畫,但是不一定就馬上進行重畫。因為Invalidate()只是通知系統,此 時的視窗已經變為無效。強制系統呼叫WM_PAINT,而這個訊息只是Post就是將該訊息放入訊息佇列。當執行到WM_PAINT訊息時才會對敞口進行重繪。

UpdateWindow只向窗體傳送WM_PAINT訊息,在傳送之前判斷GetUpdateRect(hWnd,NULL,TRUE)看有無可繪製的客戶區域,如果沒有,則不傳送WM_PAINT。

RedrawWindow()則是具有Invalidate()和UpdateWindow()的雙特性。宣告視窗的狀態為無效,並立即更新視窗,立即呼叫WM_PAINT訊息處理。

呼叫Invalidate之後,螢幕不一定馬上更新,因為WM_PAINT訊息不一定在佇列頭部,而呼叫UpdateWindow會使WM_PAINT訊息馬上執行的,繞過了訊息佇列。如果你呼叫Invalidate之後想馬上更新螢幕,那就加上UpdateWindow()這條語句。

為什麼不直接使用虛擬函式實現訊息處理函式呢?這是一個GOOD QUESTION。前面已經說過,MFC的設計者們在設計MFC時有一個很明確的目標,就是使得“MFC的程式碼儘可能小,速度儘可能快”,如果採用虛擬函式,那麼對於所有的視窗訊息,都必須有一個與之對應的虛擬函式,因而對每一個從CWnd派生的類而言,都會有一張很大的虛擬函式表vtbl。但是在實際應用中,一般只對少數的訊息進行處理,大部分都交給系統預設處理,所以表中的大部分項都是無用項,這樣做就浪費了很多記憶體資源這同MFC設計者們的設計目標是相違背的。當然,MFC所使用的方法只是解決這類問題的方式之一,不排除還有其他的解決方式,但就我個人觀點而言,這是一種最好的解決方式,體現了很高的技巧性,值得我們學習。

  至於這第二個問題,是由上面的問題引申出來的。如果在子類和父類中出現了相同的訊息出來函式,VC編譯器會怎麼處理這個問題呢?VC不會將它們看作錯誤,而會象對待虛擬函式類似的方式去處理,但對於訊息處理函式(帶afx_msg字首),則不會生成虛擬函式表vtbl

 

Invalidate過程看成類似CombineRgn()取並集,把validate過程看成取差集即可。

 

用WM_PAINT處理重畫是非同步(asynchronous)的。也就是說,在invalidate之後視窗並不會立即重畫而是等到訊息佇列為空時再重畫,這樣就有一個時間差。這個事件差有時短到不被注意,但有時就是個大問題(尤其是當程式需要執行耗費時間的任務,如串列埠I/O)。

這時可以採用同步重畫法,直接用GetDC()獲得hDC執行重畫操作。如果非要使用WM_PAINT來同步重畫(個人比較喜歡這種方法,和重畫有關的程式碼就應該在WM_PAINT的處理程式裡嘛),可以使用UpdateWindow()和RedrawWindow(). 這兩個API函式會直接把WM_PAINT送進視窗的訊息佇列而不是應用程式的訊息佇列,這樣就不用等到最後了。注意前者當update region不為空時才會傳送WM_PAINT,後者的控制選項更為豐富。

 

OnEraseBkGnd(),是視窗背景需要重新整理時由系統呼叫的。明顯的一個例子是設定視窗的背景顏色(你可以把這放在OnPaint中去做,但是會使產生閃爍的現象)。
OnPaint()用來響應WM_PAINT訊息,視類的OnPaint()內部根據是列印還是螢幕繪製分別以不同的引數呼叫OnDraw()虛擬函式。所以在OnDraw()裡你可以區別對待列印和螢幕繪製。
其實,MFC在進行列印前後還做了很多工作,呼叫了很多虛擬函式,比如OnPreparePrint()等。
OnCtlColor()是當視窗的控制元件需要繪製時發生的,它將繪製視窗的控制元件。

GetUpdateRect(GetSafeHwnd(), &R, B);

 

參考:

http://www.cnblogs.com/sfqh/p/3384730.html

------------------------------------------------------------------------------

Invalidate函式的總結
InvalidateRect只是增加重繪區域,下次WM_PAINT的時候才生效
InvalidateRect函式中的引數TRUE表示系統會在你畫之前用背景色將所選區域覆蓋一次,預設背景色為白色,可以通過設定BRUSH來改變背景色。
Invalidate標記一個需要重繪的無效區域,並不意味著呼叫該函式後就立刻進行重繪。類似於PostMessage(WM_PAINT),需要處理到WM_PAINT訊息時才真正重繪。以為您Invalidate之後還有其他的語句正在執行,程式沒有機會去處理WM_PAINT訊息,但當函式執行完畢後,訊息處理才得以進行。

Invalidate只是放一個WM_PAINT訊息在佇列裡,不做別的,所以只有噹噹前函式返回後,進入訊息迴圈,取出WM_PAINT,才執行PAINT,所以不管Invalidate放哪裡,都是最後的。
InvalidateRect(hWnd,&rect,TRUE);向hWnd窗體發出WM_PAINT的訊息,強制客戶區域重繪製,rect是你指定要重新整理的區域,此區域外的客戶區域不被重繪,這樣防止客戶區域的一個區域性的改動,而導致整個客戶區域重繪而導致閃爍,如果最後的引數為TRUE,則還向窗體傳送WM_ERASEBKGND訊息,使背景重繪,當然在客戶區域重繪之前。
UpdateWindow只向窗體傳送WM_PAINT訊息,在傳送之前判斷GetUpdateRect(hWnd,NULL,TRUE)看有無可繪製的客戶區域,如果沒有,則不傳送WM_PAINT
如果希望立即重新整理無效區域,可以在呼叫InvalidateRect之後呼叫UpdateWindow,如果客戶區的任一部分無效,則UpdateWindow將導致Windows用WM_PAINT訊息呼叫視窗過程(如果整個客戶區有效,則不呼叫視窗過程)。這一WM_PAINT訊息不進入訊息佇列,直接由WINDOWS呼叫視窗過程。視窗過程完成重新整理以後立刻退出,WINDOWS將控制返回給程式中UpdateWindow呼叫之後的語句。(windows程式設計第5版 P98)

Invalidate()之後:(MFC的,順便了)
OnPaint()->OnPrepareDC()->OnDraw()
所以只是重新整理在OnPaint()和OnDraw()函式中的繪圖語句。其它地方沒有影響。

UpdateData()順便說下,這個函式不是重新整理介面用的。
UpdateData();引數為FALSE時,將介面上控制元件繫結的變數的資料導到控制元件內,引數為TRUE時,匯入方向則相反。

另外,Delphi裡響應WM_ERASEBKGND使用FillRect重畫背景

參考:

http://www.360doc.com/content/10/0823/10/1066008_48105283.shtml

相關文章