• Download demo project - 241 KB
  • Download source - 145 KB

Sample Image

Introduction

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 dialog. 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 with them and related 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.

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 a control and you can not delete it.
  2. In all operations, instead of a handle of the root pane (GetRoot), 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, create a child pane, and it passes all the tabs to the convertible pane.
  5. When you delete a pane, its tabs pass to the parent pane, provided that this pane doesn’t have child panes and it is the only child of its parent pane.

Here is an example of the algorithm for creating the MultiPaneCtrl (fill 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::ConvertToLine).

    Sample Image

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

    Sample Image

  5. Conversion of Pane2 to a line (MultiPaneCtrl::ConvertToLine).

    Sample Image

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

    Sample Image

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

    Sample Image

Removing panes from the MultiPaneCtrl. Example 1:

  1. Initial state.

    Sample Image

  2. Removing Pane3.

    Sample Image

  3. Removing Pane2.

    Sample Image

Remove panes from MultiPaneCtrl. Example 2:

  1. Initial state.

    Sample Image

  2. Removing Pane2.

    Sample Image

For each pane which doesn’t have child panes, by default, a TabCtrl control is created. It is described in my previous article, located at http://www.codeproject.com/KB/tabs/TabCtrl.aspx. 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 particular tabbed pane or call methods of the MultiPaneCtrl class. 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:

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_LINESATROOT |    TVS_HASLINES,CRect(0,0,0,0),this,300)==0 ||   m_Tree2.Create(WS_CHILD | TVS_HASBUTTONS | TVS_LINESATROOT |    TVS_HASLINES,CRect(0,0,0,0),this,301)==0 ||   m_Edit1.Create(WS_CHILD | ES_MULTILINE,CRect(0,0,0,0),this,302)==0 ||   m_List1.Create(WS_CHILD | LVS_REPORT,CRect(0,0,0,0),this,303)==0 ||   m_List2.Create(WS_CHILD | LVS_REPORT,CRect(0,0,0,0),this,304)==0)    return -1;m_Tree1.InsertItem(_T("CTreeCtrl 1"));m_Tree2.InsertItem(_T("CTreeCtrl 2"));m_Edit1.SetWindowText(_T("CEdit 1"));m_List1.InsertColumn(0,_T("CListCtrl 1"),LVCFMT_LEFT,100);m_List1.InsertItem(0,_T("Item 1"));m_List2.InsertColumn(0,_T("CListCtrl 2"),LVCFMT_LEFT,100);m_List2.InsertItem(0,_T("Item 1"));// Creation of MultiPaneCtrl object.if(m_MultiPaneCtrl.Create(this,WS_CHILD | WS_VISIBLE,    CRect(0,0,400,300),100/*id of MultiPaneCtrl*/)==false)    return -1;CImageList imagelist,imagelistDis, imagelistSys;CBitmap bmp,bmpDis, bmpSys;imagelist.Create(16,16,ILC_COLOR24 | ILC_MASK,7,0);bmp.LoadBitmap(IDB_BITMAP1);imagelist.Add(&bmp,RGB(255,0,255));imagelistDis.Create(16,16,ILC_COLOR24 | ILC_MASK,7,0);bmpDis.LoadBitmap(IDB_BITMAP2);imagelistDis.Add(&bmpDis,RGB(255,0,255));imagelistSys.Create(14,14,ILC_COLOR24 | ILC_MASK,7,0);bmpSys.LoadBitmap(IDB_BITMAP3);imagelistSys.Add(&bmpSys,RGB(255,0,255));m_MultiPaneCtrl.SetImageLists(&imagelist,&imagelistDis);m_MultiPaneCtrl.SetSystemImageList(&imagelistSys);m_MultiPaneCtrl.SetCursors(IDC_CURSOR1,IDC_CURSOR2,                IDC_CURSOR3,IDC_CURSOR4,IDC_CURSOR5);m_MultiPaneCtrl.SetDockMarkers(MarkersLayoutC(),                DockingMarkers::Params(40,true,14),50);m_MultiPaneCtrl.RemoveTabEnable(true);m_MultiPaneCtrl.DragTabEnable(true);// Loading state or creation default state.try{    MultiPaneCtrl::Tabs tabs;    tabs.Add(m_Tree1,_T("Tree1"),-1);    tabs.Add(m_List1,_T("List1"),0);    tabs.Add(m_Edit1,_T("Edit1"),1);    tabs.Add(m_List2,_T("List2"),2);    tabs.Add(m_Tree2,_T("Tree2"),3);    //     if(m_MultiPaneCtrl.LoadState(AfxGetApp(),_T("MultiPaneCtrl"),                        _T("State"),&tabs,false)==false)    {        // create default state.        HPANE h1 = m_MultiPaneCtrl.ConvertToLine(NULL,false);        m_MultiPaneCtrl.AddTab(h1,tabs[0]);        m_MultiPaneCtrl.AddTab(h1,tabs[1]);        HPANE h2 = m_MultiPaneCtrl.Add(NULL);        HPANE h3 = m_MultiPaneCtrl.ConvertToLine(h2,true);        m_MultiPaneCtrl.AddTab(h3,tabs[2]);        HPANE h4 = m_MultiPaneCtrl.Add(h2);        HPANE h5 = m_MultiPaneCtrl.ConvertToLine(h4,false);        m_MultiPaneCtrl.AddTab(h5,tabs[3]);                HPANE h6 = m_MultiPaneCtrl.Add(h4);        m_MultiPaneCtrl.AddTab(h6,tabs[4]);        m_MultiPaneCtrl.SetEqualPaneSize();    }}catch(std::bad_alloc &){    return -1;}m_MultiPaneCtrl.Update();

The control doesn’t perform any drawing, and for this, it calls methods of the interface MultiPaneCtrlDraw. Also, for determining the thickness of the border and splitters, it uses an interface IMultiPaneCtrlRecalc. User operations are defined by methods of the interface MultiPaneCtrlUserAbility. 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 theIMultiPaneCtrlStyle 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 article about TabCtrl). This will be used to define the appearance and behavior of all tabs in the control. An object of the class of the style must exist during the working of the control. To do this, you can create an intermediate class like MultiPaneCtrlComplex. 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:

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, and 2010. To create your own styles, see the classes MultiPaneCtrlStyle_VS2003_client,MultiPaneCtrlStyle_VS2008_client_classic etc. The class MultiPaneCtrlRecalcStub creates a default implementation for the functions of the IMultiPaneCtrlRecalc interface. You can use it to create your own style objects.

In the process of pulling tabs with the mouse for better visualization, we ca use docking markers. They appear on the panes, and when you hover on them, they show the position of the future insertion of the pulling tab. To use it, you need to call SetDockMarkers. One of the parameters of this function is an object for defining graphic resources and their location to create the appearance of the marker. Three objects have been created and used in the demo of this article. They are MarkersLayoutAMarkersLayoutB, and MarkersLayoutC to set markers look similar to the markers used in VS2008, VS2005, and VS2010, respectively. To construct your own markers, use these classes as examples, also see information in the DockingMarkers.h file.

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

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

The loading state of the control is based on the content of the object MultiPaneCtrl::Tabs. It must keep information about tabs in the Registry or another archive, for use when loading. You can load information from the working control (MultiPaneCtrl::SaveTabs) or add tabs manually (MultiPaneCtrl::Tabs::Add).

By default, all drawing is based on double buffering, it excludes any blinking. If you want, useVirtualWindow::DoubleBuffering(false) to disable double buffering.

Good luck.