【转】窗口之间的主从关系与Z-Order
原文鏈接:http://www.cnblogs.com/dhatbj/p/3288152.html
說(shuō)明:這是本人2008年寫的一篇舊文,從未公開發(fā)表過(guò)。其中除了一小段描述Window Mobile平臺(tái)的內(nèi)容已過(guò)時(shí),大部分內(nèi)容對(duì)于從事Win32開發(fā)的程序員還是很有參考價(jià)值的,也是對(duì)自己從事Windows開發(fā)工作的一個(gè)總結(jié),歡迎指正。轉(zhuǎn)載請(qǐng)注明:http://www.cnblogs.com/dhatbj/原創(chuàng)。
?
范圍(Scope)
?????? 討論Windows操作系統(tǒng)中窗口之間的關(guān)系(relationship between windows),除特別指明的部分之外,適用于各版本桌面平臺(tái)和Windows Mobile平臺(tái)。
?
概述(Summary)
? ? ? ?窗口(Window)是Windows操作系統(tǒng)中用來(lái)顯示信息和接受用戶輸入的基本單元(Block)。負(fù)責(zé)管理窗口相關(guān)功能的操作系統(tǒng)部件被稱為窗口管理器(Window Manager)。Windows操作系統(tǒng)初始化時(shí)會(huì)生成一個(gè)窗口,叫做桌面窗口(Desktop Window),調(diào)用GetDesktopWindow函數(shù)可獲得它的句柄。桌面窗口會(huì)覆蓋整個(gè)屏幕,所有其它窗口都在其之上顯示。
?
窗口類型(Window Type)
? ? ? ?Windows中有3種類型的窗口:層疊窗口(Overlapped Window)、彈出窗口(Popup Window)、子窗口(Child Window),在生成窗口(調(diào)用CreateWindowEx)時(shí)分別以WS_OVERLAPPED、WS_POPUP或WS_CHILD窗口風(fēng)格(Style)來(lái)表示。層疊窗口是窗口的缺省類型,如果不指定任何窗口類型則生成的是層疊窗口。
? ? ? ?彈出窗口通常用于對(duì)話框。它隱含帶有WS_CLIPSIBLINGS窗口風(fēng)格(后面會(huì)詳細(xì)描述)。
? ? ? ?層疊窗口通常被用作應(yīng)用程序的主窗口,也隱含帶有WS_CLIPSIBLINGS窗口風(fēng)格。在桌面平臺(tái)上,層疊窗口還隱含帶有WS_CAPTION窗口風(fēng)格。帶有標(biāo)題欄的窗口都隱含帶有邊框(Border),至于原因嘛,想像一下“光禿禿的標(biāo)題欄”+“沒有邊框的窗口”會(huì)是個(gè)什么樣子。在Mobile平臺(tái)上,層疊窗口與彈出窗口的界限已經(jīng)很模糊了。
層疊窗口和彈出窗口統(tǒng)稱為頂層窗口(top-level windows)。
? ? ? 剩下的一類是子窗口,例如常見的Button,Edit Box,List Box等窗口控件。
?
WS_OVERLAPPED的值
? ? ? ?在桌面平臺(tái)上,WS_OVERLAPPED定義為0,這與窗口的缺省類型為層疊窗口的事實(shí)相符;而在Windows Mobile平臺(tái)上,WS_OVERLAPPED被定義為WS_BORDER | WS_CAPTION,這是怎么回事呢?我想這是微軟為了保持桌面平臺(tái)與Mobile平臺(tái)軟件的外觀兼容性而使用的一個(gè)技巧,因?yàn)镸obile平臺(tái)上的層疊窗口缺省是不帶WS_CAPTION風(fēng)格的,微軟的意思應(yīng)該是:(WS_OVERLAPPED in PC)=(WS_OVERLAPPED in Mobile)| WS_BORDER | WS_CAPTION,在字面上就會(huì)寫成:
#define WS_OVERLAPPED???????? WS_BORDER | WS_CAPTION
這樣定義可以方便桌面平臺(tái)上的代碼移植到Mobile平臺(tái)。但開發(fā)原生的Windows Mobile代碼時(shí)就要注意了,由于Mobile上的典型窗口是不帶標(biāo)題欄的(Mobile界面最上方的Title Bar并不屬于窗口的一部分),我們?cè)谏蓪盈B窗口時(shí)不應(yīng)使用WS_OVERLAPPED標(biāo)志(這一標(biāo)志的實(shí)際意義是:PC style overlapped window)— 不指定任何窗口類型就好。
?
窗口層次結(jié)構(gòu)(Window Hierarchy)
? ? ? ?窗口管理器以一個(gè)樹狀結(jié)構(gòu)組織和管理系統(tǒng)內(nèi)所有窗口,如圖:
?
圖1.樹狀的窗口組織圖
? ? ? ?樹形結(jié)構(gòu)的根是桌面窗口,其下屬第一層窗口是頂層窗口(層疊窗口+彈出窗口,見上一小節(jié))。頂層窗口之下的所有層里只包含子窗口。從桌面窗口出發(fā),通過(guò)一系列相關(guān)API函數(shù)的調(diào)用,可以遍歷系統(tǒng)中的所有窗口。
?
窗口的從屬關(guān)系?
?????? 包括父/子(parent-child)關(guān)系、擁有/被擁有(owner-owned)關(guān)系及兄弟(siblings)關(guān)系。?
父/子(parent-child)關(guān)系?
類型為Child Window的窗口必須有一個(gè)父窗口,父窗口的類型可以是3種類型中的任意一種。子窗口的位置坐標(biāo)都是相對(duì)于父窗口客戶區(qū)的左上角(upper-left corner)計(jì)算的。子窗口會(huì)把它的notify消息發(fā)送到父窗口。父/子關(guān)系對(duì)窗口可見性的影響為:子窗口只能顯示在它的父窗口的客戶區(qū)中,超出父窗口客戶區(qū)的部分將被裁減掉;父窗口被隱藏時(shí),它的所有子窗口也被隱藏;最小化父窗口不影響子窗口的可見狀態(tài),子窗口會(huì)隨著父窗口被最小化,但是它的WS_VISIBLE屬性不變。父窗口被銷毀的時(shí)候,它的所有子窗口都會(huì)被銷毀。
窗口生成時(shí)通過(guò)CreateWindowEx函數(shù)的hWndParent參數(shù)可指定其父窗口,或在窗口產(chǎn)生后通過(guò)SetParent函數(shù)更改。通過(guò)GetParent函數(shù)可獲取父窗口句柄。父窗口要查詢其子窗口可使用GetWindow函數(shù)(指定GW_CHILD標(biāo)志),該函數(shù)返回第一個(gè)子窗口的句柄。
桌面窗口與所有頂層窗口也是父/子關(guān)系,但又有其特殊性:對(duì)桌面窗口調(diào)用GetWindow函數(shù)(指定GW_CHILD標(biāo)志)可得到第一個(gè)頂層窗口的句柄,但對(duì)某一個(gè)頂層窗口調(diào)用GetParent卻會(huì)返回NULL,不會(huì)返回桌面窗口的句柄。由此可見,父/子關(guān)系中的“子”一方未必一定是子窗口(Child Window類型)。
擁有/被擁有(owner-owned)關(guān)系
頂層窗口之間可以存在owner-owned關(guān)系。owner-owned關(guān)系對(duì)窗口可見性的影響為:owned窗口永遠(yuǎn)顯示在owner窗口的前面;當(dāng)owner窗口最小化的時(shí)候,它所擁有的窗口都會(huì)被隱藏;隱藏owner窗口不影響它所擁有的窗口的可見狀態(tài)。根據(jù)最后這一點(diǎn),如果窗口A 擁有窗口B,窗口B擁有窗口C,則當(dāng)窗口A最小化的時(shí)候,窗口B被隱藏,但是窗口 C還是可見的。當(dāng)owner窗口被銷毀的時(shí)候,它所擁有的窗口都會(huì)被銷毀。
Owner窗口在owned窗口生成時(shí)通過(guò)CreateWindowEx函數(shù)的hWndParent參數(shù)指定,如果該參數(shù)傳入的是一個(gè)子窗口,窗口管理器將找到容納該子窗口的頂層窗口,以該頂層窗口作為owner窗口。Owner窗口一旦指定不能更改。通過(guò)GetWindow函數(shù)(指定GW_OWNER標(biāo)志)可獲取owner窗口的句柄(如果存在的話)。
兄弟(siblings)關(guān)系?
同一個(gè)父窗口的所有直屬子窗口之間是兄弟關(guān)系,也就是相互平等,沒有主從之分。窗口管理器用鏈表(linked list)來(lái)管理每個(gè)父窗口的直屬子窗口(見圖1),這個(gè)鏈表叫子窗口鏈(child window list)。
調(diào)用GetWindow函數(shù)時(shí)使用GW_HWNDPREV或GW_HWNDNEXT標(biāo)志可訪問(wèn)子窗口鏈中的前一個(gè)或后一個(gè)窗口;使用GW_HWNDFIRST或GW_HWNDLAST標(biāo)志可訪問(wèn)子窗口鏈中的第一個(gè)或最后一個(gè)窗口。
?
Z-Order?
?????? 窗口在子窗口鏈中的先后順序也就是窗口在屏幕上顯示時(shí)的前后順序,在子窗口鏈里位置越靠前的窗口顯示時(shí)也越靠前,這個(gè)前后順序就是Z-Order。Z-Order在前的頂層窗口會(huì)遮擋Z-Order在后的頂層窗口;屏幕上的一塊區(qū)域需要刷新(Update)時(shí),同一個(gè)子窗口鏈中Z-Order在前的窗口先刷新,Z-Order在后的窗口后刷新。有父/子關(guān)系的窗口是父窗口先刷新,子窗口后刷新,?
頂層窗口生成時(shí),窗口管理器會(huì)把它加到(桌面窗口的)子窗口鏈的最前面,也就是Z-Order的最前面,使整個(gè)窗口都可見。子窗口的Z-Order要高于它的父窗口,因此會(huì)顯示在父窗口前面,但任何一個(gè)子窗口的Z-Order都不會(huì)超過(guò)其父窗口的Z-Order更靠前的兄弟窗口。改變窗口的Z-Order可使用SetWindowPos函數(shù)。?
?????? 子窗口生成時(shí),與頂層窗口的情況有所不同,窗口管理器會(huì)把它加到父窗口的子窗口鏈的最后面。這似乎是反直覺的,為什么會(huì)這樣呢?窗口管理器這樣做是有原因的,其主要目的是讓后生成的窗口能顯示在前面(兄弟窗口間有重疊的情況下),并且子窗口間的Tab-Order與窗口的生成順序相同,這樣的效果才是符合直覺的。子窗口大多數(shù)情況下都共用其父窗口的顯示DC(Device Context),所以在刷新時(shí)是可以在其兄弟窗口的客戶區(qū)上繪畫(draw)的,這就造成了Z-Order在后的子窗口因?yàn)樗⑿马樞蛟诤?#xff0c;繪畫能覆蓋Z-Order在前的窗口,顯示效果反而在前的現(xiàn)象,如下圖所示:
?
圖2.子窗口相互覆蓋示意圖(無(wú)WS_CLIPSIBLINGS風(fēng)格)?
如果想使Z-Order在前的子窗口顯示時(shí)也在前(覆蓋Z-Order在后的子窗口),需要使用WS_CLIPSIBLINGS窗口風(fēng)格(后面詳述)。
?
Topmost窗口?
?????? 也就是具有WS_EX_TOPMOST擴(kuò)展風(fēng)格的窗口,僅適用于頂層窗口,子窗口無(wú)法使用。根據(jù)有無(wú)WS_EX_TOPMOST風(fēng)格將所有頂層窗口分成了兩個(gè)級(jí)別。有WS_EX_TOPMOST風(fēng)格的頂層窗口Z-Order在前,普通頂層窗口Z-Order在后。普通頂層窗口要成為Topmost窗口可以調(diào)用SetWindowPos函數(shù),指定hwndInsertAfter參數(shù)為HWND_TOPMOST,指定hwndInsertAfter參數(shù)為HWND_NOTOPMOST調(diào)用SetWindowPos則使Topmost窗口成為普通頂層窗口。
??
Tab-Order?
?????? 兄弟窗口間的Tab-Order實(shí)際上是由Z-Order決定的(與Z-Order的順序相同),因此如果想在程序運(yùn)行過(guò)程中動(dòng)態(tài)改變Tab-Order,可通過(guò)改變兄弟窗口間的Z-Order(使用SetWindowPos)來(lái)實(shí)現(xiàn)。反過(guò)來(lái),在VC++的對(duì)話框編輯器里,如果控件的位置有重疊,通過(guò)調(diào)整Tab-Order也能調(diào)整控件間的遮擋關(guān)系。
?
WS_CLIPCHILDREN和WS_CLIPSIBLINGS
???????在窗口之間有重疊的情況下,這兩個(gè)窗口風(fēng)格會(huì)影響窗口刷新區(qū)域(Update Region)的計(jì)算方法。“Clip”一詞是指從刷新區(qū)域中“剪切掉”被Z-Order在前的子窗口或兄弟窗口覆蓋的區(qū)域,如圖3所示:
?
圖3. 窗口之間有重疊時(shí)的刷新區(qū)域
窗口B與窗口A是兄弟關(guān)系,B的Z-Order高于A。整個(gè)屏幕刷新時(shí),B先刷新,A后刷新。如果A沒有WS_CLIPSIBLINGS風(fēng)格,則A的整個(gè)客戶區(qū)都被刷新,A將重新刷新兩窗口相交的C區(qū)域,造成A覆蓋B的效果;但如果A有WS_CLIPSIBLINGS風(fēng)格,則只有圖中綠色的部分會(huì)被刷新,A就不會(huì)覆蓋B。簡(jiǎn)單的說(shuō),WS_CLIPSIBLINGS可以控制Z-Order在前的窗口顯示在前還是Z-Order在后的窗口顯示在前。由于窗口管理器強(qiáng)制頂層窗口都有WS_CLIPSIBLINGS風(fēng)格,所以頂層窗口總是Z-Order在前的顯示在前(能覆蓋Z-Order在后的窗口)。子窗口缺省不帶WS_CLIPSIBLINGS風(fēng)格,所以是Z-Order在后的窗口能覆蓋Z-Order在前的兄弟窗口(如圖2)。要想使子窗口的遮擋效果與Z-Order一致,可以把相關(guān)的子窗口都加上WS_CLIPSIBLINGS風(fēng)格,之后的外觀如下圖:
?
圖4.子窗口相互覆蓋示意圖(有WS_CLIPSIBLINGS風(fēng)格)
?
由于父窗口總是先于子窗口執(zhí)行刷新動(dòng)作,所以無(wú)論是否使用WS_CLIPCHILDREN風(fēng)格,父窗口都不會(huì)遮蓋子窗口。WS_CLIPCHILDREN的作用主要是避免窗口重疊區(qū)域的重復(fù)刷新,有可能加快窗口顯示速度以及減輕刷新時(shí)的“閃爍”問(wèn)題。
??
窗口生成時(shí)使用缺省位置和大小?
???????CreateWindowEx生成的窗口如果是層疊窗口,程序可以不指定窗口的初始位置和大小,而是由窗口管理器決定。
?????? 要讓窗口管理器設(shè)置窗口的初始位置,需要使用一個(gè)特殊值CW_USEDEFAULT作為CreateWindowEx的參數(shù)“x”的值,參數(shù)“y”將被忽略,不起作用。對(duì)于桌面平臺(tái),如果窗口風(fēng)格包含WS_VISIBLE,CreateWindowEx 函數(shù)內(nèi)部將把“y”的值作為第二個(gè)參數(shù)(nCmdShow)傳遞給ShowWindow函數(shù),這時(shí)的“y”作為一個(gè)隱藏參數(shù)使用。?
?????? 要讓窗口管理器設(shè)置窗口的初始大小,需要使用CW_USEDEFAULT作為CreateWindowEx的參數(shù)nWidth的值,參數(shù)nHeight將被忽略。?
?????? CW_USEDEFAULT只能用于層疊窗口,對(duì)于彈出窗口或子窗口,如果給“x”參數(shù)傳遞了CW_USEDEFAULT,則窗口位置不確定;如果給nWidth參數(shù)傳遞了CW_USEDEFAULT,則窗口大小不確定。
?
參考資料
1.??? Win32 Window Hierarchy and Styles,Kyle Marsh,1993
http://msdn.microsoft.com/en-us/library/ms997562.aspx
轉(zhuǎn)載于:https://www.cnblogs.com/vcpp123/p/5777917.html
與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的【转】窗口之间的主从关系与Z-Order的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 2016第34周二
- 下一篇: 从Cell的视图推出一个新的界面