[轉]窗口之間的主從關係與Z-Order

[轉]窗口之間的主從關係與Z-Order

2014-6-19閱讀98 評論0

說明:這是本人2008年寫的一篇舊文,從未公開發表過。其中除了一小段描述Window Mobile平臺的內容已過時,大部分內容對於從事Win32開發的程序員還是很有參考價值的,也是對自己從事Windows開發工作的一個總結,歡迎指正。轉載請註明:http://www.cnblogs.com/dhatbj/原創。

 

範圍(Scope)

       討論Windows操作系統中窗口之間的關係(relationship between windows),除特別指明的部分之外,適用於各版本桌面平臺和Windows Mobile平臺。

 

概述(Summary)

       窗口(Window)是Windows操作系統中用來顯示信息和接受用戶輸入的基本單元(Block)。負責管理窗口相關功能的操作系統部件被稱爲窗口管理器(Window Manager)。Windows操作系統初始化時會生成一個窗口,叫做桌面窗口(Desktop Window),調用GetDesktopWindow函數可獲得它的句柄。桌面窗口會覆蓋整個屏幕,所有其它窗口都在其之上顯示。

 

窗口類型(Window Type)

       Windows中有3種類型的窗口:層疊窗口(Overlapped Window)、彈出窗口(Popup Window)、子窗口(Child Window),在生成窗口(調用CreateWindowEx)時分別以WS_OVERLAPPED、WS_POPUP或WS_CHILD窗口風格(Style)來表示。層疊窗口是窗口的缺省類型,如果不指定任何窗口類型則生成的是層疊窗口。

       彈出窗口通常用於對話框。它隱含帶有WS_CLIPSIBLINGS窗口風格(後面會詳細描述)。

       層疊窗口通常被用作應用程序的主窗口,也隱含帶有WS_CLIPSIBLINGS窗口風格。在桌面平臺上,層疊窗口還隱含帶有WS_CAPTION窗口風格。帶有標題欄的窗口都隱含帶有邊框(Border),至於原因嘛,想像一下“光禿禿的標題欄”+“沒有邊框的窗口”會是個什麼樣子。在Mobile平臺上,層疊窗口與彈出窗口的界限已經很模糊了。

  層疊窗口和彈出窗口統稱爲頂層窗口(top-level windows)。

      剩下的一類是子窗口,例如常見的Button,Edit Box,List Box等窗口控件。

 

WS_OVERLAPPED的值

       在桌面平臺上,WS_OVERLAPPED定義爲0,這與窗口的缺省類型爲層疊窗口的事實相符;而在Windows Mobile平臺上,WS_OVERLAPPED被定義爲WS_BORDER | WS_CAPTION,這是怎麼回事呢?我想這是微軟爲了保持桌面平臺與Mobile平臺軟件的外觀兼容性而使用的一個技巧,因爲Mobile平臺上的層疊窗口缺省是不帶WS_CAPTION風格的,微軟的意思應該是:(WS_OVERLAPPED in PC)=(WS_OVERLAPPED in Mobile)| WS_BORDER | WS_CAPTION,在字面上就會寫成:

#define WS_OVERLAPPED         WS_BORDER | WS_CAPTION

這樣定義可以方便桌面平臺上的代碼移植到Mobile平臺。但開發原生的Windows Mobile代碼時就要注意了,由於Mobile上的典型窗口是不帶標題欄的(Mobile界面最上方的Title Bar並不屬於窗口的一部分),我們在生成層疊窗口時不應使用WS_OVERLAPPED標誌(這一標誌的實際意義是:PC style overlapped window)— 不指定任何窗口類型就好。

 

窗口層次結構(Window Hierarchy)

       窗口管理器以一個樹狀結構組織和管理系統內所有窗口,如圖:

 

圖1.樹狀的窗口組織圖

       樹形結構的根是桌面窗口,其下屬第一層窗口是頂層窗口(層疊窗口+彈出窗口,見上一小節)。頂層窗口之下的所有層裏只包含子窗口。從桌面窗口出發,通過一系列相關API函數的調用,可以遍歷系統中的所有窗口。

 

窗口的從屬關係 

       包括父/子(parent-child)關係、擁有/被擁有(owner-owned)關係及兄弟(siblings)關係。 

  父/子(parent-child)關係 

類型爲Child Window的窗口必須有一個父窗口,父窗口的類型可以是3種類型中的任意一種。子窗口的位置座標都是相對於父窗口客戶區的左上角(upper-left corner)計算的。子窗口會把它的notify消息發送到父窗口。父/子關係對窗口可見性的影響爲:子窗口只能顯示在它的父窗口的客戶區中,超出父窗口客戶區的部分將被裁減掉;父窗口被隱藏時,它的所有子窗口也被隱藏;最小化父窗口不影響子窗口的可見狀態,子窗口會隨着父窗口被最小化,但是它的WS_VISIBLE屬性不變。父窗口被銷燬的時候,它的所有子窗口都會被銷燬。

窗口生成時通過CreateWindowEx函數的hWndParent參數可指定其父窗口,或在窗口產生後通過SetParent函數更改。通過GetParent函數可獲取父窗口句柄。父窗口要查詢其子窗口可使用GetWindow函數(指定GW_CHILD標誌),該函數返回第一個子窗口的句柄。

桌面窗口與所有頂層窗口也是父/子關係,但又有其特殊性:對桌面窗口調用GetWindow函數(指定GW_CHILD標誌)可得到第一個頂層窗口的句柄,但對某一個頂層窗口調用GetParent卻會返回NULL,不會返回桌面窗口的句柄。由此可見,父/子關係中的“子”一方未必一定是子窗口(Child Window類型)。

  擁有/被擁有(owner-owned)關係

頂層窗口之間可以存在owner-owned關係。owner-owned關係對窗口可見性的影響爲:owned窗口永遠顯示在owner窗口的前面;當owner窗口最小化的時候,它所擁有的窗口都會被隱藏;隱藏owner窗口不影響它所擁有的窗口的可見狀態。根據最後這一點,如果窗口A 擁有窗口B,窗口B擁有窗口C,則當窗口A最小化的時候,窗口B被隱藏,但是窗口 C還是可見的。當owner窗口被銷燬的時候,它所擁有的窗口都會被銷燬。

Owner窗口在owned窗口生成時通過CreateWindowEx函數的hWndParent參數指定,如果該參數傳入的是一個子窗口,窗口管理器將找到容納該子窗口的頂層窗口,以該頂層窗口作爲owner窗口。Owner窗口一旦指定不能更改。通過GetWindow函數(指定GW_OWNER標誌)可獲取owner窗口的句柄(如果存在的話)。

  兄弟(siblings)關係 

同一個父窗口的所有直屬子窗口之間是兄弟關係,也就是相互平等,沒有主從之分。窗口管理器用鏈表(linked list)來管理每個父窗口的直屬子窗口(見圖1),這個鏈表叫子窗口鏈(child window list)。

調用GetWindow函數時使用GW_HWNDPREV或GW_HWNDNEXT標誌可訪問子窗口鏈中的前一個或後一個窗口;使用GW_HWNDFIRST或GW_HWNDLAST標誌可訪問子窗口鏈中的第一個或最後一個窗口。

 

Z-Order 

       窗口在子窗口鏈中的先後順序也就是窗口在屏幕上顯示時的前後順序,在子窗口鏈裏位置越靠前的窗口顯示時也越靠前,這個前後順序就是Z-Order。Z-Order在前的頂層窗口會遮擋Z-Order在後的頂層窗口;屏幕上的一塊區域需要刷新(Update)時,同一個子窗口鏈中Z-Order在前的窗口先刷新,Z-Order在後的窗口後刷新。有父/子關係的窗口是父窗口先刷新,子窗口後刷新, 

  頂層窗口生成時,窗口管理器會把它加到(桌面窗口的)子窗口鏈的最前面,也就是Z-Order的最前面,使整個窗口都可見。子窗口的Z-Order要高於它的父窗口,因此會顯示在父窗口前面,但任何一個子窗口的Z-Order都不會超過其父窗口的Z-Order更靠前的兄弟窗口。改變窗口的Z-Order可使用SetWindowPos函數。 

       子窗口生成時,與頂層窗口的情況有所不同,窗口管理器會把它加到父窗口的子窗口鏈的最後面。這似乎是反直覺的,爲什麼會這樣呢?窗口管理器這樣做是有原因的,其主要目的是讓後生成的窗口能顯示在前面(兄弟窗口間有重疊的情況下),並且子窗口間的Tab-Order與窗口的生成順序相同,這樣的效果纔是符合直覺的。子窗口大多數情況下都共用其父窗口的顯示DC(Device Context),所以在刷新時是可以在其兄弟窗口的客戶區上繪畫(draw)的,這就造成了Z-Order在後的子窗口因爲刷新順序在後,繪畫能覆蓋Z-Order在前的窗口,顯示效果反而在前的現象,如下圖所示:

 

圖2.子窗口相互覆蓋示意圖(無WS_CLIPSIBLINGS風格) 

如果想使Z-Order在前的子窗口顯示時也在前(覆蓋Z-Order在後的子窗口),需要使用WS_CLIPSIBLINGS窗口風格(後面詳述)。

 

Topmost窗口 

       也就是具有WS_EX_TOPMOST擴展風格的窗口,僅適用於頂層窗口,子窗口無法使用。根據有無WS_EX_TOPMOST風格將所有頂層窗口分成了兩個級別。有WS_EX_TOPMOST風格的頂層窗口Z-Order在前,普通頂層窗口Z-Order在後。普通頂層窗口要成爲Topmost窗口可以調用SetWindowPos函數指定hwndInsertAfter參數爲HWND_TOPMOST,指定hwndInsertAfter參數爲HWND_NOTOPMOST調用SetWindowPos則使Topmost窗口成爲普通頂層窗口。

  

Tab-Order 

       兄弟窗口間的Tab-Order實際上是由Z-Order決定的(與Z-Order的順序相同),因此如果想在程序運行過程中動態改變Tab-Order,可通過改變兄弟窗口間的Z-Order(使用SetWindowPos)來實現。反過來,在VC++的對話框編輯器裏,如果控件的位置有重疊,通過調整Tab-Order也能調整控件間的遮擋關係。

 

WS_CLIPCHILDREN和WS_CLIPSIBLINGS

       在窗口之間有重疊的情況下,這兩個窗口風格會影響窗口刷新區域(Update Region)的計算方法。“Clip”一詞是指從刷新區域中“剪切掉”被Z-Order在前的子窗口或兄弟窗口覆蓋的區域,如圖3所示:

 

圖3. 窗口之間有重疊時的刷新區域

窗口B與窗口A是兄弟關係,B的Z-Order高於A。整個屏幕刷新時,B先刷新,A後刷新。如果A沒有WS_CLIPSIBLINGS風格,則A的整個客戶區都被刷新,A將重新刷新兩窗口相交的C區域,造成A覆蓋B的效果;但如果A有WS_CLIPSIBLINGS風格,則只有圖中綠色的部分會被刷新,A就不會覆蓋B。簡單的說,WS_CLIPSIBLINGS可以控制Z-Order在前的窗口顯示在前還是Z-Order在後的窗口顯示在前。由於窗口管理器強制頂層窗口都有WS_CLIPSIBLINGS風格,所以頂層窗口總是Z-Order在前的顯示在前(能覆蓋Z-Order在後的窗口)。子窗口缺省不帶WS_CLIPSIBLINGS風格,所以是Z-Order在後的窗口能覆蓋Z-Order在前的兄弟窗口(如圖2)。要想使子窗口的遮擋效果與Z-Order一致,可以把相關的子窗口都加上WS_CLIPSIBLINGS風格,之後的外觀如下圖:

 

圖4.子窗口相互覆蓋示意圖(有WS_CLIPSIBLINGS風格)

 

由於父窗口總是先於子窗口執行刷新動作,所以無論是否使用WS_CLIPCHILDREN風格,父窗口都不會遮蓋子窗口。WS_CLIPCHILDREN的作用主要是避免窗口重疊區域的重複刷新,有可能加快窗口顯示速度以及減輕刷新時的“閃爍”問題。

  

窗口生成時使用缺省位置和大小 

       CreateWindowEx生成的窗口如果是層疊窗口,程序可以不指定窗口的初始位置和大小,而是由窗口管理器決定。

       要讓窗口管理器設置窗口的初始位置,需要使用一個特殊值CW_USEDEFAULT作爲CreateWindowEx的參數“x”的值,參數“y”將被忽略,不起作用。對於桌面平臺,如果窗口風格包含WS_VISIBLE,CreateWindowEx 函數內部將把“y”的值作爲第二個參數(nCmdShow)傳遞給ShowWindow函數,這時的“y”作爲一個隱藏參數使用。 

       要讓窗口管理器設置窗口的初始大小,需要使用CW_USEDEFAULT作爲CreateWindowEx的參數nWidth的值,參數nHeight將被忽略。 

       CW_USEDEFAULT只能用於層疊窗口,對於彈出窗口或子窗口,如果給“x”參數傳遞了CW_USEDEFAULT,則窗口位置不確定;如果給nWidth參數傳遞了CW_USEDEFAULT,則窗口大小不確定。

 

參考資料

1.    Win32 Window Hierarchy and Styles,Kyle Marsh,1993

http://msdn.microsoft.com/en-us/library/ms997562.aspx

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