win32 WM_PAINT消息


在Windows API編程中,WM_PAINT是Windows窗口的一個重要消息,應用程序就是通過響應這個消息來完成窗口的繪制。

The
WM_PAINT message is generated by the system and should not be sent by
an application.The system sends this message when there are no other
messages in the application's message queue

注意:WM_PAINT消息是由系統產生,非要等應用程序的消息隊列為空時才發送WM_PAINT消息 。


其實系統會在很多的不同的機制下發送WM_PAINT消息,比如調用UpdateWindow函數,第一次創建窗口,改變了窗口的大小,最大化,最小化等
等。這些動作的產生都是有系統來控制的,應用程序只是接收消息,並處理消息。


當Window檢測到窗口被覆蓋的地方需要恢復的時候,它會向用戶程序發送一個WM_PAINT消息,消息中包括了需要恢復的區域,然后由用戶程序來決定
如何恢復被覆蓋的內容。窗口過程收到WM_PAINT消息后,並不代表整個客戶區都需要被刷新,有可能客戶區被覆蓋的區域只有一小塊,這個區域叫做“無效
區域”,程序只需要更新這個區域。與WM_TIMER消息類似,WM_PAINT消息也是一個低級別的消息,雖然它不會像WM_TIMER消息一樣被丟
棄,但Windows總是在消息循環空的時候才把WM_PAINT放入其中,實際上,Windows為每個窗口維護一個“繪圖信息結構”,無效區域的坐標
就在其中,每當消息循環空的時候,如果Windows發現存在一個無效區域,就會放入一個WM_PAINT消息。


無效區域的坐標並不附帶在WM_PAINT消息的參數中,在程序中有其他方法可以獲取,WM_PAINT消息只是通知程序有個區域需要更新而已,所以
Windows也不會同時將兩條WM_PAINT消息放入消息循環中,當Windows要放入一條WM_PAINT消息的時候,如果發現已經存在一個無效
區域了,那么它只需要把新舊兩個無效區域合並計算出一個無效區域就可以了,消息循環中還是只需要一條WM_PAINT消息。


如果程序在WM_PAINT消息中對客戶區刷新完畢后工作並沒有結束,如果不使無效區域變得有效,Windows會在下一輪消息循環中繼續放入一個
WM_PAINT消息,而不是根據程序是否執行了刷新過程,所以程序也可以不去刷新客戶區,而是簡單地用一個ValidateRect函數直接讓客戶區變
得有效,以此來“欺騙”Windows已經沒有無效區域了,當Windows檢查“繪圖信息結構”的時候發現沒有了無效區域,也就不會繼續發送
WM_PAINT消息了。

那么“繪圖信息結構”怎么獲取
呢?BeginPaint函數的第二個參數是一個繪圖信息結構的緩沖區地址,windows會在這里返回繪圖信息結構,結構中包含了無效區域的位置和大
小,繪圖信息結構的定義如下:

typedef 
struct
tagPAINTSTRUCT { 
//
ps 

HDC hdc; 

BOOL fErase; 

RECT rcPaint; 

BOOL fRestore; 

BOOL fIncUpdate; 

BYTE rgbReserved[
32
]; 

} PAINTSTRUCT; 

其中hdc字段是窗口的設備環境句柄,rcPaint字段是一個
RECT結構,它指定了無效區域矩形的對角頂點,fErase字段如果為非零值,表示Windows在發送WM_PAINT消息前已經使用背景色擦除了無
效區域,后面3個字段是Windows內部使用的,應用程序不必去理會他們。

摘自《Windows環境下32位匯編語言程序設計》


大多數Windows程序在WinMain中進入消息循環之前的初始化期間都要呼叫函數UpdateWindow。Windows利用這個機會給窗口消息
處理程序發送第一個WM_PAINT消息。這個消息通知窗口消息處理程序:必須繪制顯示區域。此后,窗口消息處理程序應在任何時刻都准備好處理其它
WM_PAINT消息,必要的話,甚至重新繪制窗口的整個顯示區域。在發生下面幾種事件之一時,窗口消息處理程序會接收到一個WM_PAINT消息:

在使用者移動窗口或顯示窗口時,窗口中先前被隱藏的區域重新可見。

  

使用者改變窗口的大小(如果窗口類別樣式有着CS_HREDRAW和CS_VREDRAW位旗標的設定)。

  

程序使用ScrollWindow或ScrollDC函數滾動顯示區域的一部分。

  

程序使用InvalidateRect或InvalidateRgn函數刻意產生WM_PAINT消息。

  

在某些情況下,顯示區域的一部分被臨時覆蓋,Windows試圖保存一個顯示區域,並在以后恢復它,但這不一定能成功。在以下情況下,Windows可能發送WM_PAINT消息:

Windows擦除覆蓋了部分窗口的對話框或消息框。

  

菜單下拉出來,然后被釋放。

  

顯示工具提示消息。

  

在某些情況下,Windows總是保存它所覆蓋的顯示區域,然后恢復它。這些情況是:

鼠標光標穿越顯示區域。

  

圖標拖過顯示區域。

  


處理WM_PAINT消息要求程序寫作者改變自己向顯示器輸出的思維方式。程序應該組織成可以保留繪制顯示區域需要的所有信息,並且僅當「響應要求」-即
Windows給窗口消息處理程序發送WM_PAINT消息時才進行繪制。如果程序在其它時間需要更新其顯示區域,它可以強制Windows產生一個
WM_PAINT消息。這看來似乎是在屏幕上顯示內容的一種舍近求遠的方法。但您的程序結構可以從中受益。

1. 系統何時發送WM_PAINT消息?


系統會在多個不同的時機發送WM_PAINT消息:當第一次創建一個窗口時,當改變窗口的大小時,當把窗口從另一個窗口背后移出時,當最大化或最小化窗口
時,等等,這些動作都是由
系統管理的,應用只是被動地接收該消息,在消息處理函數中進行繪制操作;大多數的時候應用也需要能夠主動引發窗口中的繪制操作,比如當窗口顯示的數據改變
的時候,這一般是通過InvalidateRect和
InvalidateRgn函數來完成的。InvalidateRect和InvalidateRgn把指定的區域加到窗口的Update
Region中,當應用的消息隊列沒有其他消息時,如果窗口的Update Region不為空時,系統就會自動產生WM_PAINT消息。


系統為什么不在調用Invalidate時發送WM_PAINT消息呢?又為什么非要等應用消息隊列為空時才發送WM_PAINT消息呢?這是因為系統把
在窗口中的繪制操作當作一種低優先級的操作,於是盡
可能地推后做。不過這樣也有利於提高繪制的效率:兩個WM_PAINT消息之間通過InvalidateRect和InvaliateRgn使之失效的區
域就會被累加起來,然后在一個WM_PAINT消息中一次得到
更新,不僅能避免多次重復地更新同一區域,也優化了應用的更新操作。像這種通過InvalidateRect和InvalidateRgn來使窗口區域無
效,依賴於系統在合適的時機發送WM_PAINT消息的機
制實際上是一種異步工作方式,也就是說,在無效化窗口區域和發送WM_PAINT消息之間是有延遲的;有時候這種延遲並不是我們希望的,這時我們當然可以
在無效化窗口區域后利用SendMessage 發送一條WM_PAINT消息來強制立即重畫,但不如使用Windows
GDI為我們提供的更方便和強大的函數:UpdateWindow和RedrawWindow。UpdateWindow會檢查窗口的Update
Region,當其不為空時才發送WM_PAINT消息;RedrawWindow則給我們更多的控制:是否重畫非客戶區和背景,是否總是發送
WM_PAINT消息而不管Update Region是否為空等。

2. BeginPaint


天在處理WM_PAINT消息時產生了一個低級的錯誤,並搞的我花了快一個小時才找到原因。我在處理消息時,沒有使用BeginPaint和
EndPaint這對函數,結果我其余的消息彈不出來,窗口拖動時,不停閃爍(其實那就是重繪)。后來還是在MSDN上找到了答案,現將原話貼出來。(在
MSDN的The WM_PAINT Message標題中)

BeginPaint
sets the update region of a window to NULL. This clears the region,
preventing it fromgenerating subsequent WM_PAINT messages. If an
application processes a WM_PAINT message but does not call BeginPaint
or otherwise clear the update region, the application continues to
receive WM_PAINT messages as long as the region is not empty. In all
cases, an application must clear the update region before returning
from the WM_PAINT message. 

BeginPaint
函數的作用就是將窗口需要重繪的區域設置為空(也就是Update
Region置空)。在正常情況下,我們接收到了WM_PAINT消息后,窗口的Update
Region都是非空的(如果為空就不需要發送WM_PAINT消息了)。而當你響應這個消息的時候又不調用BeginPaint來清空,窗口的
Update
Region就一直是非空的,系統就會一直發送WM_PAINT消息。這樣就形成了一個處理WM_PAINT消息的死循環。這就是我出現錯誤的原因,低級
錯誤。

BeginPaint和WM_PAINT消息緊密相
關。試一試在WM_PAINT處理函數中不寫BeginPaint會怎樣?程序會像進入了一個死循環一樣達到驚人的CPU占用率,你會發現程序總在處理一
個接 一個的WM_PAINT消息。這是因為在通常情況下,當應用收到WM_PAINT消息時,窗口的Update
Region都是非空的(如果為空就不需要發送WM_PAINT消息了),BeginPaint的一個作用就是把該Update
Region置為空,這樣如果不調用BeginPaint,窗口的Update
Region就一直不為空,如前所述,系統就會一直發送WM_PAINT消息。

BeginPaint
和WM_ERASEBKGND消息也有關系。當窗口的Update
Region被標志為需要擦除背景時,BeginPaint會發送WM_ERASEBKGND消息來重畫背景,同時在其返回信息里有一個標志表明窗口背景
是否被重畫過。當我們用InvalidateRect和InvalidateRgn來把指定區域加到Update
Region中時,可以設置該區域是否需要被擦除背景,這樣下一個BeginPaint就知道是否需要發送WM_ERASEBKGND消息了。


當然關於
WM_PAINT消息還有很多的知識需要學習。另外要注意的一點是,BeginPaint只能在WM_PAINT處理函數中使用,並且在調用了
BeginPaint函數后,不要忘記了調用EndPaint函數,他們可是一對的。

3.重畫函數 InvalidateRect,UpdateWindow, RedrawWindow的區別

InvalidateRect是通過線程的消息隊列來發送刷新消息,是最常用的。 

UpdateWindow
是直接調用窗口函數立即響應刷新消息,使窗口刷新消息優先被響應(消息隊列中如果沒有WM_PAINT消息就什么都不執行),一般是在
ShowWindow之后調用。 

RedrawWindow相當於先調用InvalidateRect,緊接着又調用UpdateWindow,此外RedrawWindow還提供了一些前兩者沒法做到的功能。

補充幾點:

1.WM_Paint
是一個被動消息,不能通過普通的方法簡單的 sendmessage WM_paint
了事這是不行的;但通過消息由程序員引發不是不可能;通過幾個特殊的常數可以做到,不過要到delphi下找

2.sendmessage 可以將消息發送到消息隊列;但windows會自動判斷是否存在無效的畫圖區域;如果存在無效的畫圖區域,則可能會重畫,反之則棄用該消息.

3.
可以使用 InvalidateRect
等幾個APi將屏幕上任意一個個矩形區域設置為無效區域,在UpdateWindow后調用后,windows會自動查找是否存在無效,並重畫,該矩形區
域;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM