[轉]Windows的視窗重新整理機制

來源:互聯網
上載者:User

標籤:frame   搜尋   傳回值   prevent   psi   class   object   rgb   api   

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與OnPaint2.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樣式的視窗。

[轉]Windows的視窗重新整理機制

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.