Windows的窗口刷新機制相關

Windows的窗口刷新機制

1、Windows的窗口刷新管理

窗口句柄(HWND)都是由操作系統內核管理的,系統內部有一個z-order序列,記錄着當前窗口從屏幕底部(假象的從屏幕到眼睛的方向),到屏幕最高層的一個窗口句柄的排序,這個排序不關注父窗口還是子窗口。

當任意一個窗口接收到WM_PAINT消息產生重繪,更新區域繪製完成以後,就搜索它的前面的一個窗口,如果此窗口的範圍和更新區域有交集,就向這個窗口發送WM_PAINT消息,周而復始,直到執行到頂層窗口。纔算完成。

1.1 父子窗口間的刷新管理

對於一個對話框(主窗口)來說,理論上其所有子窗口都在他的前面——也就是更靠近眼睛的位置),當主窗口接收WM_PAINT繪製完成後,會引起更新區域上所有子窗口的重繪(所有子窗口也是自底向上排序的)。

子窗口是具有WS_CHILD或者WS_CHILDWINDOW樣式的窗口。和一般窗口一樣,子窗口通過WM_PAINT來繪圖。子窗口也維護一個更新區域,應用程序和系統都可以通過設置該更新區域無效來產生WM_PAINT消息。

子窗口的更新和顯示區域受到父窗口的影響,其他樣式的窗口則不會。系統常常設置父窗口的更新區域的同時設置子窗口的更新區域,使父窗口收到WM_PAINT消息的同時子窗口也能收到WM_PAINT消息。系統把子窗口的位置限制在父窗口的client區域,超出這個區域就會被裁減掉。

無論何時,只要父窗口的更新區域包含了子窗口的一部分,系統就會爲子窗口設置更新區域。此時,系統先向父窗口發送WM_PAINT消息,然後向子窗口發送消息讓子窗口可以恢復被父窗口覆蓋的內容。

但是如果只有子窗口設置了更新區域,系統不會給父窗口也設置。在無效化子窗口時,系統不會給父窗口發WM_PAINT(因爲被覆蓋住了,根本沒有必要)。同樣的,如果使被子窗口覆蓋住的父窗口的部分區域無效化,系統也不會給父窗口發送WM_PAINT的。在這種情況下,無論子窗口還是父窗口都不會收到WM_PAINT消息。

父子窗口間的刷新,還受父窗口是否設置了WS_CLIPCHILDREN樣式影響。

父窗口如果設置了WS_CLIPCHILDREN這個樣式的話,當父窗口的更新區域被設置的時候,子窗口的更新區域不會被設置。父窗口作用在子窗口下面的任何繪圖全部被裁減掉。

因此,當父窗口無效且收到WM_PAINT消息時,如果沒有設置WS_CLIPCHILDREN樣式,則所有子窗口都會在父窗口處理WM_PAINT之後收到WM_PAINT重繪消息;如果父窗口帶有WS_CLIPCHILDREN樣式,則不會引起子窗口重繪。

1.2 兄弟窗口間的刷新管理

如果兩個窗口重疊,則兩個窗口都會收到WM_PAINT消息。他們收到WM_PAINT消息的順序與z-index相反,即最上面的(z-order最高)的收到WM_PAINT消息最晚。

應用程序可以爲窗口設置WS_CLIPSIBLING樣式來避免兄弟窗口的繪製重疊。設置了這個,高z-order的窗口部分就會被上面的窗口裁減掉了,此部分被覆蓋的區域就不會被刷新了。

結論

1)WS_CLIPCHILDREN樣式主要是用於父窗口,也就是說當在父窗口繪製的時候,父窗口上還有一個子窗口,那麼如果設置了這個樣式的話,子窗口所在區域父窗口就不負責繪製;

2)所有的overlapped和popup風格的窗口,都有WS_CLIPSIBLINGS屬性。也就是說這類風格的窗口,你是去不掉WS_CLIPSIBLINGS樣式的,這樣就是它不會在其與兄弟窗口重疊的區域繪圖;

3)WS_CLIPSIBLINGS樣式只適用於同級窗口,實際上還需要和控件的疊放順序(z order)配合使用才能看出明顯的效果。

2、OnEraseBkGnd與OnPaint

2.1 常見的問題

在OnEraseBkGnd中,如果你不調用原來缺省的OnEraseBkGnd只是重畫背景則不會有閃爍。而在OnPaint裏面,由於它隱含的調用了OnEraseBkGnd,而你又沒有處理OnEraseBkGnd函數,這時就和窗口缺省的背景畫刷相關了。缺省的OnEraseBkGnd操作使用窗口的缺省背景畫刷刷新背景(一般情況下是白刷),而隨後你又自己重畫背景造成屏幕閃動。

然而OnEraseBkGnd不是每次都會被調用的。如果你調用Invalidate的時候參數爲TRUE,那麼在OnPaint裏面隱含調用BeginPaint的時候就會產生WM_ERASEBKGND消息,如果參數是FALSE,則不會重刷背景。

以上問題解決方法有三個半:

1)用OnEraseBkGnd實現,不要調用原來的OnEraseBkGnd函數。

2)用OnPaint實現,同時重載OnEraseBkGn,其中直接返回TRUE。

3)用OnPaint實現,創建窗口時設置背景刷爲空。

4)用OnPaint實現,但是要求刷新時用Invalidate(FALSE)這樣的函數。(不過這種情況下,窗口覆蓋等造成的刷新還是要閃一下,所以不是徹底的解決方法)。

2.2 關於OnEraseBkGnd的返回值

An application should return nonzero inresponse to WM_ERASEBKGND if it processes the message and erases thebackground; this indicates that no further erasing is required. If theapplication returns zero, the window will remain marked for erasing.(Typically, this indicates that the fErase member of the PAINTSTRUCT structurewill be TRUE.)

2.3 OnPaint的執行

Windows爲每個視窗保存一個「繪圖信息結構」,這就是PAINTSTRUCT,定義如下:

typedef struct tagPAINTSTRUCT

{

HDC      hdc ;

        BOOL     fErase ;

RECT     rcPaint ;

BOOL     fRestore ;

BOOL     fIncUpdate ;

BYTE     rgbReserved[32] ;

} PAINTSTRUCT ;

在程序隱式調用BeginPaint時,Windows會適當填入該結構的各值。應用程序只使用前三個值,其他值由Windows內部使用。Hdc是設備DC句柄。在舊版本的Windows中,BeginPaint的傳回值也曾是該值。在大多數情況下,fErase被標誌爲FALSE(0),這意味著Windows已經擦除了無效矩形的背景。這最早在BeginPaint函數中發生(如果要在窗口消息處理中自己定義一些背景擦除行爲,可以自行處理WM_ERASEBKGND消息)。Windows使用WNDCLASS結構的hbrBackground指定的畫刷來擦除背景,這個WNDCLASS結構程序在註冊窗口類型時使用的。許多Windows程序使用白色畫刷。以下敘述設定窗口類型結構中畫刷的值:

wndclass.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH) ;

不過,如果程序通過調用Windows函數InvalidateRect使顯示區域中的矩形失效,則該函數的最後一個參數會指定是否擦除背景。如果這個參數爲FALSE(即0),則Windows將不會擦除背景,並且在調用完BeginPaint後PAINTSTRUCT結構的fErase將爲TRUE(非零)。

PAINTSTRUCT結構的rcPaint是RECT型態的結構。它定義了無效矩形的邊界,這些值均以像素爲單位,並相對於客戶區域的左上角。無效矩形是指應該重畫的區域。

PAINTSTRUCT結構的rcPaint不僅是無效矩形,它還是一個“裁剪”矩形。這意味着Windows將繪圖操作限制在該裁剪矩形內(更確切地說,如果無效矩形區域不爲矩形,則Windows將繪圖操作限制在這個區域內)。

在處理WM_PAINT消息時,爲了在更新的矩形外繪圖,可以使用如下函數:

InvalidateRect (hwnd, NULL, TRUE) ;

該函數在BeginPaint調用之前進行,它使整個顯示區域變爲無效,並擦除背景。但是,如果最後一個參數等於FALSE,則不擦除背景,原有的東西將保留在原處。通常這是Windows程序在無論何時收到WM_PAINT訊息而不考慮rcPaint結構的情況下簡單地重畫整個顯示區域最方便的方法。

3、WM_PAINT消息

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

The WM_PAINT message is generated by thesystem and should not be sent by an application.The system sends this messagewhen 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消息了。

大多數的時候應用也需要能夠主動引發窗口中的繪製操 作,比如當窗口顯示的數據改變的時候,這一般是通過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是否爲空等。

4、BeginPaint

BeginPaint sets the update region of awindow to NULL. This clears the region, preventing it fromgenerating subsequentWM_PAINT messages. If an application processes a WM_PAINT message but does notcall BeginPaint or otherwise clear the update region, the application continuesto 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_PAINTmessage.

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

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

BeginPaint只能在WM_PAINT處理函數中使用,並且在調用了BeginPaint函數後,必須調用EndPaint函數,他們可是一對的。

5、重繪函數

5.1 InvalidateRect / InvalidateRgn

The InvalidateRect function adds a rectangleto the specified window's update region. The update region represents theportion of the window's client area that must be redrawn.

The invalidated areas accumulate in theupdate region until the region is processed when the next WM_PAINT message occurs or until the region isvalidated by using the ValidateRect or ValidateRgn function.

The system sends a WM_PAINT message to awindow whenever its update region is not empty and there are no other messagesin the application queue for that window.

If the bErase parameter is TRUE for any partof the update region, the background is erased in the entire region, not justin the specified part.

InvalidateRect和InvalidateRgn把指定的區域加到窗口的Update Region中,當應用的消息隊列沒有其他消息時,如果窗口的Update Region不爲空時,系統就會自動產生WM_PAINT消息。

如果指定bErase爲TRUE,整個的背景都將會被擦除。

5.2 UpdateWindow

The UpdateWindow function updates the clientarea of the specified window by sending a WM_PAINT message to the window if thewindow's update region is not empty. The function sends a WM_PAINT messagedirectly to the window procedure of the specified window, bypassing theapplication queue. If the update region is empty, no message is sent.

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

5.3 RedrawWindow

RedrawWindow相當於先調用InvalidateRect,緊接着又調用UpdateWindow,此外RedrawWindow還提供了一些前兩者沒法做到的功能,如是否重畫非客戶區和背景,是否總是發送 WM_PAINT消息而不管Update Region是否爲空等。

其flags定義如下:

Flag(無效化標記)

描述

RDW_ERASE

當窗口重繪時將收到WM_ERASEBKGND消息。必須與RDW_INVALIDATE同時使用,否則無效。

RDW_FRAME

與無效區域有交集的非客戶區域將收到WM_NCPAINT消息,必須與RDW_INVALIDATE同時使用,否則無效。

在RedrawWindow處理過程中WM_NCPAINT消息不會被髮送,除非指定了RDW_UPDATENOW或RDW_EARSENOW

RDW_INTERNALPAINT

Post一條WM_PAINT消息,不管Update Region是否爲空

RDW_INVALIDATE

無效化參數指定的區域

Flag(有效化標記)

描述

RDW_NOERASE

阻止即將發生的WM_ERASEBKGND消息

RDW_NOFRAME

阻止即將發生的WM_NCPAINT消息,使用它要特別注意,可能會使窗口繪製不正確。

它必須與RDW_VALIDATE同時使用,

通常和RDW_NOCHILDREN一起使用。

RDW_NOINTERNALPAINT

阻止即將發生的NULL無效區域的WM_PAINT消息

RDW_VALIDATE

將參數的指定的區域有效化

Flag(刷新時機標記)

描述

RDW_EARSENOW

如果必要,將在RedrawWindow返回前使被影響的窗口(由RDW_ALLCHILDREN和RDW_NOCHILDREN指定)收到WM_NCPAINT和WM_ERASEBKGND消息

RDW_UPDATENOW

如果必要,將在RedrawWindow返回前使被影響的窗口(由RDW_ALLCHILDREN和RDW_NOCHILDREN指定)收到WM_NCPAINT、WM_ERASEBKGND和WM_PAINT消息

Flag(影響標記)

描述

RDW_ALLCHILDREN

如果有子窗口,子窗口將被影響

RDW_NOCHILDREN

如果有子窗口,子窗口不收影響

默認情況下,如果不指定RDW_NOCHILDREN和RDW_ALLCHILDREN,受影響的窗口由WS_CLIPCHILDREN樣式決定,如果指定的窗口設置了該樣式子窗口不受影響,否則子窗口將會被遞歸式的影響直到遇到一個具有WS_CLIPCHILDREN樣式的窗口。

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