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
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 訊息了。
另外要注意的一點是,BeginPaint 只能在 WM_PAINT 處理函數中使用。
補充幾點:
1.WM_Paint 是一個被動訊息,不能通過普通的方法簡單的 sendmessage WM_paint 了事
這是不行的;但通過訊息由程式員引發不是不可能;通過幾個特殊的常數可以做到,不過要到delphi下找
2.sendmessage 可以將訊息發送到訊息佇列;但windows會自動判斷是否存在無效的畫圖地區;
如果存在無效的畫圖地區,則可能會重畫,反之則棄用該訊息.
3.可以使用 InvalidateRect 等幾個APi將螢幕上任意一個個矩形地區設定為無效地區,在UpdateWindow後調用後,windows會自動尋找是否存在無效,並重畫,該矩形地區;
簡單那說一下吧:
我們知道,繪製視窗的內容,通常是在 WM_PAINT
訊息的處理中。這個訊息會由各種各樣的情況觸發,例如,某個覆蓋在自己之上的視窗關閉了,等等。不過,出於效率考慮,WM_PAINT
訊息的產生並不意味著整個視窗都需要重新繪製,考慮剛才說的例子,原來有個視窗 B 覆蓋了視窗 A 的左上方,則視窗 B 關閉後,視窗 A
就會收到一條 WM_PAINT
訊息,但是需要繪製的其實只是左上方的那一部分(為了討論簡單,不考慮視窗自身的內容一直在變化的情況,如動畫視窗之類的)。這裡的左上方,正是一個“無
效地區”。由此,我們可以解釋一下 InvalidateRect
函數了,這個函數的作用非常簡單,就是把某個矩形加入到視窗的無效地區列表中,告訴視窗在處理下一條 WM_PAINT
訊息時,這個地區是需要重新繪製的。順便說一下,InvalidateRect 有個兄弟函數,不止是簡單地處理一個矩形,而是處理一個地區,那就是
InvalidateRgn 函數,道理是一樣的。
我們還知道,在大多數情況下,WM_PAINT
訊息並不是我們程式自己發出的,而是視窗管理器(也可看做就是 Windows
系統本身)發出的。不過,系統還是給了我們自己發送這個訊息的途徑,那就是 UpdateWindow 函數。我們常常會看到,經典的 SDK 風格的
Hello World 程式裡,主視窗建立之後,會調用 ShowWindow 函數把它顯示出來,跟跟著立刻有一個 UpdateWindow
的調用,就是為了主視窗顯示之後能立即進行第一次重新整理。不過這是在 32 位 Windows 之前的情況,現在視窗管理器已經比原來更聰明,所以
UpdateWindow 函數的作用已經相當弱化了,不再被頻繁使用。當然,UpdateWindow
還會做一些自己的判斷,僅當無效地區存在時才會真正發出 WM_PIANT 訊息。
至於 RedrawWindow
函數,其實現非常複雜,不過我們可以不去理會無關宏旨的部分。大概說來,如果你瞭解 PostMessage 和 SendMessage
的區別,那就會比較好理解。一般的 WM_PAINT 訊息都是被投遞到了訊息佇列裡,等待 GetMessage 將之取出後進行處理。而
RedrawWindow
會強行直接調用視窗的回呼函數就地處理。說白了,就是非同步和同步的區別。這個函數允許指定要重繪的部分(矩形或者地區),因此,也常被人說這個函數兼具前
面兩個函數的功效。
InvalidateRect是把某個地區標記為無效,之後系統會不斷向視窗Post WM_PAINT訊息,直到該地區被標記為有效為止。
UpdateWindow是立即響應WM_PAINT訊息,相當於直接調用視窗函數。
RedrawWindow相當於把前兩者組合起來,並有很多參數可供選擇,可以更靈活地進行重繪。