MFC程式員的WTL指南: Part VII – 分隔視窗

來源:互聯網
上載者:User

MFC程式員的WTL指南: Part VII - 分隔視窗

原作 :Michael Dunn [英文原文]
翻譯 :Orbit(桔皮幹了) [www.winmsg.com]

下載示範程式碼

本章內容

  • 介紹
  • WTL 的分隔視窗
    • 相關的類
    • 建立分隔視窗
    • 基本的方法
    • 資料成員
  • 開始一個例子工程
  • 建立一個窗格內的視窗
  • 訊息處理
  • 窗格容器
    • 相關的類
    • 基本方法
    • 在分隔視窗中使用窗格容器
    • 關閉按鈕和訊息處理
  • 進階功能
    • 嵌套的分隔視窗
    • 在窗格中使用ActiveX控制項
    • 特殊繪製
  • 窗格容器內的特殊繪製
  • 在狀態列顯示進度條
  • 繼續
  • 參考
  • 修改記錄

介紹

隨著使用兩個分隔的視圖管理檔案系統的資源管理員在Windows 95中第一次出現,分隔視窗逐漸成為一種流行的介面元素。MFC也有一個複雜的功能強大的分隔視窗類別,但是要掌握它的用法確實有點難,並且它和文檔/視圖架構聯絡緊密。在第七章我將介紹WTL的分隔視窗,它比MFC的分隔視窗要簡單一些。WTL的分隔視窗沒有MFC那麼多特性,但是便於使用和擴充。

本章的例子工程是用WTL重寫的ClipSpy,如果你對這個程式不太熟悉,現在可以快速探索一下本章內容,因為我只是複製了ClipSpy的功能而沒用深入的解釋它是如何工作的,畢竟這篇文章的重點是分隔視窗,不是剪貼簿。

WTL 的分隔視窗

標頭檔atlsplit.h含有所有WTL的分隔視窗類別,一共有三個類:CSplitterImpl,CSplitterWindowImpl和CSplitterWindowT,不過你通常只會用到其中的一個。下面將介紹這些類和它們的基本方法。

相關的類

CSplitterImpl是一個有兩個參數的模板類,一個是視窗介面類的類名,另一個是布爾型變數表示分隔視窗的方向:true表示垂直方向,false表示水平方向。CSplitterImpl類包含了幾乎所有分隔視窗的實現代碼,它的許多方法是可重載的,重載這些方法可以自己繪製分隔條的外觀或者實現其它的效果。CSplitterWindowImpl類是從CWindowImpl和CSplitterImpl兩個類派生出來的,但是它的代碼不多,有一個空的WM_ERASEBKGND訊息處理函數和一個WM_SIZE處理函數用於重新置放分隔視窗。

最後一個是CSplitterWindowT類,它從CSplitterImpl類派生,它的視窗類別名是“WTL_SplitterWindow”。還有兩個自訂資料類型通常用來取代上面的三個類:CSplitterWindow用於垂直分隔視窗,CHorSplitterWindow用於水平分隔視窗。

建立分割視窗

由於CSplitterWindow是從CWindowImpl類派生的,所以你可以像建立其他子視窗那樣建立分隔視窗。分隔視窗將存在於整個主架構視窗的生命週期,應該在CMainFrame類添加一個CSplitterWindow類型的變數。在CMainFrame::OnCreate()函數內,你可以將分隔視窗作為主視窗的子視窗建立,然後將其設定為主視窗的客戶區視窗:

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;}

建立分隔視窗之後,你就可以為每個窗格指定視窗或者做其他必要的初始化工作。

基本方法

bool SetSplitterPos(int xyPos = -1, bool bUpdate = true)int GetSplitterPos()

可以調用SetSplitterPos()函數設定分隔條的位置,這個位置表示分割條距離分隔視窗的上邊界(水平分隔視窗)或左邊界(垂直分隔視窗)有多少個象素點。你可以使用預設值-1將分隔條設定到分隔視窗的中間,使兩個窗格大小相同,通常傳遞true給bUpdate參數表示在移動分隔條之後相應的改變兩個窗格的大小。GetSplitterPos()返回當前分隔條的位置,這個位置也是相對於分隔視窗的上邊界或左邊界。

bool SetSinglePaneMode(int nPane = SPLIT_PANE_NONE)int GetSinglePaneMode()

調用SetSinglePaneMode()函數可以改變分隔視窗的模式使單窗格模式還是雙窗格模式,在單窗格模式下,只有一個窗格使可見的並且隱藏了分隔條,這和MFC的動態分隔視窗相似(只是沒有那個小鉗子形狀的手柄,它用於重新分隔分隔視窗)。對於nPane參數可用的值是SPLIT_PANE_LEFT,SPLIT_PANE_RIGHT,SPLIT_PANE_TOP,SPLIT_PANE_BOTTOM,和SPLIT_PANE_NONE,前四個指示顯示那個窗格(例如,使用SPLIT_PANE_LEFT參數將顯示左邊的窗格,隱藏右邊的窗格),使用SPLIT_PANE_NONE表示兩個窗格都顯示。GetSinglePaneMode()返回五個SPLIT_PANE_*值中的一個表示當前的模式。

DWORD SetSplitterExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)DWORD GetSplitterExtendedStyle()

分隔視窗有自己的樣式用於控制當整個分隔視窗改變大小時如何移動分隔條。有以下幾種樣式:

  • SPLIT_PROPORTIONAL: 兩個窗格一起改變大小
  • SPLIT_RIGHTALIGNED: 右邊的窗格保持大小不變,只改變左邊的窗格大小
  • SPLIT_BOTTOMALIGNED: 下部的窗格保持大小不變,只改變上邊的窗格大小

如果既沒有指定SPLIT_PROPORTIONAL,也沒有指定SPLIT_RIGHTALIGNED/SPLIT_BOTTOMALIGNED,則分隔視窗會變成靠左對齊或上對齊。如果將SPLIT_PROPORTIONAL和SPLIT_RIGHTALIGNED/SPLIT_BOTTOMALIGNED一起使用,則優先選用SPLIT_PROPORTIONAL樣式。

還有一個附加的樣式用來控制分隔條是否可以被使用者移動:

  • SPLIT_NONINTERACTIVE:分隔條不能被移動並且不相應滑鼠

擴充樣式的預設值是 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是一個SPLIT_PANE_*類型的值,表示設定拿一個窗格。hWnd是子視窗的視窗控制代碼。你可以使用SetSplitterPane()將一個子視窗同時指定給兩個窗格,對於bUpdate參數通常使用預設值,也就是告訴分隔視窗立即調整子視窗的大小以適應窗格的大小。可以調用GetSplitterPane()得到某個窗格的子視窗控制代碼,如果窗格沒有指派子視窗則GetSplitterPane()返回NULL。

bool SetActivePane(int nPane)int GetActivePane()

SetActivePane()函數將分隔視窗中的某個子視窗設定為當前焦點視窗,nPane是SPLIT_PANE_*類型的值,表示需要啟用哪個窗格,這個函數還可以設定預設的活動窗格(後面介紹)。GetActivePane()函數查看所有擁有焦點的視窗,如果擁有焦點的視窗是窗格或窗格的子視窗就返回一個SPLIT_PANE_*類型的值,表示是哪個窗格。如果當前擁有焦點的視窗不是窗格的子視窗,那麼GetActivePane()返回SPLIT_PANE_NONE。

bool ActivateNextPane(bool bNext = true)

如果分隔視窗是單窗格模式,焦點被設到可見的窗格上,否則的話,ActivateNextPane()函數將調用GetActivePane()查看擁有焦點的視窗。如果一個窗格(或窗格內的子視窗)擁有檢點,分隔視窗就將焦點設給另一個窗格,否則ActivateNextPane()將判斷bNext的值,如果是true就啟用left/top窗格,如果是false則啟用right/bottom窗格。

bool SetDefaultActivePane(int nPane)bool SetDefaultActivePane(HWND hWnd)int GetDefaultActivePane()

調用SetDefaultActivePane()函數可以設定預設的活動窗格,它的參數可以是SPLIT_PANE_*類型的值,也可以是視窗的控制代碼。如果分隔視窗自身得到的焦點,可以通過調用SetFocus()將焦點轉移給預設窗格。GetDefaultActivePane()函數返回SPLIT_PANE_*類型的值表示哪個窗格是當前預設的活動窗格。

void GetSystemSettings(bool bUpdate)

GetSystemSettings()讀取系統設定並相應的設定資料成員。分隔視窗在OnCreate()函數中自動調用這個函數,你不需要自己調用這個函數。當然,你的主架構視窗應該響應WM_SETTINGCHANGE並將它傳遞給分隔視窗, CSplitterWindow在WM_SETTINGCHANGE訊息的處理函數中調用GetSystemSettings()。傳遞true給bUpdate參數,分隔視窗會根據新的設定重畫自己。

資料成員

其他的一些特性可以通過直接存取CSplitterWindow的公有成員來設定,只要GetSystemSettings()被調用了,這些公有成員也會相應的被重設。

m_cxySplitBar:控制分隔條的寬度(垂直分隔條)和高度(水平分隔條)。預設值是通過調用GetSystemMetrics(SM_CXSIZEFRAME)(垂直分隔條)或GetSystemMetrics(SM_CYSIZEFRAME)(水平分隔條)得到的。

m_cxyMin:控制每個窗格的最小寬度(垂直分隔)和最小高度(水平分隔),分隔視窗不允許拖動比這更小的寬度或高度。如果分隔視窗有WS_EX_CLIENTEDGE擴充屬性,則這個變數的預設值是0,否則其預設值是2*GetSystemMetrics(SM_CXEDGE)(垂直分隔)或2*GetSystemMetrics(SM_CYEDGE)(水平分隔)。

m_cxyBarEdge:控制畫在分隔條兩側的3D邊界的寬度(垂直分隔)或高度(水平分隔),其預設值剛好和m_cxyMin相反。

m_bFullDrag:如果是true,當分隔條被拖動時窗格大小跟著調整,如果是false,拖動時只顯示一個分隔條的影子,直到拖動停止才調整窗格的大小。預設值是調用SystemParametersInfo(SPI_GETDRAGFULLWINDOWS)函數的傳回值。

開始一個例子工程

既然我們已經對分隔視窗有了基本的瞭解,我們就來看看如何建立一個包含分隔視窗的架構視窗。使用WTL嚮導開始一個新工程,在第一頁選擇SDI Application並單擊Next,在第二頁,如所示取消工具條並選擇不使用視圖視窗:

我們不使用分隔視窗是因為分隔視窗和它的窗格將作為“視圖視窗”,在CMainFrame類中添加一個CSplitterWindow類型的資料成員:

class CMainFrame : public ...{//...protected:    CSplitterWindow  m_wndVertSplit;};

接著在OnCreate()中建立分隔視窗並將其設為視圖視窗:

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()將分隔視窗設定為初始時的大小。如果跳過這一步,分隔視窗的大小將不確定,可能小於200個象素點的寬度,最終導致SetSplitterPos()出現意想不到的結果。還有一種不調用UpdateLayout()函數的方,就是先得到架構視窗的客戶區座標,然後使用這個客戶區座標替換rcDefault座標建立分隔視窗。使用這種方式建立的分隔視窗一開始就在正確的初始位置上,隨後對位置調整的函數(例如 SetSplitterPos())都可以正常工作。

現在運行我們的程式就可以看到分隔條,即使沒有建立任何窗格視窗它仍具有基本的行為。你可以拖動分隔條,用滑鼠雙擊分隔條使其移到視窗的中間位置。

為了示範分隔視窗的不同使用方法,我將使用一個CListViewCtrl衍生類別和一個簡單的CRichEditCtrl,下面是從CClipSpyListCtrl類摘錄的代碼,我們在左邊的窗格使用這個類:

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()//...};

如果你看過前面的幾篇文章就會很容易讀懂這個類的代碼。它響應WM_CHANGECBCHAIN訊息,這樣就可以知道是否啟動和關閉了其它剪貼簿查看程式,它還響應WM_DRAWCLIPBOARD訊息,這樣就可以知道剪貼簿的內容是否改變。

由於分隔視窗窗格內的子視窗在程式運行其間一直存在,我們也可以將它們設為CMainFrame類的成員:

class CMainFrame : public ...{//...protected:    CSplitterWindow  m_wndVertSplit;    CClipSpyListCtrl m_wndFormatList;    CRichEditCtrl    m_wndDataViewer;};

建立一個窗格內的視窗

既然已經有了分隔視窗和子視窗的成員變數,填充分隔視窗就是一件簡單的事情了。先建立分隔視窗,然後建立兩個子視窗,使用分隔視窗作為它們的父視窗:

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;}

注意兩個類的Create()函數都用m_wndVertSplit作為父視窗,RECT參數無關緊要,因為分隔視窗會重新調整它們的大小,所以可以使用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控制項上增加了幾欄,結果看起來是這個樣子:

需要注意的是分隔視窗對放進窗格的視窗類別型沒有限制,不像MFC那樣必須是CView的衍生類別。窗格視窗只要有WS_CHILD樣式就行了,沒有任何其他限制。

訊息處理

由於在主架構視窗和我們的窗格視窗之間加了一個分隔視窗,你可能想知道現在通知訊息是如何工作的,比如,主架構視窗是如何收到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()

最後的哪個FORWARD_NOTIFICATIONS()宏最重要,回憶一下第四章,有一些通知訊息總是被發送的子視窗的父視窗,FORWARD_NOTIFICATIONS()就是做了這些工作,它將這些訊息轉寄給分隔視窗的父視窗。也就是說,當list視窗發送一個WM_NOTIFY訊息給分隔視窗時(它是list的父視窗),分隔視窗就將這個WM_NOTIFY訊息轉寄給主架構視窗(它是分隔視窗的父視窗)。當主架構視窗反射回訊息時會將訊息反射給WM_NOTIFY訊息的最初寄件者,也就是list視窗,所以分隔視窗並沒有參與訊息反射。

在list視窗和主架構視窗之間的這些訊息傳遞並不影響分隔視窗的工作,這使得在程式中添加和移除分隔視窗非常容易,因為子視窗不需要做任何改變就可以繼續工作。

窗格容器

WTL還有一個被稱為窗格容器的構件,它就像Explorer中左邊的窗格那樣,頂部有一個可以顯示文字的地區,還有一個可選擇是否顯示的Close按鈕:

就像分隔視窗管理兩個窗格視窗一樣,這個窗格容器也管理一個子視窗,當容器視窗的大小改變時,子視窗也相應的改變大小以便能夠填充容器視窗的內部空間。

相關的類

這個窗格容器的實現需要兩個類:CPaneContainerImpl和CPaneContainer,它們都在atlctrlx.h中聲明。CPaneContainerImpl是一個CWindowImpl衍生類別,它含有窗格容器的完整實現,CPaneContainer只是提供了一個類名,除非重載CPaneContainerImpl的方法或改變容器的外觀,一般使用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)

建立一個CPaneContainer視窗和建立其它子視窗一樣。有兩個Create()函數,它們的區別僅僅是第二個參數不同。第一個函數需要傳遞一個字串作為容器頂部地區顯示的文字,第二個參數需要需要傳一個字串的資源ID,其他參數只要使用預設值就行了。

DWORD SetPaneContainerExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)DWORD GetPaneContainerExtendedStyle()

CPaneContainer還有一些擴充樣式用來控制容器視窗上Close按鈕的布局方式:

  • PANECNT_NOCLOSEBUTTON:使用樣式去掉頂部的Close按鈕。
  • PANECNT_VERTICAL:設定這個樣式後,頂部的文字地區將沿著容器視窗的左邊界垂直放置。

擴充樣式的預設值是0,表示容器視窗是水平放置的,還有一個Close按鈕。

HWND SetClient(HWND hWndClient)HWND GetClient()

調用SetClient()可以將一個子視窗指派給窗格容器,這和調用CSplitterWindow類的SetSplitterPane()方法作用類似。SetClient()同時返回原來的客戶區視窗控制代碼而調用GetClient()則可以得到當前的客戶區視窗控制代碼。

BOOL SetTitle(LPCTSTR lpstrTitle)BOOL GetTitle(LPTSTR lpstrTitle, int cchLength)int GetTitleLength()

調用SetTitle()可以改變容器視窗頂部顯示的文字,調用GetTitle()可以得到當前視窗頂部地區顯示的文字,調用GetTitleLength()可以得到當前顯示的文字的字元個數(不包括結尾的Null 字元)。

BOOL EnableCloseButton(BOOL bEnable)

如果窗格容器使用的Close按鈕,你可以調用EnableCloseButton()來控制這個按鈕的狀態。

在分隔視窗中使用窗格容器

為了說明窗格容器的使用方法,我們將向ClipSpy的分隔視窗的左窗格添加一個窗格容器,我們將一個窗格容器指派給左窗格取代原來使用的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,同時m_wndPaneContainer被設定成分隔視窗的左窗格。

下面是修改後的左窗格的外觀,由於窗格容器在頂部的文本地區自己畫了一個三維邊框,所以我還要稍微修改一下邊框的樣式。這樣看起來不是很好看,你可以自己調整樣式知道你滿意為止。(當然,你需要在Windows XP 上測試一下哪個介面主題可以使得分隔視窗看起來“更有意思”。)

關閉按鈕和訊息處理

當使用者用按一下滑鼠Close按鈕時,窗格容器向父視窗發送一個WM_COMMAND訊息,命令的ID是ID_PANE_CLOSE。如果你在分隔視窗中使用了窗格容器,你需要響應整個訊息,調用SetSinglePaneMode()隱藏這個窗格。(但是,不要忘了提供使用者一個重新顯示窗格的方法!)

CPaneContainer的訊息鏈也用到了FORWARD_NOTIFICATIONS()宏,和CSplitterWindow一樣,窗格容器在客戶視窗和它的父視窗之間傳遞通知訊息。在ClipSpy這個例子中,在list控制項和主架構視窗之間隔了兩個視窗(窗格容器和分隔視窗),但是FORWARD_NOTIFICATIONS()宏可以確保所有的通知訊息被送到主架構視窗。 進階功能

在這一節,我將介紹一些如何使用WTL的進階介面特性。

嵌套的分隔視窗

如果你要編寫一個email的用戶端程式,你可能需要使用嵌套的分隔條,一個水平的和一個垂直的分隔條。使用WTL很容易做到這一點:建立一個分隔視窗作為另一個分隔視窗的子視窗。

為了示範這種效果,我將為ClipSpy添加一個水平分隔視窗。首先,添加一個名為m_wndHorzSplitter的CHorSplitterWindow類型的成員,像建立垂直分隔視窗m_wndVertSplitter那樣建立這個水平分隔視窗,使水平分隔視窗m_wndHorzSplitter成為頂層視窗,將m_wndVertSplitter建立成m_wndHorzSplitter的子視窗。最後將m_hWndClient設定為m_wndHorzSplitter,因為現在水平分隔視窗佔據整個主架構視窗的客戶區。

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 );//...}

最終的結果是這個樣子的:

在窗格中使用ActiveX控制項

在分隔視窗的窗格中使用ActiveX控制項與在對話方塊中使用ActiveX控制項類似,使用CAxWindow類的方法在運行是建立控制項,然後將這個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 );

特殊繪製

如果你想改變分隔條的外觀,例如在上面使用一些材質,你可以從CSplitterWindowImpl派生新類並重載DrawSplitterBar()函數。如果你只是想調整一下分隔條的外觀,可以複製CSplitterWindowImpl類的函數,然後稍做修改。下面的例子就在分隔條中使用了斜交叉線圖案。

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也有幾個函數可以重載,用來改變窗格容器的外觀。你可以從CPaneContainerImpl派生新類並重載你需要的方法,例如:

class CMyPaneContainer :    public CPaneContainerImpl<CMyPaneContainer>{public:    DECLARE_WND_CLASS_EX(_T("My_PaneContainer"), 0, -1)//... overrides here ...};

一些更有意思的方法是:

void CalcSize()

調用CalcSize()函數只是為了設定m_cxyHeader,這個變數控制著窗格容器的頂部地區的寬度和高度。不過SetPaneContainerExtendedStyle()函數中有一個BUG,導致窗格從水平切換到垂直時沒有調用衍生類別的CalcSize()方法,你可以將CalcSize()調用改為pT->CalcSize()來修補這個BUG。

HFONT GetTitleFont()

這個方法返回一個HFONT,它被用來畫頂部地區的文字,預設的值是調用GetStockObject(DEFAULT_GUI_FONT)得到的字型,也就是MS Sans Serif。如果你想改稱更現代的Tahoma字型,你可以重載GetTitleFont()方法,返回你建立的Tahoma字型。

BOOL GetToolTipText(LPNMHDR lpnmh)

重載這個方法提供滑鼠移到Close按鈕時彈出的提示資訊,這個函數實際上是TTN_GETDISPINFO的相應函數,你可以將lpnmh轉換成NMTTDISPINFO*,並設定這個資料結構內相應的成員變數。記住一點,你必須檢查通知代碼,它可能是TTN_GETDISPINFO或TTN_GETDISPINFOW,你需要有區別的訪問這兩個資料結構。

void DrawPaneTitle(CDCHandle dc)

你可以重載這個方法自己畫頂部地區,你可以用GetClientRect()和m_cxyHeader來計算頂部地區的範圍。下面的例子示範了在水平容器的頂部地區畫一個漸層填充的背景:

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 );}

例子工程代碼中示範了對這幾個方法的重載,使得結果看起來是這個樣子的:

從上面的圖中可以看到,這個示範程式有一個Splitters菜單,通過它可以在各種風格的分隔條(包括自畫風格)和窗格容器之間切換,比較它們之間的異同。你還可以鎖定分隔條的位置,這是通過設定和取消SPLIT_NONINTERACTIVE擴充風格來實現的。

在狀態列顯示進度條

正如我在前幾篇文章中做得保證那樣,新的ClipSpy也示範了如何在狀態條上建立進展條,它和MFC版本得功能一樣,幾個相關得步驟是:

  1. 得到狀態條第一個窗格得座標範圍RECT
  2. 建立一個進展條作為狀態條得子視窗,視窗大小就是哪個狀態條窗格得大小
  3. 隨著edit控制項被填充的同時更新進展條的位置

這些代碼在CMainFrame::CreateProgressCtrlInStatusBar()函數中。

繼續

在第八章我將介紹屬性頁面和嚮導對話方塊的用法

參考

WTL Splitters and Pane Containers by Ed Gadziemski

修改記錄

July 9, 2003: 文章第一次發布。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.