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消息,并且該消息不應該被程序(自己寫代碼用SendMessage)來發送。
當調用UpdateWindow函數,或者是Window檢測到窗口被覆蓋的地方需要恢復的時候,比如,第一次創建窗口,改變了窗口的大小,最大化,最小化等等(其實這些事件發生時會調用UpdateWindow函數,由該函數發送WM_PAINT消息),它會向用戶程序發送一個WM_PAINT消息。窗口過程收到WM_PAINT消息后,并不代表整個客戶區都需要被刷新,有可能客戶區被覆蓋的區域只有一小塊,這個區域叫做“無效區域”,程序只需要更新這個區域。與WM_TIMER消息類似,WM_PAINT消息也是一個低級別的消息,雖然它不會像WM_TIMER消息一樣被丟棄,但Windows總是在消息循環空的時候才把WM_PAINT放入其中。
無效區域的坐標并不附帶在WM_PAINT消息的參數中,在程序中有其他方法可以獲取。WM_PAINT消息只是通知程序有個區域需要更新而已,所以Windows也不會同時將兩條WM_PAINT消息放入消息循環中。當Windows要放入一條WM_PAINT消息的時候,如果發現已經存在一個無效區域了,那么它只需要把新舊兩個無效區域合并計算出一個無效區域就可以了,消息循環中還是只需要一條WM_PAINT消息。
實際上,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結構,它指定了無效區域矩形的對角頂點(如果開始有一個((0,0),(40,40)),現在又來一個((20,20),(60,30)),那么拼接后就是((0,0),(60,40))),fErase字段如果為非零值,表示Windows在發送WM_PAINT消息前已經使用背景色擦除了無效區域,后面3個字段是Windows內部使用的,應用程序不必去理會他們。
在某些情況下,顯示區域的一部分被臨時覆蓋,Windows試圖保存一個顯示區域,并在以后恢復它,但這不一定能成功。Windows可能發送WM_PAINT消息:Windows擦除覆蓋了部分窗口的對話框或消息框;菜單下拉出來,然后被釋放;顯示工具提示消息。
在某些情況下,Windows總是一定保存它所覆蓋的顯示區域,然后恢復它。這些情況是:鼠標光標穿越顯示區域;圖標拖過顯示區域。
有時候應用也需要能夠主動引發窗口中的繪制操作,比如當窗口顯示的數據改變的時候,這一般是通過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是否為空等。
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函數,他們可是一對的。
重畫函數 InvalidateRect,Invalidate,UpdateWindow, RedrawWindow
InvalidateRect(部分區域) 和Invalidate(整個窗口) 僅僅是用來設置無效區域,但是并不重繪窗口。
UpdateWindow 檢查窗口有無無效區域,如果有,則立即發送一個WM_PAINT 消息給窗口并立即重畫。
RedrawWindow相當于先調用InvalidateRect,緊接著又調用UpdateWindow,此外RedrawWindow還提供了一些前兩者沒法做到的功能。
如果不調用 InvalidateRect就調用 UpdateWindow,那么UpdateWindow什么都不做,因為沒有無效區域。如果調用 InvalidateRect 后不調用UpdateWindow,則系統會自動在窗口消息隊列為空的時候,系統自動發送一WM_PAINT消息。