Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C++

MultiPaneCtrl - Create Hierarchically Nested Areas

Rate me:
Please Sign up or sign in to vote.
4.98/5 (153 votes)
26 Mar 2021Public Domain5 min read 228.9K   17.7K   351   120
A control that allows you to create multiple tabbed regions that can be dragged to another location with the mouse.
In this article, you will learn about a control that allows you to create hierarchically nested areas, each of which may have tabs. This control is based on the CWnd class and can be placed as a child window anywhere, e.g., in the client area of a frame or in a dialog.

Sample Image

Introduction

The control allows you to create hierarchically nested areas, each of which may have tabs. Using the mouse, the user has the ability to move tabs and their child windows. This allows the user to customize the location and size of working areas to their liking. One possible use of MultiPaneCtrl is as a window filling the client area of the frame. The result is a simple, easily configurable interface.

This control is based on the CWnd class and can be placed as a child window anywhere, for example, in the client area of a frame or in a dialog.

Using the Code

Each pane can be in one of two states:

  1. Be empty (do not have child panes and tabs) or have tabs
  2. Have child panes (to be a line)

The current state is determined by the function IsLine. Many functions can be called for panes in only one of two states.

The basic rules for working with the control are:

  1. Root pane is created when you create MultiPaneCtrl and you cannot delete it.
  2. In all operations, instead of a handle of the root pane (GetRootPane), you can use NULL.
  3. Adding tabs and converting to a line can only be done for panes that don’t have child panes.
  4. When you convert a pane to a line, a child pane is created for it, which is passed all the tabs of the converted pane.
  5. When you delete a pane, its tabs pass to the parent pane, provided that the pane being deleted is the only child of its parent pane.

Here is an example of the algorithm for creating the MultiPaneCtrl (add panes and tabs):

  1. Initial state:

    Sample Image

  2. Adding tabs to the root pane (MultiPaneCtrl::AddTab):

    Sample Image

  3. Conversion of the root pane to a line (MultiPaneCtrl::ConvertPaneToLine):

    Sample Image

  4. Adding a child pane to the root pane (MultiPaneCtrl::AddPane):

    Sample Image

  5. Conversion Pane2 to a line (MultiPaneCtrl::ConvertPaneToLine):

    Sample Image

  6. Adding a child pane to Pane2 (MultiPaneCtrl::AddPane):

    Sample Image

  7. Adding tabs to Pane3 and Pane4 (MultiPaneCtrl::AddTab):

Sample Image

Removing panes from MultiPaneCtrl. Example 1:

  1. Initial state:

    Sample Image

  2. Removing Pane3 (MultiPaneCtrl::DeletePane):

    Sample Image

  3. Removing Pane2 (MultiPaneCtrl::DeletePane).

    Sample Image

Removing panes from MultiPaneCtrl. Example 2:

  1. Initial state:

    Sample Image

  2. Removing Pane2 (MultiPaneCtrl::DeletePane):

    Sample Image

For each pane which doesn’t have child panes, by default, a TabCtrl control is created. This control is also developed by me, and you can find it at https://www.codeproject.com/Articles/84524/TabCtrl . All TabCtrl controls are created as child windows for MultiPaneCtrl and do not form additional levels of nesting. This is especially important because modern versions of Windows have a limited number of nesting windows in each other, which often does not exceed 13-15. The function GetTabCtrl allows you to get a pointer to TabCtrl. It can be called only for a pane which is not a line. You can use this pointer for a specific tabbed pane, or you can simply call methods of the MultiPaneCtrl class to manipulate the tabs. For example, to add a tab to a pane, use the function MultiPaneCtrl::AddTab.

To create the control and add elements to it, you can do the following steps:

C++
#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();

The control doesn’t perform any drawing, and for this, it calls methods of the interface MultiPaneCtrl::Draw. Also, for determining the thickness of the border and splitters, it uses an interface MultiPaneCtrl::IRecalc. User operations are defined by methods of the interface MultiPaneCtrl::Ability. These include the ability to insert the pane in a certain area when you drag it with the mouse, as well as showing system buttons (Close, Menu, Scroll). In general, for proper working of the control, it needs to set the style. To do this, you must implement the functions of the MultiPaneCtrl::IStyle interface and pass a pointer to it using the method InstallStyle. Besides pointers to the above interfaces, you must return a pointer to the interface ITabCtrlStyle (see an article about TabCtrl here). This will be used to define the appearance and behavior of all tabs in the control. The style class object (implementation of the MultiPaneCtrl::IStyle interface) must exist while the control is running. To do this, you can create an intermediate class like the MultiPaneCtrlComplex described in the DemoDlg.h file. If you are working with only one style, then use the template class MultiPaneCtrlEx. The class name of the style is defined as a template parameter, for example:

C++
MultiPaneCtrlEx< MultiPaneCtrlStyle_VS2003_client > m_MultiPaneCtrl;

Some styles have already been created. For example, styles similar to the docking/floating panels in Visual Studio 2003, 2008, 2010, and 2019. To create your own styles, see the classes MultiPaneCtrlStyle_VS2003_client, MultiPaneCtrlStyle_VS2008_client_classic, etc.

In the process of pulling tabs with the mouse for better visualization, we can use markers. They appear on the panes, and when you hover on them, they show the position of the future insertion of the tab. To use them, you need to call SetDockingMarkers. One of the parameters of this function is an object for defining the graphic resources of markers and the location of their parts on the screen to create the appearance of the marker. This demo project includes five classes (MarkersLayoutA ... MarkersLayoutE) describing the appearance of markers.

Image 14

They are located in the DemoDlg.h file and allow you to create markers similar to those used in VS2003, VS2008, VS2010 and VS2019. To construct your own markers, use these classes as examples, also see information in the DockingMarkers.h file.

By calling the DisableDockingMarkers function, you can opt out of displaying markers and replace them with an outline display of areas where a dragging tab can be inserted.

The control does not send messages to the parent window and uses an interface MultiPaneCtrl::Notify for the notification of the events. Use SetNotifyManager to set the pointer to your implementation of MultiPaneCtrl::Notify.

The control requires a call to Update after you add or delete tabs, as well as change its properties and state.

The loading of the control's state depends on the contents of the MultiPaneCtrl::Tabs object. It should store information about the tabs that can be added to the control when loading its state from the registry or CArchive. You can fill MultiPaneCtrl::Tabs with information about all the tabs that the control currently has (MultiPaneCtrl::GetAllTabs) or add tabs manually (MultiPaneCtrl::Tabs::Add).

Good luck!

History

  • 12th July, 2010: Initial version
  • 17th March, 2021: Update
  • 26th March, 2021: Added a new style and scrolling of tabs with the mouse wheel

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Software Developer
Canada Canada

Comments and Discussions

 
GeneralYou got my 5. Pin
Josef Manurung13-Aug-10 21:41
Josef Manurung13-Aug-10 21:41 
GeneralMy vote of 5 Pin
Md. Marufuzzaman10-Aug-10 21:40
professionalMd. Marufuzzaman10-Aug-10 21:40 
GeneralMy vote of 5 Pin
JongchanAhn9-Aug-10 15:33
JongchanAhn9-Aug-10 15:33 
GeneralIf you can file for handling different events like the Tab Pin
dnybz8-Aug-10 17:35
dnybz8-Aug-10 17:35 
GeneralRe: If you can file for handling different events like the Tab Pin
Aleh Baradzenka9-Aug-10 23:48
Aleh Baradzenka9-Aug-10 23:48 
GeneralI got a error 0xc000007b in 64bit mode Windows 7. Pin
yejianping27-Jul-10 16:55
professionalyejianping27-Jul-10 16:55 
GeneralRe: I got a error 0xc000007b in 64bit mode Windows 7. Pin
Aleh Baradzenka28-Jul-10 3:31
Aleh Baradzenka28-Jul-10 3:31 
GeneralRe: I got a error 0xc000007b in 64bit mode Windows 7. Pin
Aleh Baradzenka8-Aug-10 2:28
Aleh Baradzenka8-Aug-10 2:28 
Email from yejianping:

Dear Borodenko Oleg,
I found out the program is because manifest, comment this line in rc file, then the test program is OK.
//1 RT_MANIFEST "res\\TabCtrl.manifest"
thank you very much.
GeneralMy vote of 5 Pin
Member 51755827-Jul-10 0:54
Member 51755827-Jul-10 0:54 
GeneralMy vote of 5 Pin
pophelix25-Jul-10 1:19
pophelix25-Jul-10 1:19 
GeneralSomething Strange Pin
Rick York23-Jul-10 20:57
mveRick York23-Jul-10 20:57 
GeneralRe: Something Strange Pin
Aleh Baradzenka24-Jul-10 20:53
Aleh Baradzenka24-Jul-10 20:53 
GeneralRe: Something Strange Pin
Rick York26-Jul-10 13:37
mveRick York26-Jul-10 13:37 
GeneralRe: Something Strange Pin
Rick York26-Jul-10 14:08
mveRick York26-Jul-10 14:08 
GeneralRe: Something Strange Pin
Aleh Baradzenka27-Jul-10 0:14
Aleh Baradzenka27-Jul-10 0:14 
GeneralRe: Something Strange Pin
Rick York27-Jul-10 6:30
mveRick York27-Jul-10 6:30 
GeneralMy vote of 5 Pin
Bob Flynn23-Jul-10 11:55
Bob Flynn23-Jul-10 11:55 
GeneralMy vote of 5 Pin
Matth Moestl23-Jul-10 8:24
professionalMatth Moestl23-Jul-10 8:24 
GeneralMy vote of 5 Pin
redone21821-Jul-10 16:45
professionalredone21821-Jul-10 16:45 
GeneralMy vote of 5 Pin
Wyz21-Jul-10 14:57
Wyz21-Jul-10 14:57 
GeneralMy vote of 5 Pin
UltimaWeapon21-Jul-10 6:46
UltimaWeapon21-Jul-10 6:46 
GeneralMy vote of 5 Pin
CodeHead20-Jul-10 3:03
CodeHead20-Jul-10 3:03 
GeneralMy vote of 5 Pin
Tarek Ahmed Abdel Rahmane19-Jul-10 23:43
Tarek Ahmed Abdel Rahmane19-Jul-10 23:43 
GeneralNice work! [modified] Pin
Kraeven19-Jul-10 21:04
Kraeven19-Jul-10 21:04 
GeneralRe: Nice work! Pin
Aleh Baradzenka20-Jul-10 9:11
Aleh Baradzenka20-Jul-10 9:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.