關於WM_PAINT消息

 
此文收集了網上的一些資料,譬如:CSDN的論壇等。。

  在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會在這裏返回繪圖信息結構,結構中包含了無效區域的位置和大小,繪圖信息結構的定義如下:
  1. typedef struct tagPAINTSTRUCT { // ps 
  2.     HDC  hdc; 
  3.     BOOL fErase; 
  4.     RECT rcPaint; 
  5.     BOOL fRestore; 
  6.     BOOL fIncUpdate; 
  7.     BYTE rgbReserved[32]; 
  8. } PAINTSTRUCT;
  1.      PAINTSTRUCT STRUCT
  2.        hdc DWORD ?
  3.        fErase DWORD ?
  4.        rcPaint RECT <>
  5.        fRestore DWORD ?
  6.        fIncUpdate DWORD ?
  7.        rgbReserved BYTE 32 dup(?)
  8.      PAINTSTRUCT ENDS
  其中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會自動查找是否存在無效,並重畫,該矩形區域;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章