Visual Studio.NET Style Tear Off Panes - Part I
This article discusses a framework that can be used to implement Visual Studio.NET style Tear Off panes. The framework uses the commonly used MFC classes to achieve the functionality. This article is the first in the series in which I intend to develop the framework in a step-by-step fashion.
Contents
Introduction
The Visual Studio.NET style framework for dropping/docking/tearing off panes and with support for auto hide features is becoming increasingly common. The framework allows for a better usage of the screen real estate and gives the user a wide set of options to maximize the use of this real estate based on one's convenience.
Figure 1.
Some amount of playing around with it made me realize that this calls for a neat framework. My goal was to realize it using the familiar MFC framework as the backbone but not relying too heavily on it. I have tried to use standard MFC controls as much as possible.
In this first article, we shall take a look at how the backbone for the framework is developed. Some features, such as auto hide, sliding panes, and floating panes will be avoided in this article; I plan to tackle these and other issues in a follow-up article.
The features that will be implemented in this article are:
- Nested pane structure.
- Repositioning of panes.
- Show/Hide panes.
- Drag'n'drop of panes with feedback about new position.
The FrameWork
Goals
Let us tackle these one by one by trying to list the goals the framework needs to achieve.
- Should be easily pluggable into a SDI/MDI framework.
- Should be able to accomodate normal view classes for the panes.
- Should allow dragging and dropping.
- Should allow hiding/unhiding of panes.
Design
Figure 2.
The design is pretty straightforward. The framework uses a class, called XTearOffPaneManager, to manage the panes. XTearOffPaneManager holds an array of panes. If you look at Figure 2, all the square blocks represent the individual panes. The blocks are either leaf nodes or have child nodes. XTearOffPaneManager uses a structure called XTearOffPaneInfo to keep information about these panes. Some of the attributes of the structure specify what alignment is allowed on the pane, if it is visible or not, the CWnd object pointer of the pane window, and a link to child nodes and parent nodes, if any. The red colored blocks are of the XTearOffSplitterWnd class that derives from CSplitterWnd and the blue colored blocks are of any class specified by the user when creating the panes.
Figure 3.
To insert a new pane aligned to the left of say, pane 1, the following steps are followed (refer to Figure 3):
- A new splitterWnd (D) is created; that means a new red block.
- Pane 1 is moved as a child of this splitterWnd.
- The new pane (5) is attached as the other child of this splitterWnd.
- The splitterWnd replaces the node for the initial parent (A) of Pane 1.
To move a pane to another location say, to move pane 1 to position to the left of pane 4, the following steps are followed:
- The parent of pane 1, i.e. pane A, replaces pane 4.
- Pane 4 is moved as a child of pane A.
- The previous sibling of pane 1, which was pane B, becomes the child of pane A's parent node if one existed.
To hide a pane, the steps followed are:
- The pane is first hidden.
- If as a result, no more panes in the splitter are visible, the splitter itself needs to be hidden. This process needs to be done all the way up.
Notes
- The pane inserted as the root pane can never be hidden. To show a pane, a similar logic is followed. For example, if a pane is made visible and its parent node is not visible, it is made visible too and so on all the way up.
- The parent child relationship exists at two levels, on the node level and on the window level. Whenever the parent child relationship among the nodes changes, a change in the parent-child relationship at the window level also needs to be changed. So, one can see explicit calls to SetParent in the code that implements the repositioning.
- Just as the window relationship is important, the concept of the control ID for the panes is important too. Having a proper control ID for the panes(here I do not mean the panes but the individual panes of the splitter window) is central to the proper functioning of the CSplitterWnd class. Hence, there are methods such as RecalcDialogIDs() that assist in properly calculating the IDs.
Essential components
Figure 4.
Below is a breakup of the components of this framework. We shall discuss some of these one by one
XTearOffPaneManager
The heart of the framework, it holds an array of panes. This is the public interface for achieving the goals aforementioned. All possible operations are exposed as public methods of this class. The goals of the framework translates to calls to public methods of this class. The development of the framework took place in the order in which the goals are listed.
- Goal 1—Should be easily pluggable into a SDI/MDI framework.
Application of such a framework applies mostly to SDI/MDI apps. The framework initialization starts with adding a root pane, using the class method.
CWnd* InsertRootPane(CWnd* pPaneToInsert,
const int nPaneID,
const DWORD dwAlignFlags)
Parameters
pPaneToInsert—pointer to pane to insert
nPaneID—ID of the pane to insert
dwAlignFlags—allowed alignments for any drop onto this pane
Pointer to the pane inserted if successful, NULL otherwise.
RemarksIn a normal SDI/MDI case, pPaneToInsert would be the view associated with the SDI/MDI window. The pane added as root pane cannot be hidden. Hence, it is important to ensure that you specify the right one here. No matter what panes you hide and show, this one is always visible. It's similar to an SDI app always having at least one view. All other panes that are inserted henceforth will be inserted into or around this pane. This is the first step one would have to do to use the panemanager. nPaneID is the ID you want to associate with the pane. Additionally, one can specify what are the valid align flags that this pane allows. For example, if you would want this pane to allow other panes to be dragged onto its left edge, you could specify TEAROFF_ALIGN_LEFT as the third parameter. The default is TEAROFF_ALIGN_ANY, which means that it allows panes to be dropped into any of the four edges.
To achieve this, there are two methods that allow inserting panes. One takes a runtime class info as parameter, in which case the XTearOffPaneManager class will be responsible for creating the window, and another that directly takes a CWnd* as a parameter.
CWnd* InsertPane(const int nRefPane,
const XTearOffInsertLocation eInsertAt,
CWnd* pPaneToInsert,const int nPaneID,
const DWORD dwAlignFlags =
TEAROFF_ALIGN_ANY);
CWnd* InsertPane(const int nRefPane,
const XTearOffInsertLocation eInsertAt,
CRuntimeClass* pRuntimeClass,const int
nPaneID, const DWORD dwAlignFlags =
TEAROFF_ALIGN_ANY);
Parameters
nRefPane—This is the ID of the target pane in which the new pane will be inserted
eInsertAt—This is the location in the refPane where the new pane has to be positioned
pPaneToInsert—The new pane to be inserted
nPaneID—The pane ID for the new pane
dwAlignFlags—The align flags for the new pane
pRuntimeClass—Run time class of new pane to be inserted
Pointer to the pane inserted if successful, NULL otherwise.
RemarksThis method is used subsequently to insert additional panes. There are two overloaded methods, one that takes a CWnd* pointer and one that takes a RUNTIME_CLASS info. In either case, you need to specify the pane into which you want to drop and the location where you would want to insert it. If, for some reason, the nPaneID specified has already been added, or if the reference pane does not allow inserting at the specified location, the method returns NULL.
The actual mechanism of a drag/drop operation is implemented in the XTearOffDragContext class. However, the XTearOffPaneViewManager class provides a public method for the final step of the drag drop/operation, which is the repostioning of the panes following the drag operation. To achieve this, XTearOffViewManager exposes a public method MovePane. The XTearOffDragContext class uses this method for producing the final result.
BOOL MovePane(const int nPaneToMove,const int nTargetPane,
const XTearOffInsertLocation eInsertAt)
Parameters
nPaneToMove—Pane to be moved to new location
nTargetPane—Drop target for the pane
eInsertAt—Location where to position the pane to insert
TRUE if move successful, FALSE otherwise.
RemarksUsed to move a pane from one position to the other.
The framework should allow hiding and unhiding of panes to allow the user to hide the pane when not needed and unhide it when required. XTearOffPaneViewManager exposes a public method, ShowPane, to achieve this.
BOOL ShowPane(const int nPane, const BOOL bShow)Parameters
nPane—ID of pane to show/hide
bShow—FALSE to hide, show otherwise
TRUE if move successful, FALSE otherwise.
RemarksUsed to show or hide a pane. Note that this method fails on the root pane which is always visible.
XTearOffDragContext
This is a class that is used to implement the drag and drop operation for the panes. The code for this class has been lifted from MFC's CDockContext class, only made a little simpler. The only hook provided is in the WM_MOUSEMOVE handler and WM_LBUTTONUP handlers, wherein the XTearOffDragContext keeps querying the XTearOffPaneManager class for the draw rect to give proper feedback to the user of the result of the drag operation. Here, two helper methods of XTearOffPaneManager, GetPaneIDFromPoint() and GetInsertLocation(), give the necessary information for XTearOffDragContext to draw the proper drag rectangle. The XTearOffPaneManager, given the screen coords of the point, checks whether the point lies within any of the visible panes and whether it allows drop. If it does, it returns the drop rectangle back to the caller. On LBUTTONUP, the DragContext class will call the MovePane method to finally execute the move.
XTearOffSplitterWnd
This is derived from CSplitterWnd and provides a few helper methods to change the row count and column count. Note that, in this framework, a visible splitter can have just one pane (the other one being hidden).
XTearOffPaneView
This is derived from CView and demonstrates the use of the XTearOffDragContext class to implement drag and drop. This view class will be later enhanced to add support to host tabs and to support tearing off of individual tabs and dropping them on other panes. This will be implemented in the follow-up article. These are some things that need to be done to support dragging of your own view class.
- Add a XTearOffDragContext member to the class
- Add a XTearOffPaneManager* member to the class and set this member to the PaneManager object pointer that is used by the application.
- We need to initiate the drag somehow. For this, XTearOffPaneView traps WM_NCLBUTTONDOWN for HTCAPTION case, and calls StartDrag on the dragcontext member. The dragging and dropping is taken care of by the context class henceforth.
- Additionally, XTearOffPaneView also traps WM_NCLBUTTOWN for HTCLOSE case, to Hide the pane window if clicked on the close button.
How to Use the Framework
This framework is suited for SDI or MDI applications. A typical way to use it would be:
- Include the files XTearOffPaneManager.cpp/.h, XTearOffSplitterWnd.cpp/.h, XTearOffPaneView.cpp/.h, and XTearOffDragContext.cpp/.h to your project.
- Add a XTearOffPaneManager object to the mainframe or childframe class.
class CMainFrame : public CFrameWnd
{
protected: // create from serialization only
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)
XTearOffPaneManager m_oPaneManager;
...
};
class CMainFrame : public CFrameWnd
{
protected: // create from serialization only
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)
// Attributes
public:
// Operations
public:
void InitializePanes();
.....
};
void CMainFrame::InitializePanes()
{
CString szListText;
//1. Root pane
//Insert the root pane. This always the first step
//Root pane ID = 1. The active view is of XTearOffPaneView
//class
XTearOffPaneView* pRootPane =
(XTearOffPaneView*)m_oPaneManager.InsertRootPane
(GetActiveView(),1);
ASSERT(pRootPane);
//to allow dragging on this pane, we need to set the pane
//manager to the XTearOffPaneView object
pRootPane->SetPaneManager(&m_oPaneManager);
szListText.Format(_T("Pane ID : %d
Allows : TEAROFF_ALIGN_ANY
Class : XTearOffPaneView"),1);
m_oPage.m_oPaneListItems.Add(szListText);
//2. Add a CEditView now, for e.g. let us align it to left of
//root pane i.e. pane 1
CEditView* pEditView =
(CEditView*)m_oPaneManager.InsertPane
(1,XTearOffInsertAtTop,RUNTIME_CLASS
(CEditView),2);
ASSERT(pEditView);
GetActiveDocument()->AddView((CView*)pEditView);
//let us populate some data into it now
pEditView->GetEditCtrl().SetWindowText(_T("Sample edit text"));
szListText.Format(_T("Pane ID : %d
Allows : TEAROFF_ALIGN_ANY
Class : CEditView"),2);
m_oPage.m_oPaneListItems.Add(szListText);
//3. Add a CListView now, for e.g. let us align it to right
//of edit view pane i.e. pane 2
CListView* pListView =
(CListView*)m_oPaneManager.InsertPane
(2,XTearOffInsertAtRight,RUNTIME_CLASS(CListView),3);
ASSERT(pListView);
GetActiveDocument()->AddView((CView*)pListView);
//let us populate some data into it now
pListView->GetListCtrl().ModifyStyle(0,LVS_REPORT);
pListView->GetListCtrl().InsertColumn(0,
_T("Column header"),LVCFMT_LEFT,100);
pListView->GetListCtrl().InsertItem(0,
_T("first list item"));
pListView->GetListCtrl().InsertItem(0,
_T("second list item"));
szListText.Format(_T("Pane ID : %d
Allows : TEAROFF_ALIGN_ANY
Class : CListView"),3);
m_oPage.m_oPaneListItems.Add(szListText);
//4. Add a CTreeView now, for e.g. let us align it to the
//bottom of the edit view pane i.e. pane 2
CTreeView* pTreeView =
(CTreeView*)m_oPaneManager.InsertPane
(2,XTearOffInsertAtBottom,RUNTIME_CLASS(CTreeView),4);
ASSERT(pTreeView);
GetActiveDocument()->AddView((CView*)pTreeView);
HTREEITEM hItem = pTreeView->
GetTreeCtrl().InsertItem(_T("Root"));
pTreeView->GetTreeCtrl().InsertItem(_T("First child"),
hItem);
pTreeView->GetTreeCtrl().InsertItem(_T("Second child"),
hItem);
pTreeView->GetTreeCtrl().Expand(hItem,TVE_EXPAND);
szListText.Format(_T("Pane ID : %d
Allows : TEAROFF_ALIGN_ANY
Class : CTreeView"),4);
m_oPage.m_oPaneListItems.Add(szListText);
//5. Add another XTearOffPaneView now, which supports
//dragging, for e.g. let us align it to the bottom of the
//tree view pane i.e pane 4
XTearOffPaneView* pTearOffView =
(XTearOffPaneView*)m_oPaneManager.InsertPane
(4,XTearOffInsertAtLeft,RUNTIME_CLASS(XTearOffPaneView),5);
ASSERT(pTearOffView);
GetActiveDocument()->AddView((CView*)pTearOffView);
pTearOffView->SetPaneManager(&m_oPaneManager);
szListText.Format(_T("Pane ID : %d
Allows : TEAROFF_ALIGN_ANY
Class : XTearOffPaneView"),5);
m_oPage.m_oPaneListItems.Add(szListText);
RecalcLayout();
}
BOOL CTearOffPanesApp::InitInstance()
{
................
................
// The one and only window has been initialized, so show
// and update it.
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
((CMainFrame*)m_pMainWnd)->InitializePanes();
return TRUE;
}
About the Included Demo
I have included a demo application demonstrating the use of the methods exposed by XTearOffPaneManager class. It is an SDI application. In addition to the initial layout, you can add your own panes, show/hide them, and execute each of the XTearOffPaneManager's methods by using the Pane Manager -> Manage panes menu. A dialog is presented with a list of three methods and an execute button for each. On pressing execute, the corresponding method is called with the parameters shown in the GUI. This can be used as a unit test tool. The code PaneManipulationPage class will show a typical way of calling these APIs.
What's Next
In the next article I wish to address some of these issues:
- Bug fixes
- Addition of tabs to the panes
- Drag and drop tabs within and across panes
- Hide/Show at tab level
- Auto hide enabling of tabs
- Floating panes
- Notification of events

Comments
Problem with MDI project
Posted by werner.keilholz on 06/28/2006 09:36amHi, I could not get this to work with my MDI project: InitializePanes() adds the current View to the pane manager, using GetActiveView(). But in MDI projects, GetActiveView() returns NULL (there is no current View in MDI applications, ). Any ideas would be appreciated... Thanks, Werner
-
-
-
-
-
ReplyGot it to work...
Posted by werner.keilholz on 07/20/2006 10:08amI finally found a solution that does it all (including MDI) - in case anyone else has the problem, the solution is at http://www.codeproject.com/docking/sizecbar.asp or http://www.datamekanix.com/ (the samples are SDI, but MDI works fine also by just following the instructions). Thanks Cristi Posea ! Werner
ReplyRe: alternative
Posted by werner.keilholz on 07/05/2006 06:43amSorry, I don't see the slider in this example ... ? (I cannot change the size of the bar added on the left). I need the thing to be resizable (hence the need foir a slider), and add a text edit to it. You're right, the goal is to have the slider available all the time, no matter if there is a child frame/current document.
Replyalternative
Posted by kirants on 07/04/2006 12:36pmFrom what you say, it sounds like you want to have the slider to be available for the mainframe all the time, whether a child frame is there or not. In that case, tearOff panes is overkill. I would suggest, you explore the idea of hosting a docking bar in the mainframe. Please look at this thread: http://www.codeguru.com/forum/showpost.php?p=1015768&postcount=4 The idea here is to show a dockbar at the left. Within this dockbar you can create any child control you want. Is this suitable ?
ReplyGetActiveDocument in MDI
Posted by werner.keilholz on 07/04/2006 04:23amThanks a lot for your answer, I appreciate your help. My goal is to add a slider to the bottom of an MDI application, like the output window in the Developer Studio. I tried to follow the steps in the "How to Use the Framework" section (a great article by the way !), both adding the code to ChildFrm and CMainFrame. But in both cases, InitializePanes() fails, because it uses GetActiveDocument() to add a view. (In MDI applications, no active document exists upon startup, so GetActiveDocument returns NULL). Also, wouldn't adding the code to ChildFrm split each document window, rather than add a window at the bottom of the main frame ? I also looked at chapter 4, which is exactly what I need except that it is SDI. I tried to compile the AppWizard (hoping for a SDI/MDI option in it), but bldguid.h seems to be missing. (I don't know how to add the .awx file to my .NET 2003 application but I think it was made for an earlier version). Thanks again for your ideas ! Werner
ReplyNot sure what you are attempting
Posted by kirants on 06/29/2006 12:27amHi, thanks for posting your concern. Am not sure what you are attempting though. If you could explain in more detail, probably I could suggest something. Have you checked the "How to Use the Framework" section ? Herein, I suggest what you need to do. In case of MDI, you need to do all this in the ChildFrm class and not the mainframe class. In the meantime, have you checked Part IV of the series and seen if that fits your needs ?
ReplyNot pre-formatted
Posted by kirants on 08/29/2005 01:16pmFirst line. Second line. Third paragraph. This is a long line which I am typing in here to see if the text wraps favorably well after having submitted as comment. My guess is this will appear without the carriage returns.
-
Reply
ReplyPre formatted
Posted by kirants on 08/29/2005 01:20pmcannot download demo and sources
Posted by palani_vs on 09/04/2004 12:11amhi can anyone help me to download the files. palani vs
-
ReplyI see no problem
Posted by kirants on 09/07/2004 11:31amHi there, I have no problem downloading the zip files. What kind of problem are you facing ? Do you want me to email those files to you ? PM me the the email ID if I should.
ReplyNICE !
Posted by diego.moita on 05/10/2004 10:09pmVery interesting. I gave it a rate of "Excelent" and will use it in my code. You said you'll add floating panes to the next version. I think it is the same as overlaped window, right? That would be very cool. When will the next version be available?
-
ReplyThanks
Posted by kirants on 05/11/2004 11:03amGlad that it is useful for you in some way. I'm not sure when I'd be ready for the next article, but I'm working on it already, Also, not sure if I'd be able to add floating panes ( yes, an overlapped window ) in the next one. Maybe I still can. It's a little difficult to say since I have to do all this in whatever time is available after work. Anyways, stay tuned !!!
Replypretty cool~
Posted by xufeisjtu on 05/06/2004 07:09amcool article~
-
ReplyThank you..
Posted by kirants on 05/06/2004 11:30amPlease let me know of issues you find or recommendation. I may be able to incorporate them in the follow up article.
Reply