65.9K
CodeProject 正在变化。 阅读更多。
Home

MultiPaneCtrl - 创建分层嵌套区域

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (153投票s)

2010年7月12日

公共领域

5分钟阅读

viewsIcon

262448

downloadIcon

17813

一个允许您创建可以拖动到另一个位置的多个制表区域的控件。

Sample Image

引言

该控件允许您创建分层嵌套的区域,每个区域都可以拥有自己的选项卡。用户可以通过鼠标移动选项卡及其子窗口。这使用户能够按照自己的喜好自定义工作区域的位置和大小。MultiPaneCtrl的一种可能用途是作为一个填充框架客户区域的窗口。其结果是一个简单、易于配置的界面。

该控件基于CWnd类,可以作为子窗口放置在任何地方,例如,在框架的客户区域或对话框中。

Using the Code

每个窗格可以处于以下两种状态之一

  1. 为空(没有子窗格和选项卡)或有选项卡
  2. 有子窗格(成为一条线)

当前状态由函数IsLine确定。许多函数只能为两种状态之一的窗格调用。

使用该控件的基本规则是

  1. 根窗格在创建MultiPaneCtrl时创建,并且不能删除它。
  2. 在所有操作中,您可以使用NULL代替根窗格的句柄(GetRootPane)。
  3. 添加选项卡和转换为线条只能对没有子窗格的窗格进行。
  4. 当您将一个窗格转换为线条时,会为其创建一个子窗格,并将转换窗格的所有选项卡传递给它。
  5. 当您删除一个窗格时,其选项卡会传递给父窗格,前提是正在删除的窗格是其父窗格的唯一子窗格。

这是创建MultiPaneCtrl(添加窗格和选项卡)的算法示例

  1. 初始状态

    Sample Image

  2. 向根窗格添加选项卡(MultiPaneCtrl::AddTab

    Sample Image

  3. 将根窗格转换为线条(MultiPaneCtrl::ConvertPaneToLine

    Sample Image

  4. 向根窗格添加子窗格(MultiPaneCtrl::AddPane

    Sample Image

  5. Pane2转换为线条(MultiPaneCtrl::ConvertPaneToLine

    Sample Image

  6. 向Pane2添加子窗格(MultiPaneCtrl::AddPane

    Sample Image

  7. Pane3Pane4添加选项卡(MultiPaneCtrl::AddTab

Sample Image

MultiPaneCtrl中移除窗格。示例1

  1. 初始状态

    Sample Image

  2. 移除Pane3MultiPaneCtrl::DeletePane

    Sample Image

  3. 移除Pane2MultiPaneCtrl::DeletePane)。

    Sample Image

MultiPaneCtrl中移除窗格。示例2

  1. 初始状态

    Sample Image

  2. 移除Pane2(MultiPaneCtrl::DeletePane

    Sample Image

对于每个没有子窗格的窗格,默认情况下会创建一个TabCtrl控件。该控件也是我开发的,您可以在https://codeproject.org.cn/Articles/84524/TabCtrl找到它。所有TabCtrl控件都创建为MultiPaneCtrl的子窗口,并且不形成额外的嵌套级别。这一点尤其重要,因为现代Windows版本每个嵌套窗口的数量有限,通常不超过13-15个。函数GetTabCtrl允许您获取指向TabCtrl的指针。它只能为非线条窗格调用。您可以使用此指针来处理特定的制表窗格,也可以直接调用MultiPaneCtrl类的方法来操作选项卡。例如,要向窗格添加选项卡,请使用函数MultiPaneCtrl::AddTab

要创建控件并向其中添加元素,您可以执行以下步骤

#include "MultiPaneCtrl.h"

MultiPaneCtrlEx< MultiPaneCtrlStyle_VS2003_client > m_MultiPaneCtrl;
CTreeCtrl m_Tree1, m_Tree2;
CEdit m_Edit1;
CListCtrl m_List1, m_List2;

...

// Creation and initialization of child windows.
if( !m_Tree1.Create(WS_CHILD | TVS_HASBUTTONS | TVS_HASLINES,CRect(0,0,0,0),this,ID_Tree1) ||
    !m_Tree2.Create(WS_CHILD | TVS_HASBUTTONS | TVS_HASLINES,CRect(0,0,0,0),this,ID_Tree2) ||
    !m_Edit1.Create(WS_CHILD | ES_MULTILINE,CRect(0,0,0,0),this,ID_Edit1) ||
    !m_List1.Create(WS_CHILD | LVS_REPORT,CRect(0,0,0,0),this,ID_List1) ||
    !m_List2.Create(WS_CHILD | LVS_REPORT,CRect(0,0,0,0),this,ID_List2) )
    return -1;    // error.

m_Tree1.InsertItem("CTreeCtrl 1");
m_Tree2.InsertItem("CTreeCtrl 2");
m_Edit1.SetWindowText("CEdit 1");
m_List1.InsertColumn(0,"CListCtrl 1",LVCFMT_LEFT,100);
m_List1.InsertItem(0,"Item 1");
m_List2.InsertColumn(0,"CListCtrl 2",LVCFMT_LEFT,100);
m_List2.InsertItem(0,"Item 1");

// Creation of MultiPaneCtrl object.
if( !m_MultiPaneCtrl.Create(this,WS_CHILD | WS_VISIBLE, CRect(0,0,400,300),ID_MultiPaneCtrl) )
    return -1;    // error.

m_MultiPaneCtrl.CreateSystemImages(nullptr,IDB_IMAGES_SYSTEM,true,14);
m_MultiPaneCtrl.CreateImages(nullptr,IDB_IMAGES_TAB_NORMAL,IDB_IMAGES_TAB_DISABLE,true,16);

m_MultiPaneCtrl.SetCursors(IDC_TAB, IDC_SPLITTER_HORZ,IDC_SPLITTER_VERT,
    IDC_DRAGOUT_ENABLE,IDC_DRAGOUT_DISABLE);

m_MultiPaneCtrl.SetDockingMarkers( MarkersLayoutC(), 50);
m_MultiPaneCtrl.EnableTabRemove(true);
m_MultiPaneCtrl.EnableTabDrag(true);

// Loading state or creation default state.
MultiPaneCtrl::Tabs tabs;
tabs.Add(m_Tree1,"Tree1",-1);
tabs.Add(m_List1,"List1",0);
tabs.Add(m_Edit1,"Edit1",1);
tabs.Add(m_List2,"List2",2);
tabs.Add(m_Tree2,"Tree2",3);

if( !m_MultiPaneCtrl.LoadState(AfxGetApp(),"MultiPaneCtrl","State",&tabs,false) )
{
    // Create default state.
    HPANE h1 = m_MultiPaneCtrl.ConvertPaneToLine(NULL,false);
        m_MultiPaneCtrl.AddTab(h1,tabs[0]);
        m_MultiPaneCtrl.AddTab(h1,tabs[1]);

    HPANE h2 = m_MultiPaneCtrl.AddPane(NULL);

        HPANE h3 = m_MultiPaneCtrl.ConvertPaneToLine(h2,true);
            m_MultiPaneCtrl.AddTab(h3,tabs[2]);

        HPANE h4 = m_MultiPaneCtrl.AddPane(h2);
            HPANE h5 = m_MultiPaneCtrl.ConvertPaneToLine(h4,false);
                m_MultiPaneCtrl.AddTab(h5,tabs[3]);

            HPANE h6 = m_MultiPaneCtrl.AddPane(h4);
                m_MultiPaneCtrl.AddTab(h6,tabs[4]);

    m_MultiPaneCtrl.SetLinesEqualPanesSize();
}

m_MultiPaneCtrl.Update();

该控件不执行任何绘图,为此,它会调用接口MultiPaneCtrl::Draw的方法。此外,为了确定边框和分隔线的厚度,它使用接口MultiPaneCtrl::IRecalc。用户操作由接口MultiPaneCtrl::Ability的方法定义。这包括在拖动时将窗格插入到特定区域的能力,以及显示系统按钮(关闭、菜单、滚动)。总的来说,为了使控件正常工作,需要设置样式。为此,您必须实现MultiPaneCtrl::IStyle接口的函数,并通过方法InstallStyle传递指向它的指针。除了指向上述接口的指针之外,您还必须返回指向接口ITabCtrlStyle的指针(请参阅关于TabCtrl此处的文章)。这将用于定义控件中所有选项卡的外观和行为。样式类对象(MultiPaneCtrl::IStyle接口的实现)必须在控件运行时存在。为此,您可以创建一个中间类,如DemoDlg.h文件中的MultiPaneCtrlComplex。如果您只使用一种样式,请使用模板类MultiPaneCtrlEx。样式的类名定义为模板参数,例如

MultiPaneCtrlEx< MultiPaneCtrlStyle_VS2003_client > m_MultiPaneCtrl;

已经创建了一些样式。例如,样式类似于Visual Studio 2003、2008、2010和2019中的停靠/浮动面板。要创建自己的样式,请参阅类MultiPaneCtrlStyle_VS2003_clientMultiPaneCtrlStyle_VS2008_client_classic等。

在通过鼠标拖动选项卡的过程中,为了更好的可视化,我们可以使用标记。它们会出现在窗格上,当您将鼠标悬停在上面时,它们会显示选项卡将要插入的位置。要使用它们,您需要调用SetDockingMarkers。此函数的一个参数是用于定义标记图形资源以及屏幕上标记部分的对象的,以创建标记的外观。此演示项目包含五个类(MarkersLayoutA...MarkersLayoutE),描述了标记的外观。

它们位于DemoDlg.h文件中,并允许您创建类似于VS2003、VS2008、VS2010和VS2019中使用的标记。要构建自己的标记,请使用这些类作为示例,另请参阅DockingMarkers.h文件中的信息。

通过调用DisableDockingMarkers函数,您可以选择不显示标记,而是用可以插入拖动选项卡的区域的轮廓显示来替换它们。

该控件不向父窗口发送消息,而是使用接口MultiPaneCtrl::Notify来通知事件。使用SetNotifyManager来设置指向您的MultiPaneCtrl::Notify实现的指针。

在添加或删除选项卡以及更改其属性和状态后,控件需要调用Update

控件状态的加载取决于MultiPaneCtrl::Tabs对象的内容。它应该存储在加载其状态(从注册表或CArchive)时可以添加到控件中的选项卡信息。您可以将MultiPaneCtrl::Tabs填充有关控件当前拥有的所有选项卡的信息(MultiPaneCtrl::GetAllTabs),或手动添加选项卡(MultiPaneCtrl::Tabs::Add)。

祝您好运!

历史

  • 2010年7月12日:初始版本
  • 2021年3月17日:更新
  • 2021年3月26日:添加了新样式和鼠标滚轮滚动选项卡
© . All rights reserved.