??????相信在Windows 下面編程的很多兄弟們都不是很清楚Windows 中窗口的層次關(guān)系是怎么樣的,這個(gè)東西很久已經(jīng)研究過(guò)一下,后來(lái)又忘記了,今天又一次遇到了這個(gè)問(wèn)題,所以便整理一下。下面就說(shuō)說(shuō)Windows 中桌面(Desktop)以及頂層窗口,以及子窗口之間的關(guān)系。
??????在Windows 的圖形界面下,最基本顯示信息的元素就是窗口,每一個(gè)Windows 窗口都管理著自己與其他窗口之間的關(guān)系和自身的一些信息,如:是否可見(jiàn),窗口的所有者,窗口的父/子關(guān)系等等信息,當(dāng)窗口創(chuàng)建、銷毀、顯示的時(shí)候,就會(huì)用到這些信息。
??????在每一個(gè)窗口實(shí)例中,有四個(gè)元素被窗口管理器用來(lái)建立窗口管理鏈表。

- Child : 指向窗口子窗口的句柄
- Parent:指向窗口父窗口的句柄
- Owner:指向窗口所有者的句柄
- Next: 指向下一個(gè)同屬窗口的句柄
??????眾所周知當(dāng)Windows初始化的時(shí)候,它創(chuàng)建桌面這個(gè)窗口,桌面覆蓋著整個(gè)窗口,窗口管理器用這個(gè)窗口作為窗口鏈表中第一個(gè)元素。因此桌面在窗口的層次關(guān)系中在最上層。
??????在窗口層次關(guān)系中,桌面窗口下一層窗口叫做頂層窗口,頂層窗口就是那些不是子窗口的窗口,頂層窗口不能夠有WS_CHILD屬性。窗口管理器是如何把桌面窗口和頂層窗口聯(lián)系起來(lái)的呢?窗口管理器把頂層窗口都組織到一個(gè)鏈表中,而這個(gè)鏈表的頭存儲(chǔ)的就是桌面窗口的子窗口句柄,每一個(gè)子窗口通過(guò)Next就可以找到鏈表中下一個(gè)窗口了。這個(gè)鏈表被稱為子窗口鏈表,在同一個(gè)子窗口鏈表中的窗口是互為同屬窗口,所有頂層窗口都是同屬窗口。窗口在子窗口鏈表中的次序,也表明了窗口距離距離桌面窗口的距離[依次減小,第一個(gè)最上面,第二個(gè)在第一個(gè)下面,最后一個(gè)離桌面最近,也就是Z次序依次減小,第一個(gè)Z次序最大最能被看見(jiàn)]。頂層窗口所形成的子窗口鏈表構(gòu)成了一個(gè)Z 軸,窗口管理器就是根據(jù)Z序列來(lái)覺(jué)得窗口的哪一部分是顯示的,哪一部分是被遮蓋的。
??????所有頂層窗口的父窗口都是指向桌面窗口的,這樣一來(lái)頂層窗口是桌面窗口的子窗口,所有頂層窗口構(gòu)成的鏈表是桌面窗口的子窗口鏈表。當(dāng)頂層窗口創(chuàng)建的時(shí)候,窗口管理器把頂層窗口放在Z軸的頂上,這樣使得整個(gè)窗口可見(jiàn),窗口管理器把窗口插入到桌面窗口子窗口鏈表的前面。WS_EX_TOPMOST這個(gè)屬性控制著窗口管理器創(chuàng)建頂層窗口,窗口管理器把沒(méi)有WS_EX_TOPMOST屬性的窗口放在具有WS_EX_TOPMOST屬性的窗口的后面,這樣就使得具有WS_EX_TOPMOST屬性的窗口一直顯示在前面。
??????桌面窗口是在窗口層次中的第一層,頂層窗口在窗口層次中的第二層,子窗口也就是那些創(chuàng)建的時(shí)候指定了WS_CHILD 屬性的窗口占據(jù)了窗口層次的其他層。窗口和子窗口之間的聯(lián)系,就像桌面窗口和頂層窗口之間的關(guān)系一樣。
??????子窗口顯示在其父窗口的客戶區(qū)域,所有同一個(gè)窗口的子窗口同樣建立一個(gè)Z軸【次序越大在越最上方】,這個(gè)和頂層窗口是類似的,頂層窗口也是顯示在其父窗口――桌面窗口的客戶區(qū)域。
16 位和32 位窗口系統(tǒng)的區(qū)別
??????窗口之間的父子關(guān)系、歸屬所有關(guān)系、以及根據(jù) Z軸來(lái)顯示的這些規(guī)則在16位和32位窗口系統(tǒng)中都是相同的。這樣可以是在兩種窗口系統(tǒng)中高度的兼容。兩種窗口系統(tǒng)的區(qū)別在于安全和多線程。
??????Windows NT 在原有的窗口層次關(guān)系中多增加了一層,每一個(gè)運(yùn)行著Windows NT的系統(tǒng)中都有一個(gè)Windows工作站對(duì)象,這個(gè)對(duì)象是安全對(duì)象的第一層,是所有用戶安全對(duì)象的繼承之源,每一個(gè)Windows工作站對(duì)象可以擁有一些桌面對(duì)象,每一個(gè)桌面都擁有上面描述的那樣的窗口關(guān)系。Windows Nt用了兩個(gè)桌面窗口對(duì)象,一個(gè)是用來(lái)處理登陸界面、屏蔽、鎖住工作站等,一個(gè)是我們登陸之后進(jìn)來(lái)操作的窗口了。J通常用戶是不能夠創(chuàng)建和刪除桌面的,不過(guò)那是通常,實(shí)際上在Windows下面也可以實(shí)現(xiàn)類似 Linux 中的多個(gè)桌面的效果,每一個(gè)桌面都是一個(gè)獨(dú)立的世界。
??????兩種窗口系統(tǒng)還有兩位一個(gè)區(qū)別,在16 位窗口系統(tǒng)中不支持多線程,所以應(yīng)用程序開(kāi)發(fā)者在創(chuàng)建窗口的時(shí)候不必考慮線程的問(wèn)題了。而在32位窗口系統(tǒng)中由于又支持了窗口的父子關(guān)系,歸屬與擁有關(guān)系,同一個(gè)窗口下面的所有線程都擁有相同的一個(gè)輸入隊(duì)列,應(yīng)用程序開(kāi)發(fā)者應(yīng)該明白輸入隊(duì)列是共享的,在同一個(gè)時(shí)刻只能有一個(gè)線程處理消息,其他的線程都在等待輸入隊(duì)列一直到GetMessage或者PeekMessage返回,而且必須注意的是父窗口和子窗口或者是歸屬與擁有窗口共用同一個(gè)線程。
??????在32 窗口系統(tǒng)中定義兩種新的窗口類型,前臺(tái)窗口和背景窗口,這兩種窗口沒(méi)有列到窗口的層次關(guān)系中,前臺(tái)窗口就是用戶當(dāng)前操作的窗口,其他的所有窗口都是背景窗口。 32位窗口系統(tǒng)中支持兩個(gè)函數(shù)來(lái)處理前臺(tái)窗口SetForegroundWindow和GetForegroundWindow。
操作窗口列表
下面是窗口列表操作的一些函數(shù):
? EnumChildWindows
使用這個(gè)函數(shù)得到一個(gè)窗口的所有子窗口,包括子窗口的子窗口。不過(guò)在列舉的過(guò)程中這個(gè)函數(shù)不能夠列出正在創(chuàng)建的或者銷毀的窗口。
? EnumThreadWindows
使用這個(gè)函數(shù)可以列出所有屬于這個(gè)線程的窗口。在這個(gè)函數(shù)調(diào)用之后創(chuàng)建的窗口是不能夠被列舉出來(lái)的。
? EnumWindows
使用這個(gè)函數(shù)列舉出所有頂層窗口,不能夠列舉出子窗口,要列出所有的頂層窗口,使用這個(gè)函數(shù)比GetWindow安全。使用GetWindow來(lái)列出所有的窗口,可能會(huì)導(dǎo)致程序無(wú)限循環(huán),因?yàn)樵谡{(diào)用GetWindow的過(guò)程中,可能一些窗口已經(jīng)銷毀了。EnumWindows不能夠列舉出調(diào)用這個(gè)函數(shù)之后創(chuàng)建的頂層窗口。
? FindWindow
可以使用這個(gè)函數(shù)通過(guò)類名或者使用窗口的標(biāo)題來(lái)找到頂層窗口,這個(gè)函數(shù)不能夠用來(lái)找子窗口,這個(gè)函數(shù)不區(qū)分參數(shù)的大小寫(xiě)。這個(gè)函數(shù)在Z軸中尋找窗口,找到了之后,就會(huì)返回。
? GetDesktopWindow
得到桌面窗口句柄
? GetNextWindow
使用這個(gè)函數(shù)得到這個(gè)窗口的同屬窗口,在16 位窗口系統(tǒng)中GetNextWindow 和GetWindow是兩個(gè)不同的函數(shù),在32位系統(tǒng)中這個(gè)函數(shù)是通過(guò)GetWindow來(lái)實(shí)現(xiàn)的。
? GetParent
如果一個(gè)窗口存在父窗口,那么可以通過(guò)這個(gè)函數(shù)得到窗口的父窗口,如果窗口是頂層窗口,則返回其所有者窗口句柄。
? GetThreadDesktop
這個(gè)函數(shù)用來(lái)得到指定線程的所屬的桌面窗口句柄,在win95和 win98下面由于不支持多桌面,每次調(diào)用該函數(shù)都返回同一個(gè)值。
? GetTopWindow
可以用這個(gè)函數(shù)來(lái)得到給定窗口的第一個(gè)子窗口的句柄,如果傳遞給函數(shù)的參數(shù)是NULL的話,那么這個(gè)函數(shù)將會(huì)返回最上面的頂層窗口。
? GetWindow
應(yīng)用程序可以調(diào)用這個(gè)函數(shù)來(lái)在窗口列表中導(dǎo)航,這個(gè)函數(shù)有兩個(gè)參數(shù),一個(gè)是窗口的句柄,另外是要得到的窗口句柄和這個(gè)窗口之間的關(guān)系。
· GW_HWNDNEXT:這個(gè)函數(shù)返回給定窗口的下一個(gè)同屬窗口
· GW_HWNDPREV:返回給定窗口的前一個(gè)同屬窗口
· GW_HWNDFIRST:返回給定窗口的第一個(gè)同屬窗口
· GW_HWNDLAST:返回給定窗口的最后一個(gè)同屬窗口
· GW_OWNER:返回給定窗口的所有者窗口句柄
· GW_CHILD:返回給定窗口的第一個(gè)子窗口句柄
? IsChild
這個(gè)函數(shù)有兩個(gè)參數(shù),兩個(gè)窗口句柄,判斷兩個(gè)窗口是否存在父子關(guān)系
窗口的屬性
??????當(dāng)應(yīng)用程序調(diào)用CreateWindow創(chuàng)建窗口的時(shí)候,我們必須為窗口指定屬性,下面簡(jiǎn)要的介紹一下窗口的屬性。
WS_OVERLAPPED
??????交迭屬性是頂層窗口的一種屬性,使用這種屬性創(chuàng)建的窗口,會(huì)被鏈接到桌面窗口的子窗口鏈表中,應(yīng)用程序通常使用這種屬性的窗口作為應(yīng)用程序的主窗口,具有交迭屬性的窗口通常具有有標(biāo)題欄,即使是WS_CAPTION 這個(gè)屬性沒(méi)有指定。具有交迭屬性的窗口通常都是有邊框的,具有交迭屬性的窗口可以擁有自己的頂層窗口,也可以所屬其他的頂層窗口,所有的這類窗口都具有WS_CLIPSIBLINGS 屬性,即使是沒(méi)有給窗口指定這個(gè)屬性。
WS_POPUP
??????彈出屬性也是應(yīng)用到頂層窗口的一種屬性,使用這種屬性創(chuàng)建的窗口會(huì)被鏈接到桌面窗口的子窗口鏈表中,應(yīng)用程序通常為對(duì)話框窗口設(shè)置這個(gè)屬性,彈出屬性和交迭屬性的主要區(qū)別在于具有彈出屬性的窗口不是一定要有標(biāo)題欄的,而具有交迭屬性的窗口則是一定要具有標(biāo)題欄,具有彈出屬性的窗口可以沒(méi)有邊框。和具有交迭屬性的窗口一樣,具有彈出屬性的窗口可以有自己的頂層所屬窗口,也可以所屬其他的頂層窗口。所有具有彈出屬性的窗口必須具有WS_CLIPSIBINGS 屬性,即使是用戶沒(méi)有指定這個(gè)屬性。具有彈出屬性的窗口在創(chuàng)建的時(shí)候,它的大小和位置不能夠使用CW_USEDEFAULT 值。
WS_CHILD
??????子窗口必須具有這個(gè)屬性,子窗口只能夠出現(xiàn)在父窗口的客戶區(qū)域,這是子窗口和具有交迭屬性的窗口以及彈出屬性的窗口的主要區(qū)別,創(chuàng)建子窗口的時(shí)候,位置和大小不能夠使用CW_USEDEFAULT 這個(gè)值,否則是不能夠創(chuàng)建窗口的。
WS_CAPTION
??????當(dāng)窗口被設(shè)置這個(gè)屬性的時(shí)候,窗口的最上頭會(huì)有標(biāo)題欄,應(yīng)用程序可以通過(guò)SetWindowText 這個(gè)函數(shù)來(lái)改變標(biāo)題欄的標(biāo)題,通常具有標(biāo)題欄的窗口還具有最大、最小、關(guān)閉按鈕,和系統(tǒng)菜單。如果一個(gè)窗口沒(méi)有標(biāo)題欄,那么Windows 是不會(huì)創(chuàng)建這些東西的,即使是用戶指定了這些屬性,系統(tǒng)菜單是依賴標(biāo)題欄窗口的存在而存在的,如果沒(méi)有標(biāo)題欄那么是一定不會(huì)有系統(tǒng)菜單的存在的。具有標(biāo)題欄的窗口通常具有單線的邊界具有可以改變窗口大小的屬性,通常具有標(biāo)題欄的窗口是不能具有對(duì)話框的邊界屬性的,除非為窗口設(shè)置WS_EX_DLGMODALFRAME 屬性。
WS_MINIMIZEBOX
??????當(dāng)為窗口設(shè)置這個(gè)屬性的時(shí)候,窗口的標(biāo)題欄上會(huì)有一個(gè)最小化的按鈕,其實(shí)對(duì)于Windows 來(lái)實(shí)現(xiàn)這個(gè)屬性的時(shí)候,只是在標(biāo)題欄上面放置了一個(gè)最小化的位圖,當(dāng)用戶點(diǎn)擊這個(gè)最小化位圖的時(shí)候,窗口最小化,如果最大化位圖最在,那么最小化位圖被放置在最大化位圖的左邊。沒(méi)有這個(gè)屬性的窗口是不能夠最小化的。
WS_MAXIMIZEBOX
??????當(dāng)為窗口設(shè)置這個(gè)屬性的時(shí)候,窗口的標(biāo)題欄的右上會(huì)被放置一個(gè)最大化的位圖,如果窗口設(shè)置了這個(gè)屬性,用戶可以點(diǎn)擊最大化的位圖或者是通過(guò)系統(tǒng)菜單來(lái)實(shí)現(xiàn)窗口的最大化,沒(méi)有這個(gè)屬性的窗口是不能夠被最大化的。
WS_SYSMENU
??????如果為窗口指定這個(gè)屬性,那么就會(huì)在窗口的左上角上放置系統(tǒng)菜單位圖,系統(tǒng)菜單為用戶提供了操作窗口的接口,通常系統(tǒng)菜單會(huì)有下面這些系統(tǒng)命令:
- 恢復(fù)最小化的窗口
- 使用鍵盤(pán)移動(dòng)窗口
- 使用鍵盤(pán)改變窗口的大小
- 最小化窗口
- 最大化窗口
- 關(guān)閉窗口
- 切換到其他的任務(wù)
??????如果一個(gè)窗口有系統(tǒng)菜單,用戶可以通過(guò)點(diǎn)擊系統(tǒng)菜單圖標(biāo)來(lái)調(diào)用系統(tǒng)菜單,或者通過(guò)Alt+ 空格的快捷鍵調(diào)出系統(tǒng)菜單,或者通過(guò)點(diǎn)擊任務(wù)欄上窗口的圖標(biāo)來(lái)調(diào)出系統(tǒng)菜單,如果一個(gè)窗口沒(méi)有系統(tǒng)菜單,那么用戶不能夠通過(guò)鍵盤(pán)來(lái)實(shí)現(xiàn)系統(tǒng)命令,除非應(yīng)用程序自身提供了這樣的接口。系統(tǒng)菜單對(duì)于最大化的窗口也是很有用處的,最大化的窗口覆蓋了整個(gè)屏幕,這樣的窗口不能夠被移動(dòng),除非恢復(fù)到不是最大化的狀態(tài),如果這個(gè)最大化的窗口有了系統(tǒng)菜單,則就不必一定恢復(fù)到非最大化的狀態(tài)才能夠移動(dòng)。
WS_HSCROLL
??????如果窗口被指定了這個(gè)屬性,那么窗口會(huì)有一個(gè)水平的滾動(dòng)條,窗口是不會(huì)自動(dòng)的滾動(dòng)滾動(dòng)條的,如果應(yīng)用程序要支持滾動(dòng)條,那么必須自己處理WM_HSCROLL 消息,這個(gè)屬性通常是在窗口創(chuàng)建的時(shí)候,被指定的。
WS_VSCROLL
??????如果窗口被指定了這個(gè)屬性,那么窗口會(huì)有一個(gè)豎直的滾動(dòng)條,窗口不會(huì)自動(dòng)的滾動(dòng)滾動(dòng)條,應(yīng)用程序必須自己處理WM_VSCROOL 消息來(lái)處理滾動(dòng)條滾動(dòng)的消息,這個(gè)屬性通常是在窗口被創(chuàng)建的時(shí)候指定的。
WS_BORDER
??????如果窗口被指定了這個(gè)屬性,那么窗口會(huì)有一個(gè)單線的邊在窗口的周圍,如果沒(méi)有指定這個(gè)屬性,但是窗口具有標(biāo)題欄,那么窗口會(huì)自動(dòng)的擁有這個(gè)屬性,如果窗口沒(méi)有這個(gè)屬性,擁有這個(gè)屬性的窗口不能夠通過(guò)鍵盤(pán)或者是鼠標(biāo)改變窗口的大小。
WS_DLGFRAME
??????如果窗口被指定了這個(gè)屬性,那么窗口具有對(duì)話框的邊框,這個(gè)屬性通常是用在對(duì)話框窗口的,只能夠用在窗口沒(méi)有標(biāo)題欄的情況下,如果一個(gè)不是對(duì)話框的窗口使用了這個(gè)窗口,那么窗口必須被指定WS_EX_DLGMODALFRAME 屬性。使用這個(gè)屬性創(chuàng)建的窗口,不能夠通過(guò)鍵盤(pán)和鼠標(biāo)改變窗口的大小。
WS_THICKFRAME
??????當(dāng)窗口被指定了這個(gè)屬性,那么窗口會(huì)有一個(gè)可以改變大小的邊框,這種屬性通常用在程序的主窗口,具有這種屬性的窗口的大小可以通過(guò)鍵盤(pán)或者鼠標(biāo)來(lái)改變。
WS_CLIPCHILDREN
??????這個(gè)屬性用在具有子窗口的窗口,使用這個(gè)屬性,可以使Windows 把子窗口所占的區(qū)域拷貝到父窗口,而不是甴父窗口直接的畫(huà)子窗口所屬的區(qū)域,如果窗口沒(méi)有指定這個(gè)屬性,那么那么父窗口會(huì)覆蓋子窗口的區(qū)域。在一些圖片顯示或者OpenGL 顯示的窗口中,指定這個(gè)屬性是很重要的。
WS_CLIPSIBLINGS
??????當(dāng)窗口賦予這個(gè)屬性,窗口在自繪的時(shí)候,不會(huì)繪制到同屬的子窗口,所有具有交迭屬性和彈出屬性的窗口都具有這個(gè)屬性,所有的頂層窗口都具有這個(gè)屬性,這樣一來(lái)頂層窗口在自繪的時(shí)候,不會(huì)繪制在到其他的頂層窗口。
WS_VISIBLE
??????當(dāng)窗口被設(shè)置這個(gè)屬性的時(shí)候,窗口是可見(jiàn)的,默認(rèn)的情況下,應(yīng)用程序必須自己調(diào)用ShowWindow 來(lái)顯示窗口。
WS_DISABLED
??????當(dāng)窗口被設(shè)置這個(gè)屬性的時(shí)候,創(chuàng)建的窗口不能夠接受用戶的輸入,除非應(yīng)用程序自身提供方法來(lái)輸入。這個(gè)屬性通常用在Windows 控件上面。
WS_CHILDWINDOWS
??????這個(gè)屬性同WS_CHILD。
WS_OVERLAPPEDWINDOWS
??????這個(gè)屬性同WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,這個(gè)屬性通常用在應(yīng)用程序的主窗口。
WS_POPUPWINDOWS
??????這個(gè)屬性同WS_POPUP | WS_BORDER | WS_SYSMENU,盡管這個(gè)屬性中包含了WS_SYSMENU 屬性,如果窗口沒(méi)有 WS_CAPTION 屬性,那么窗口也不會(huì)有系統(tǒng)菜單。
WS_EX_DLGMODALFRAME
??????當(dāng)窗口設(shè)置了這個(gè)屬性的時(shí)候,窗口具有對(duì)話框的邊框,這個(gè)屬性通常用在對(duì)話框窗口,不過(guò)任何窗口都可以使用這個(gè)屬性來(lái)獲得對(duì)話框的邊框。
WS_EX_NOPARENTNOTIFY
??????這個(gè)屬性是用在子窗口上的,當(dāng)子窗口設(shè)置了這個(gè)屬性,Windows 不發(fā)送WM_NOTIFY 消息給子窗口的父窗口,默認(rèn)情況下,Windows 會(huì)在子窗口創(chuàng)建或者銷毀的時(shí)候發(fā)送WM_NOTIFY 消息給子窗口的父窗口。
WS_EX_TOPMOST
??????這個(gè)屬性僅用在頂層窗口,對(duì)于子窗口設(shè)置這個(gè)屬性是被忽略的,如果窗口設(shè)置了這個(gè)屬性,那么窗口會(huì)一直在其他窗口的上面。
WS_EX_ACCEPTFILES
??????窗口設(shè)置了這個(gè)屬性,那么窗口可以接受拖放的對(duì)象。
WS_EX_TRANSPARENT
??????這個(gè)屬性能夠使窗口透明,設(shè)置了這個(gè)屬性的窗口的背景使可以被看到的,透明窗口對(duì)于鼠標(biāo)和鍵盤(pán)的消息事件并不是透明的。