1. 程式人生 > >談談Windows程式設計中的父視窗和所有者視窗

談談Windows程式設計中的父視窗和所有者視窗

一、概念和區別

    在windows系統中,每個視窗物件都對應有一個數據結構,形成一個list連結串列。系統的視窗管理器通過這個list來獲取視窗資訊和管理每個視窗。這個資料結構中有四個資料用來構建list,即child、sibling、parent、owner四個域。
    所以我們可以看到,視窗之間的關係有兩種:owner-owned 關係和 parent-child關係。前者稱之為擁有/被擁有關係,後者稱之為父/子關係。在這篇文字中,我把owner視窗稱之所有者視窗。換句話說,一個視窗在有一個父視窗(parent)的同時,還可能被不同的視窗擁有(owner),也可以有自己的子視窗(child)。在MFC 的CWnd類中,所有者視窗儲存在m_hWndOwner成員變數中,父視窗則儲存在m_hParent中,但是這兩個值並不一定和視窗物件資料結構中的值相對應。
   
    視窗之間的關係,決定了視窗的外在表現。比如顯示、銷燬等。

    如果一個視窗資料的owner域非NULL,則它和該視窗建立了owner-owned 關係,擁有關係決定了:
    (1)被擁有的視窗永遠顯示在擁有它的那個視窗的前面;
    (2)當所有者視窗最小化的時候,它所擁有的視窗都會被隱藏;
    (3)當所有者視窗被銷燬的時候,它所擁有的視窗都會被銷燬。
    需要注意的是,隱藏所有者視窗並不會影響它所擁有的視窗的可見狀態。比如:如果視窗 A 擁有視窗B,視窗B擁有視窗C,則當視窗A最小化的時候,視窗B被隱藏,但是視窗 C還是可見。


    如果一個視窗的parent域非NULL,則它和該視窗之間就建立了parent-child關係。父子決定了:
    (1)視窗在螢幕上面的顯示位置。父視窗提供了用來定位子視窗的座標系統,一個子視窗只能顯示在它的父視窗的客戶區中,之外的部分將被裁減。這個裁減法則決定了如果父視窗不可見,則子視窗肯定不可見。如果父視窗移動到了螢幕之外,子視窗也一樣。
    (2)當父視窗被隱藏時,它的所有子視窗也被隱藏。
    (3)父視窗被銷燬的時候,它所擁有的子視窗都會被銷燬。
     注意!最小化父視窗不會影響子視窗的可見狀態,子視窗會隨著父視窗被最小化,但是它的WS_VISIBLE屬性不會變。

    Windows系統為什麼要使用兩種關係呢?這是為了更加靈活的管理視窗。舉個例子:組合框(combobox)的下拉列表框(list box)可以超出組合框的父視窗的客戶區,這樣有利於顯示,因此係統建立該list box的時候,是作為控制檯視窗(desktop window)的子視窗,它的父視窗hWndParent是NULL,這樣,list box的顯示區域是限制在整個螢幕內,但是該list box的所有者卻是組合框的第一個非子視窗祖先(比如對話方塊),當它的所有者視窗銷燬後,該 list box自動銷燬。

    另外,視窗之間訊息的傳遞也和視窗關係有關,通常,一個視窗會把自己的通知訊息傳送給它的父視窗,但不全是這樣,比如,CToolBar傳送通知訊息給它的所有者視窗而不是父視窗。這樣以來,就可以允許工具條作為一個視窗(比如一個 OLE 容器程式視窗)的子視窗的同時,能夠給另一個視窗(比如in-place框架視窗)傳送訊息。至於某類視窗到底是把訊息傳送給誰,是父視窗還是所有者視窗,microsoft並沒有明示。還有,在現場(in-place)編輯的情況下,當一個 server 視窗啟用或者失效的時候,框架視窗所擁有的子視窗自動隱藏或者顯示,這也是通過直接呼叫SetOwner函式實現的。

 
二、視窗型別的說明和限制

(1)控制檯視窗(desktop window)。這是系統最早建立的視窗。可以認為它是所有 WS_OVERLAPPED 型別視窗的所有者和父視窗。Kyle Marsh在他的文章“Win32 Window Hierarchy and Styles”中指出,當系統初始化的時候,它首先建立控制檯視窗,大小覆蓋整個螢幕。所有其它視窗都在這個控制檯視窗上面顯示。視窗管理器所用的視窗list中第一個就是這個控制檯。它的下一層視窗叫做頂級視窗(top-level),頂級視窗是指所有非child、沒有父視窗,或者父視窗是desktop的視窗,它們沒有WS_CHILD屬性。

(2)WS_OVERLAPPED型別的視窗可以顯示在螢幕的任何地方。它們的所有者視窗是控制檯。

     Overlapped 型別的視窗屬於頂級視窗,一般作為應用程式的主視窗。不論是否給出了WS_CAPTION、WS_BORDER屬性,這類視窗建立後都有標題欄和邊框。Overlapped視窗可以擁有其它頂級視窗或者被其它頂級視窗所擁有。所有overlapped視窗都有WS_CLIPSIBLINGS屬性。系統可以自動設定 overlapped視窗的大小和初始位置。

    當系統 shuts down的時候,它將銷燬所有overlapped型別的視窗。

(3)WS_POPUP型別的視窗可以顯示在螢幕任何地方,它們一般沒有父視窗,但是如果明確呼叫SetParent,這類視窗也可以有父視窗。

     WS_POPUP型別的視窗的所有者是在CreateWindow函式中通過設定hWndParent引數給定的,如果hWndParent不是子視窗,則該視窗就成為這個新的彈出式視窗的owner,否則,系統從hWndParent的父視窗向上找,直到找到第一個非子視窗,把它作為該彈出視窗的owner。當owner視窗銷燬的時候,系統自動銷燬這個彈出視窗。

     Pop-up型別的視窗也屬於頂級視窗,它和 overlapped 視窗的主要區別是彈出式視窗不需要有標題欄,也不必有邊框。彈出式可以擁有其它頂級視窗或者被擁有。所有彈出式視窗也都有 WS_CLIPSIBLINGS屬性。

(4)所有者視窗(owner)只能是 overlapped 或者 pop-up 型別的視窗,子視窗不能是所有者視窗,也就是說子視窗不能擁有其它視窗。

    overlapped 或者 pop-up 型別的視窗在擁有其它視窗的同時,也可以被擁有。

    在使用CreateWindowEx建立 WS_OVERLAPPED 或者 WS_POPUP型別的視窗時,可以在 hwndParent 引數中給出它的所有者視窗的控制代碼。如果 hwndParent 給出的是一個child 型別的視窗控制代碼,則系統自動將新建立視窗的所有權交給該子視窗的頂級父視窗。在這種情況下,引數hwndParent被儲存在新建視窗的parent域中,而它的所有者視窗控制代碼則儲存在owner域中。

(5)預設情況下,對話方塊和訊息框屬於 owned 視窗,除非在建立它們的時候明確給出了WS_CHILD屬性,(比如對話方塊中嵌入對話方塊的情形)
否則由系統負責給它們指定owner視窗。需要注意的是,一旦建立了owned型別的視窗,就無法再改變其所有關係,因為WIN32沒有沒有提供改變視窗所有者的方法。

     而且在Win32中,由於有多執行緒的存在,所以要注意保證父子視窗或者owner/owned 視窗要同屬於一個執行緒。

(6)對於 WS_CHILD型別的視窗,它的父視窗就是它的所有者視窗。一個子視窗的父視窗也是在CreateWindow函式中用hWndParent引數指定的。子視窗只能在父視窗的客戶區中顯示,並隨父視窗一起銷燬。
     子視窗必須有一個父視窗,這是它和overlapped 以及 pop-up 視窗之間的主要區別。父視窗可以是頂級視窗,也可以是其它子視窗。


三、幾個相關函式的說明

(1)獲取/設定所有者視窗
 
    win32 API提供了函式GetWindow函式(GW_OWNER 標誌)來獲取一個視窗的所有者視窗控制代碼。
    GetWindow(hWnd, GW_OWNER)永遠返回視窗的所有者(owner)。對於子視窗,函式返回 NULL,因為它們的父視窗就相當於所有者(注意,是“相當於”)。因為Windows系統沒有維護子視窗的所有者資訊。

    MFC中則是通過如下函式得到所有者視窗指標:
    _AFXWIN_INLINE CWnd* CWnd::GetOwner() const
      { return m_hWndOwner != NULL ? CWnd::FromHandle(m_hWndOwner) : GetParent(); }
    從上述程式碼我們可以看出,它返回的值和GetWindow返回的有所區別,如果當前視窗沒有owner,那麼將返回它的父視窗指標。

    但是Windows沒有提供改變視窗所有者的方法。MFC中則提供了改變所有者的方法:
    _AFXWIN_INLINE void CWnd::SetOwner(CWnd* pOwnerWnd)
      { m_hWndOwner = pOwnerWnd != NULL ? pOwnerWnd->m_hWnd : NULL; }

    另外,mfc還提供了CWnd::GetSafeOwner( CWnd* pParent, HWND* pWndTop );函式,可以用來得到引數pParent的第一個非child屬性的父視窗指標。如果這個引數是NULL,則返回當前執行緒的主視窗(通過AfxGetMainWnd得到)。框架經常使用這個函式查詢對話方塊或者屬性頁的所有者視窗。

(2)獲取/設定父視窗

     WIN32 API給出了函式GetParent和SetParent。而mfc也是完全封裝了這兩個函式:

    _AFXWIN_INLINE CWnd* CWnd::SetParent(CWnd* pWndNewParent)
      { ASSERT(::IsWindow(m_hWnd)); return CWnd::FromHandle(::SetParent(m_hWnd,
   pWndNewParent->GetSafeHwnd())); }

    _AFXWIN_INLINE CWnd* CWnd::GetParent() const
      { ASSERT(::IsWindow(m_hWnd)); return CWnd::FromHandle(::GetParent(m_hWnd)); }

    對於SetParent,msdn裡面說明了父子視窗必須是同一個程序的。但是由於視窗控制代碼是系統全域性唯一的,不屬於同一個程序的情況下,也可以成功呼叫,但是後果未知。
    GetParent的返回值比較複雜,對於overlapped型別的視窗,它返回0,對於WS_CHILD型別,它返回其父視窗,對於WS_POPUP型別,它返回其所有者視窗,如果想得到建立它時所傳遞進去的那個hwndParent引數,應該用GetWindowWord(GWW_HWNDPARENT)函式。

(3)GetWindowWord(hWnd, GWW_HWNDPARENT)返回一個視窗的父視窗,如果沒有,則返回其所有者。

(4)上面談到,當一個owner視窗被最小化後,系統自動隱藏它所擁有的視窗。當owner視窗被恢復的時候,系統自動顯示它所擁有的視窗。在這兩種情況下,系統都會發送(send)WM_SHOWWINDOW訊息給被擁有的視窗。某些時候,我們可能需要隱藏 owned視窗,但並不想最小化其所有者視窗,這時候,可以通過ShowOwnedPopups函式來實現,該函式設定或者刪除當前視窗所擁有的視窗的WS_VISIBLE屬性,然後傳送WM_SHOWWINDOW訊息更新視窗顯示。