原作 :Michael Dunn [英文原文] 翻譯 :Orbit(桔皮干了) [http://www.winmsg.com/cn] 下載演示程序代碼 本章內容 介紹 隨著(zhù)使用兩個(gè)分隔的視圖管理文件系統的資源管理器在Windows 95中第一次出現,分隔窗口逐漸成為一種流行的界面元素。MFC也有一個(gè)復雜的功能強大的分隔窗口類(lèi),但是要掌握它的用法確實(shí)有點(diǎn)難,并且它和文檔/視圖框架聯(lián)系緊密。在第七章我將介紹WTL的分隔窗口,它比MFC的分隔窗口要簡(jiǎn)單一些。WTL的分隔窗口沒(méi)有MFC那么多特性,但是易于使用和擴展。 本章的例子工程是用WTL重寫(xiě)的ClipSpy,如果你對這個(gè)程序不太熟悉,現在可以快速瀏覽一下本章內容,因為我只是復制了ClipSpy的功能而沒(méi)用深入的解釋它是如何工作的,畢竟這篇文章的重點(diǎn)是分隔窗口,不是剪貼板。 WTL 的分隔窗口 頭文件atlsplit.h含有所有WTL的分隔窗口類(lèi),一共有三個(gè)類(lèi):CSplitterImpl,CSplitterWindowImpl和CSplitterWindowT,不過(guò)你通常只會(huì )用到其中的一個(gè)。下面將介紹這些類(lèi)和它們的基本方法。 相關(guān)的類(lèi) CSplitterImpl是一個(gè)有兩個(gè)參數的模板類(lèi),一個(gè)是窗口界面類(lèi)的類(lèi)名,另一個(gè)是布爾型變量表示分隔窗口的方向:true表示垂直方向,false表示水平方向。CSplitterImpl類(lèi)包含了幾乎所有分隔窗口的實(shí)現代碼,它的許多方法是可重載的,重載這些方法可以自己繪制分隔條的外觀(guān)或者實(shí)現其它的效果。CSplitterWindowImpl類(lèi)是從CWindowImpl和CSplitterImpl兩個(gè)類(lèi)派生出來(lái)的,但是它的代碼不多,有一個(gè)空的WM_ERASEBKGND消息處理函數和一個(gè)WM_SIZE處理函數用于重新定位分隔窗口。 最后一個(gè)是CSplitterWindowT類(lèi),它從CSplitterImpl類(lèi)派生,它的窗口類(lèi)名是“WTL_SplitterWindow”。還有兩個(gè)自定義數據類(lèi)型通常用來(lái)取代上面的三個(gè)類(lèi):CSplitterWindow用于垂直分隔窗口,CHorSplitterWindow用于水平分隔窗口。 創(chuàng )建分割窗口 由于CSplitterWindow是從CWindowImpl類(lèi)派生的,所以你可以像創(chuàng )建其他子窗口那樣創(chuàng )建分隔窗口。分隔窗口將存在于整個(gè)主框架窗口的生命周期,應該在CMainFrame類(lèi)添加一個(gè)CSplitterWindow類(lèi)型的變量。在CMainFrame::OnCreate()函數內,你可以將分隔窗口作為主窗口的子窗口創(chuàng )建,然后將其設置為主窗口的客戶(hù)區窗口: LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs ){// ...const DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, dwSplitExStyle = WS_EX_CLIENTEDGE; m_wndSplit.Create ( *this, rcDefault, NULL, dwSplitStyle, dwSplitExStyle ); m_hWndClient = m_wndSplit;}創(chuàng )建分隔窗口之后,你就可以為每個(gè)窗格指定窗口或者做其他必要的初始化工作。 基本方法bool SetSplitterPos(int xyPos = -1, bool bUpdate = true)int GetSplitterPos() 可以調用SetSplitterPos()函數設置分隔條的位置,這個(gè)位置表示分割條距離分隔窗口的上邊界(水平分隔窗口)或左邊界(垂直分隔窗口)有多少個(gè)象素點(diǎn)。你可以使用默認值-1將分隔條設置到分隔窗口的中間,使兩個(gè)窗格大小相同,通常傳遞true給bUpdate參數表示在移動(dòng)分隔條之后相應的改變兩個(gè)窗格的大小。GetSplitterPos()返回當前分隔條的位置,這個(gè)位置也是相對于分隔窗口的上邊界或左邊界。 bool SetSinglePaneMode(int nPane = SPLIT_PANE_NONE)int GetSinglePaneMode() 調用SetSinglePaneMode()函數可以改變分隔窗口的模式使單窗格模式還是雙窗格模式,在單窗格模式下,只有一個(gè)窗格使可見(jiàn)的并且隱藏了分隔條,這和MFC的動(dòng)態(tài)分隔窗口相似(只是沒(méi)有那個(gè)小鉗子形狀的手柄,它用于重新分隔分隔窗口)。對于nPane參數可用的值是SPLIT_PANE_LEFT,SPLIT_PANE_RIGHT,SPLIT_PANE_TOP,SPLIT_PANE_BOTTOM,和SPLIT_PANE_NONE,前四個(gè)指示顯示那個(gè)窗格(例如,使用SPLIT_PANE_LEFT參數將顯示左邊的窗格,隱藏右邊的窗格),使用SPLIT_PANE_NONE表示兩個(gè)窗格都顯示。GetSinglePaneMode()返回五個(gè)SPLIT_PANE_*值中的一個(gè)表示當前的模式。 DWORD SetSplitterExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)DWORD GetSplitterExtendedStyle() 分隔窗口有自己的樣式用于控制當整個(gè)分隔窗口改變大小時(shí)如何移動(dòng)分隔條。有以下幾種樣式: - SPLIT_PROPORTIONAL: 兩個(gè)窗格一起改變大小
- SPLIT_RIGHTALIGNED: 右邊的窗格保持大小不變,只改變左邊的窗格大小
- SPLIT_BOTTOMALIGNED: 下部的窗格保持大小不變,只改變上邊的窗格大小
如果既沒(méi)有指定SPLIT_PROPORTIONAL,也沒(méi)有指定SPLIT_RIGHTALIGNED/SPLIT_BOTTOMALIGNED,則分隔窗口會(huì )變成左對齊或上對齊。如果將SPLIT_PROPORTIONAL和SPLIT_RIGHTALIGNED/SPLIT_BOTTOMALIGNED一起使用,則優(yōu)先選用SPLIT_PROPORTIONAL樣式。 還有一個(gè)附加的樣式用來(lái)控制分隔條是否可以被用戶(hù)移動(dòng): - SPLIT_NONINTERACTIVE:分隔條不能被移動(dòng)并且不相應鼠標
擴展樣式的默認值是 SPLIT_PROPORTIONAL。 bool SetSplitterPane(int nPane, HWND hWnd, bool bUpdate = true)void SetSplitterPanes(HWND hWndLeftTop, HWND hWndRightBottom, bool bUpdate = true)HWND GetSplitterPane(int nPane) 可以調用SetSplitterPane()為分隔窗口的窗格指派子窗口,nPane是一個(gè)SPLIT_PANE_*類(lèi)型的值,表示設置拿一個(gè)窗格。hWnd是子窗口的窗口句柄。你可以使用SetSplitterPane()將一個(gè)子窗口同時(shí)指定給兩個(gè)窗格,對于bUpdate參數通常使用默認值,也就是告訴分隔窗口立即調整子窗口的大小以適應窗格的大小??梢哉{用GetSplitterPane()得到某個(gè)窗格的子窗口句柄,如果窗格沒(méi)有指派子窗口則GetSplitterPane()返回NULL。 bool SetActivePane(int nPane)int GetActivePane() SetActivePane()函數將分隔窗口中的某個(gè)子窗口設置為當前焦點(diǎn)窗口,nPane是SPLIT_PANE_*類(lèi)型的值,表示需要激活哪個(gè)窗格,這個(gè)函數還可以設置默認的活動(dòng)窗格(后面介紹)。GetActivePane()函數查看所有擁有焦點(diǎn)的窗口,如果擁有焦點(diǎn)的窗口是窗格或窗格的子窗口就返回一個(gè)SPLIT_PANE_*類(lèi)型的值,表示是哪個(gè)窗格。如果當前擁有焦點(diǎn)的窗口不是窗格的子窗口,那么GetActivePane()返回SPLIT_PANE_NONE。 bool ActivateNextPane(bool bNext = true) 如果分隔窗口是單窗格模式,焦點(diǎn)被設到可見(jiàn)的窗格上,否則的話(huà),ActivateNextPane()函數將調用GetActivePane()查看擁有焦點(diǎn)的窗口。如果一個(gè)窗格(或窗格內的子窗口)擁有檢點(diǎn),分隔窗口就將焦點(diǎn)設給另一個(gè)窗格,否則ActivateNextPane()將判斷bNext的值,如果是true就激活left/top窗格,如果是false則激活right/bottom窗格。 bool SetDefaultActivePane(int nPane)bool SetDefaultActivePane(HWND hWnd)int GetDefaultActivePane() 調用SetDefaultActivePane()函數可以設置默認的活動(dòng)窗格,它的參數可以是SPLIT_PANE_*類(lèi)型的值,也可以是窗口的句柄。如果分隔窗口自身得到的焦點(diǎn),可以通過(guò)調用SetFocus()將焦點(diǎn)轉移給默認窗格。GetDefaultActivePane()函數返回SPLIT_PANE_*類(lèi)型的值表示哪個(gè)窗格是當前默認的活動(dòng)窗格。 void GetSystemSettings(bool bUpdate) GetSystemSettings()讀取系統設置并相應的設置數據成員。分隔窗口在OnCreate()函數中自動(dòng)調用這個(gè)函數,你不需要自己調用這個(gè)函數。當然,你的主框架窗口應該響應WM_SETTINGCHANGE并將它傳遞給分隔窗口, CSplitterWindow在WM_SETTINGCHANGE消息的處理函數中調用GetSystemSettings()。傳遞true給bUpdate參數,分隔窗口會(huì )根據新的設置重畫(huà)自己。 數據成員 其他的一些特性可以通過(guò)直接訪(fǎng)問(wèn)CSplitterWindow的公有成員來(lái)設定,只要GetSystemSettings()被調用了,這些公有成員也會(huì )相應的被重置。 m_cxySplitBar:控制分隔條的寬度(垂直分隔條)和高度(水平分隔條)。默認值是通過(guò)調用GetSystemMetrics(SM_CXSIZEFRAME)(垂直分隔條)或GetSystemMetrics(SM_CYSIZEFRAME)(水平分隔條)得到的。 m_cxyMin:控制每個(gè)窗格的最小寬度(垂直分隔)和最小高度(水平分隔),分隔窗口不允許拖動(dòng)比這更小的寬度或高度。如果分隔窗口有WS_EX_CLIENTEDGE擴展屬性,則這個(gè)變量的默認值是0,否則其默認值是2*GetSystemMetrics(SM_CXEDGE)(垂直分隔)或2*GetSystemMetrics(SM_CYEDGE)(水平分隔)。 m_cxyBarEdge:控制畫(huà)在分隔條兩側的3D邊界的寬度(垂直分隔)或高度(水平分隔),其默認值剛好和m_cxyMin相反。 m_bFullDrag:如果是true,當分隔條被拖動(dòng)時(shí)窗格大小跟著(zhù)調整,如果是false,拖動(dòng)時(shí)只顯示一個(gè)分隔條的影子,直到拖動(dòng)停止才調整窗格的大小。默認值是調用SystemParametersInfo(SPI_GETDRAGFULLWINDOWS)函數的返回值。 開(kāi)始一個(gè)例子工程 既然我們已經(jīng)對分隔窗口有了基本的了解,我們就來(lái)看看如何創(chuàng )建一個(gè)包含分隔窗口的框架窗口。使用WTL向導開(kāi)始一個(gè)新工程,在第一頁(yè)選擇SDI Application并單擊Next,在第二頁(yè),如下圖所示取消工具條并選擇不使用視圖窗口: 我們不使用分隔窗口是因為分隔窗口和它的窗格將作為“視圖窗口”,在CMainFrame類(lèi)中添加一個(gè)CSplitterWindow類(lèi)型的數據成員: class CMainFrame : public ...{//...protected: CSplitterWindow m_wndVertSplit;};接著(zhù)在OnCreate()中創(chuàng )建分隔窗口并將其設為視圖窗口: LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs ){//... // Create the splitter windowconst DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, dwSplitExStyle = WS_EX_CLIENTEDGE; m_wndVertSplit.Create ( *this, rcDefault, NULL, dwSplitStyle, dwSplitExStyle ); // Set the splitter as the client area window, and resize // the splitter to match the frame size. m_hWndClient = m_wndVertSplit; UpdateLayout(); // Position the splitter bar. m_wndVertSplit.SetSplitterPos ( 200 ); return 0;}需要注意的是在設置分隔窗口的位置之前要先設置m_hWndClient并調用CFrameWindowImpl::UpdateLayout()函數,UpdateLayout()將分隔窗口設置為初始時(shí)的大小。如果跳過(guò)這一步,分隔窗口的大小將不確定,可能小于200個(gè)象素點(diǎn)的寬度,最終導致SetSplitterPos()出現意想不到的結果。還有一種不調用UpdateLayout()函數的方,就是先得到框架窗口的客戶(hù)區坐標,然后使用這個(gè)客戶(hù)區坐標替換rcDefault坐標創(chuàng )建分隔窗口。使用這種方式創(chuàng )建的分隔窗口一開(kāi)始就在正確的初始位置上,隨后對位置調整的函數(例如 SetSplitterPos())都可以正常工作。 現在運行我們的程序就可以看到分隔條,即使沒(méi)有創(chuàng )建任何窗格窗口它仍具有基本的行為。你可以拖動(dòng)分隔條,用鼠標雙擊分隔條使其移到窗口的中間位置。 為了演示分隔窗口的不同使用方法,我將使用一個(gè)CListViewCtrl派生類(lèi)和一個(gè)簡(jiǎn)單的CRichEditCtrl,下面是從CClipSpyListCtrl類(lèi)摘錄的代碼,我們在左邊的窗格使用這個(gè)類(lèi): typedef CWinTraitsOR<LVS_REPORT | LVS_SINGLESEL | LVS_NOSORTHEADER> CListTraits; class CClipSpyListCtrl : public CWindowImpl<CClipSpyListCtrl, CListViewCtrl, CListTraits>, public CCustomDraw<CClipSpyListCtrl>{public: DECLARE_WND_SUPERCLASS(NULL, WC_LISTVIEW) BEGIN_MSG_MAP(CClipSpyListCtrl) MSG_WM_CHANGECBCHAIN(OnChangeCBChain) MSG_WM_DRAWCLIPBOARD(OnDrawClipboard) MSG_WM_DESTROY(OnDestroy) CHAIN_MSG_MAP_ALT(CCustomDraw<CClipSpyListCtrl>, 1) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP()//...};如果你看過(guò)前面的幾篇文章就會(huì )很容易讀懂這個(gè)類(lèi)的代碼。它響應WM_CHANGECBCHAIN消息,這樣就可以知道是否啟動(dòng)和關(guān)閉了其它剪貼板查看程序,它還響應WM_DRAWCLIPBOARD消息,這樣就可以知道剪貼板的內容是否改變。 由于分隔窗口窗格內的子窗口在程序運行其間一直存在,我們也可以將它們設為CMainFrame類(lèi)的成員: class CMainFrame : public ...{//...protected: CSplitterWindow m_wndVertSplit; CClipSpyListCtrl m_wndFormatList; CRichEditCtrl m_wndDataViewer;};創(chuàng )建一個(gè)窗格內的窗口 既然已經(jīng)有了分隔窗口和子窗口的成員變量,填充分隔窗口就是一件簡(jiǎn)單的事情了。先創(chuàng )建分隔窗口,然后創(chuàng )建兩個(gè)子窗口,使用分隔窗口作為它們的父窗口: LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs ){//... // Create the splitter windowconst DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, dwSplitExStyle = WS_EX_CLIENTEDGE; m_wndVertSplit.Create ( *this, rcDefault, NULL, dwSplitStyle, dwSplitExStyle ); // Create the left pane (list of clip formats) m_wndFormatList.Create ( m_wndVertSplit, rcDefault ); // Create the right pane (rich edit ctrl)const DWORD dwRichEditStyle = WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | ES_READONLY | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE; m_wndDataViewer.Create ( m_wndVertSplit, rcDefault, NULL, dwRichEditStyle ); m_wndDataViewer.SetFont ( AtlGetStockFont(ANSI_FIXED_FONT) ); // Set the splitter as the client area window, and resize // the splitter to match the frame size. m_hWndClient = m_wndVertSplit; UpdateLayout(); m_wndVertSplit.SetSplitterPos ( 200 ); return 0;}注意兩個(gè)類(lèi)的Create()函數都用m_wndVertSplit作為父窗口,RECT參數無(wú)關(guān)緊要,因為分隔窗口會(huì )重新調整它們的大小,所以可以使用CWindow::rcDefault。 最后就是將窗口的句柄傳遞給分隔窗口的窗格,這一步也需要在UpdateLayout()調用之前完成,這樣最終所有的窗口都有正確的大小。 LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs ){//... m_wndDataViewer.SetFont ( AtlGetStockFont(ANSI_FIXED_FONT) ); // Set up the splitter panes m_wndVertSplit.SetSplitterPanes ( m_wndFormatList, m_wndDataViewer ); // Set the splitter as the client area window, and resize // the splitter to match the frame size. m_hWndClient = m_wndVertSplit; UpdateLayout(); m_wndVertSplit.SetSplitterPos ( 200 ); return 0;}現在,list控件上增加了幾欄,結果看起來(lái)是這個(gè)樣子: 需要注意的是分隔窗口對放進(jìn)窗格的窗口類(lèi)型沒(méi)有限制,不像MFC那樣必須是CView的派生類(lèi)。窗格窗口只要有WS_CHILD樣式就行了,沒(méi)有任何其他限制。 消息處理 由于在主框架窗口和我們的窗格窗口之間加了一個(gè)分隔窗口,你可能想知道現在通知消息是如何工作的,比如,主框架窗口是如何收到NM_CUSTOMDRAW通知消息并將它反射給list控件的?答案就在CSplitterWindowImpl的消息鏈中:
BEGIN_MSG_MAP(thisClass) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground) MESSAGE_HANDLER(WM_SIZE, OnSize) CHAIN_MSG_MAP(baseClass) FORWARD_NOTIFICATIONS() END_MSG_MAP() 最后的哪個(gè)FORWARD_NOTIFICATIONS()宏最重要,回憶一下第四章,有一些通知消息總是被發(fā)送的子窗口的父窗口,FORWARD_NOTIFICATIONS()就是做了這些工作,它將這些消息轉發(fā)給分隔窗口的父窗口。也就是說(shuō),當list窗口發(fā)送一個(gè)WM_NOTIFY消息給分隔窗口時(shí)(它是list的父窗口),分隔窗口就將這個(gè)WM_NOTIFY消息轉發(fā)給主框架窗口(它是分隔窗口的父窗口)。當主框架窗口反射回消息時(shí)會(huì )將消息反射給WM_NOTIFY消息的最初發(fā)送者,也就是list窗口,所以分隔窗口并沒(méi)有參與消息反射。 在list窗口和主框架窗口之間的這些消息傳遞并不影響分隔窗口的工作,這使得在程序中添加和移除分隔窗口非常容易,因為子窗口不需要做任何改變就可以繼續工作。 窗格容器 WTL還有一個(gè)被稱(chēng)為窗格容器的構件,它就像Explorer中左邊的窗格那樣,頂部有一個(gè)可以顯示文字的區域,還有一個(gè)可選擇是否顯示的Close按鈕: 就像分隔窗口管理兩個(gè)窗格窗口一樣,這個(gè)窗格容器也管理一個(gè)子窗口,當容器窗口的大小改變時(shí),子窗口也相應的改變大小以便能夠填充容器窗口的內部空間。 相關(guān)的類(lèi) 這個(gè)窗格容器的實(shí)現需要兩個(gè)類(lèi):CPaneContainerImpl和CPaneContainer,它們都在atlctrlx.h中聲明。CPaneContainerImpl是一個(gè)CWindowImpl派生類(lèi),它含有窗格容器的完整實(shí)現,CPaneContainer只是提供了一個(gè)類(lèi)名,除非重載CPaneContainerImpl的方法或改變容器的外觀(guān),一般使用CPaneContainer就夠了。 基本方法HWND Create( HWND hWndParent, LPCTSTR lpstrTitle = NULL, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)HWND Create( HWND hWndParent, UINT uTitleID, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL) 創(chuàng )建一個(gè)CPaneContainer窗口和創(chuàng )建其它子窗口一樣。有兩個(gè)Create()函數,它們的區別僅僅是第二個(gè)參數不同。第一個(gè)函數需要傳遞一個(gè)字符串作為容器頂部區域顯示的文字,第二個(gè)參數需要需要傳一個(gè)字符串的資源ID,其他參數只要使用默認值就行了。 DWORD SetPaneContainerExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)DWORD GetPaneContainerExtendedStyle() CPaneContainer還有一些擴展樣式用來(lái)控制容器窗口上Close按鈕的布局方式: - PANECNT_NOCLOSEBUTTON:使用樣式去掉頂部的Close按鈕。
- PANECNT_VERTICAL:設置這個(gè)樣式后,頂部的文字區域將沿著(zhù)容器窗口的左邊界垂直放置。
擴展樣式的默認值是0,表示容器窗口是水平放置的,還有一個(gè)Close按鈕。 HWND SetClient(HWND hWndClient)HWND GetClient() 調用SetClient()可以將一個(gè)子窗口指派給窗格容器,這和調用CSplitterWindow類(lèi)的SetSplitterPane()方法作用類(lèi)似。SetClient()同時(shí)返回原來(lái)的客戶(hù)區窗口句柄而調用GetClient()則可以得到當前的客戶(hù)區窗口句柄。 BOOL SetTitle(LPCTSTR lpstrTitle)BOOL GetTitle(LPTSTR lpstrTitle, int cchLength)int GetTitleLength() 調用SetTitle()可以改變容器窗口頂部顯示的文字,調用GetTitle()可以得到當前窗口頂部區域顯示的文字,調用GetTitleLength()可以得到當前顯示的文字的字符個(gè)數(不包括結尾的空字符)。 BOOL EnableCloseButton(BOOL bEnable) 如果窗格容器使用的Close按鈕,你可以調用EnableCloseButton()來(lái)控制這個(gè)按鈕的狀態(tài)。 在分隔窗口中使用窗格容器 為了說(shuō)明窗格容器的使用方法,我們將向ClipSpy的分隔窗口的左窗格添加一個(gè)窗格容器,我們將一個(gè)窗格容器指派給左窗格取代原來(lái)使用的list控件,而將list控件指派給窗格容器。下面是在CMainFrame::OnCreate()中為支持窗格容器而添加的代碼。 LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs ){//... m_wndVertSplit.Create ( *this, rcDefault, NULL, dwSplitStyle, dwSplitExStyle ); // Create the pane container. m_wndPaneContainer.Create ( m_wndVertSplit, IDS_LIST_HEADER ); // Create the left pane (list of clip formats) m_wndFormatList.Create ( m_wndPaneContainer, rcDefault );//... // Set up the splitter panes m_wndPaneContainer.SetClient ( m_wndFormatList ); m_wndVertSplit.SetSplitterPanes ( m_wndPaneContainer, m_wndDataViewer );注意,現在list控件的父窗口是m_wndPaneContainer,同時(shí)m_wndPaneContainer被設定成分隔窗口的左窗格。 下面是修改后的左窗格的外觀(guān),由于窗格容器在頂部的文本區域自己畫(huà)了一個(gè)三維邊框,所以我還要稍微修改一下邊框的樣式。這樣看起來(lái)不是很好看,你可以自己調整樣式知道你滿(mǎn)意為止。(當然,你需要在Windows XP 上測試一下哪個(gè)界面主題可以使得分隔窗口看起來(lái)“更有意思”。) 關(guān)閉按鈕和消息處理 當用戶(hù)用鼠標單擊Close按鈕時(shí),窗格容器向父窗口發(fā)送一個(gè)WM_COMMAND消息,命令的ID是ID_PANE_CLOSE。如果你在分隔窗口中使用了窗格容器,你需要響應整個(gè)消息,調用SetSinglePaneMode()隱藏這個(gè)窗格。(但是,不要忘了提供用戶(hù)一個(gè)重新顯示窗格的方法!) CPaneContainer的消息鏈也用到了FORWARD_NOTIFICATIONS()宏,和CSplitterWindow一樣,窗格容器在客戶(hù)窗口和它的父窗口之間傳遞通知消息。在ClipSpy這個(gè)例子中,在list控件和主框架窗口之間隔了兩個(gè)窗口(窗格容器和分隔窗口),但是FORWARD_NOTIFICATIONS()宏可以確保所有的通知消息被送到主框架窗口。 高級功能 在這一節,我將介紹一些如何使用WTL的高級界面特性。 嵌套的分隔窗口 如果你要編寫(xiě)一個(gè)email的客戶(hù)端程序,你可能需要使用嵌套的分隔條,一個(gè)水平的和一個(gè)垂直的分隔條。使用WTL很容易做到這一點(diǎn):創(chuàng )建一個(gè)分隔窗口作為另一個(gè)分隔窗口的子窗口。 為了演示這種效果,我將為ClipSpy添加一個(gè)水平分隔窗口。首先,添加一個(gè)名為m_wndHorzSplitter的CHorSplitterWindow類(lèi)型的成員,像創(chuàng )建垂直分隔窗口m_wndVertSplitter那樣創(chuàng )建這個(gè)水平分隔窗口,使水平分隔窗口m_wndHorzSplitter成為頂層窗口,將m_wndVertSplitter創(chuàng )建成m_wndHorzSplitter的子窗口。最后將m_hWndClient設置為m_wndHorzSplitter,因為現在水平分隔窗口占據整個(gè)主框架窗口的客戶(hù)區。 LRESULT CMainFrame::OnCreate(){//... // Create the splitter windows. m_wndHorzSplit.Create ( *this, rcDefault, NULL, dwSplitStyle, dwSplitExStyle ); m_wndVertSplit.Create ( m_wndHorzSplit, rcDefault, NULL, dwSplitStyle, dwSplitExStyle );//... // Set the horizontal splitter as the client area window. m_hWndClient = m_wndHorzSplit; // Set up the splitter panes m_wndPaneContainer.SetClient ( m_wndFormatList ); m_wndHorzSplit.SetSplitterPane ( SPLIT_PANE_TOP, m_wndVertSplit ); m_wndVertSplit.SetSplitterPanes ( m_wndPaneContainer, m_wndDataViewer );//...}最終的結果是這個(gè)樣子的: 在窗格中使用ActiveX控件 在分隔窗口的窗格中使用ActiveX控件與在對話(huà)框中使用ActiveX控件類(lèi)似,使用CAxWindow類(lèi)的方法在運行是創(chuàng )建控件,然后將這個(gè)CAxWindow指定給分隔窗口的窗格。下面演示了如何在水平分隔窗口下面的窗格中使用瀏覽器控件: // Create the bottom pane (browser)CAxWindow wndIE;const DWORD dwIEStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL; wndIE.Create ( m_wndHorzSplit, rcDefault, _T("http://www.codeproject.com"), dwIEStyle ); // Set the horizontal splitter as the client area window. m_hWndClient = m_wndHorzSplit; // Set up the splitter panes m_wndPaneContainer.SetClient ( m_wndFormatList ); m_wndHorzSplit.SetSplitterPanes ( m_wndVertSplit, wndIE ); m_wndVertSplit.SetSplitterPanes ( m_wndPaneContainer, m_wndDataViewer );特殊繪制 如果你想改變分隔條的外觀(guān),例如在上面使用一些材質(zhì),你可以從CSplitterWindowImpl派生新類(lèi)并重載DrawSplitterBar()函數。如果你只是想調整一下分隔條的外觀(guān),可以復制CSplitterWindowImpl類(lèi)的函數,然后稍做修改。下面的例子就在分隔條中使用了斜交叉線(xiàn)圖案。 template <bool t_bVertical = true>class CMySplitterWindowT : public CSplitterWindowImpl<CMySplitterWindowT<t_bVertical>, t_bVertical>{public: DECLARE_WND_CLASS_EX(_T("My_SplitterWindow"), CS_DBLCLKS, COLOR_WINDOW) // Overrideables void DrawSplitterBar(CDCHandle dc) { RECT rect; if ( m_br.IsNull() ) m_br.CreateHatchBrush ( HS_DIAGCROSS, t_bVertical ? RGB(255,0,0) : RGB(0,0,255) ); if ( GetSplitterBarRect ( &rect ) ) { dc.FillRect ( &rect, m_br ); // draw 3D edge if needed if ( (GetExStyle() & WS_EX_CLIENTEDGE) != 0) dc.DrawEdge(&rect, EDGE_RAISED, t_bVertical ? (BF_LEFT | BF_RIGHT) : (BF_TOP | BF_BOTTOM)); } } protected: CBrush m_br;}; typedef CMySplitterWindowT<true> CMySplitterWindow;typedef CMySplitterWindowT<false> CMyHorSplitterWindow;這就是結果(將分隔條變寬是為了更容易看到效果): 窗格容器內的特殊繪制 CPaneContainer也有幾個(gè)函數可以重載,用來(lái)改變窗格容器的外觀(guān)。你可以從CPaneContainerImpl派生新類(lèi)并重載你需要的方法,例如: class CMyPaneContainer : public CPaneContainerImpl<CMyPaneContainer>{public: DECLARE_WND_CLASS_EX(_T("My_PaneContainer"), 0, -1)//... overrides here ...};一些更有意思的方法是: void CalcSize() 調用CalcSize()函數只是為了設置m_cxyHeader,這個(gè)變量控制著(zhù)窗格容器的頂部區域的寬度和高度。不過(guò)SetPaneContainerExtendedStyle()函數中有一個(gè)BUG,導致窗格從水平切換到垂直時(shí)沒(méi)有調用派生類(lèi)的CalcSize()方法,你可以將CalcSize()調用改為pT->CalcSize()來(lái)修補這個(gè)BUG。 HFONT GetTitleFont() 這個(gè)方法返回一個(gè)HFONT,它被用來(lái)畫(huà)頂部區域的文字,默認的值是調用GetStockObject(DEFAULT_GUI_FONT)得到的字體,也就是MS Sans Serif。如果你想改稱(chēng)更現代的Tahoma字體,你可以重載GetTitleFont()方法,返回你創(chuàng )建的Tahoma字體。 BOOL GetToolTipText(LPNMHDR lpnmh) 重載這個(gè)方法提供鼠標移到Close按鈕時(shí)彈出的提示信息,這個(gè)函數實(shí)際上是TTN_GETDISPINFO的相應函數,你可以將lpnmh轉換成NMTTDISPINFO*,并設置這個(gè)數據結構內相應的成員變量。記住一點(diǎn),你必須檢查通知代碼,它可能是TTN_GETDISPINFO或TTN_GETDISPINFOW,你需要有區別的訪(fǎng)問(wèn)這兩個(gè)數據結構。 void DrawPaneTitle(CDCHandle dc) 你可以重載這個(gè)方法自己畫(huà)頂部區域,你可以用GetClientRect()和m_cxyHeader來(lái)計算頂部區域的范圍。下面的例子演示了在水平容器的頂部區域畫(huà)一個(gè)漸變填充的背景: void CMyPaneContainer::DrawPaneTitle ( CDCHandle dc ){RECT rect; GetClientRect(&rect); TRIVERTEX tv[] = { { rect.left, rect.top, 0xff00 }, { rect.right, rect.top + m_cxyHeader, 0, 0xff00 } };GRADIENT_RECT gr = { 0, 1 }; dc.GradientFill ( tv, 2, &gr, 1, GRADIENT_FILL_RECT_H );}例子工程代碼中演示了對這幾個(gè)方法的重載,使得結果看起來(lái)是這個(gè)樣子的: 從上面的圖中可以看到,這個(gè)演示程序有一個(gè)Splitters菜單,通過(guò)它可以在各種風(fēng)格的分隔條(包括自畫(huà)風(fēng)格)和窗格容器之間切換,比較它們之間的異同。你還可以鎖定分隔條的位置,這是通過(guò)設置和取消SPLIT_NONINTERACTIVE擴展風(fēng)格來(lái)實(shí)現的。 在狀態(tài)欄顯示進(jìn)度條 正如我在前幾篇文章中做得保證那樣,新的ClipSpy也演示了如何在狀態(tài)條上創(chuàng )建進(jìn)展條,它和MFC版本得功能一樣,幾個(gè)相關(guān)得步驟是: - 得到狀態(tài)條第一個(gè)窗格得坐標范圍RECT
- 創(chuàng )建一個(gè)進(jìn)展條作為狀態(tài)條得子窗口,窗口大小就是哪個(gè)狀態(tài)條窗格得大小
- 隨著(zhù)edit控件被填充的同時(shí)更新進(jìn)展條的位置
這些代碼在CMainFrame::CreateProgressCtrlInStatusBar()函數中。 繼續 在第八章我將介紹屬性頁(yè)和向導對話(huà)框的用法 參考 WTL Splitters and Pane Containers by Ed Gadziemski 修改記錄 July 9, 2003: 文章第一次發(fā)布。 |