
Introduction
This article details how one can use my CMultiSplitterView class that allows
one to implement multiple switchable views within a splitter pane. In the image
above the grey area is the first view. By going to Menu bar and clicking 'View
> Show 2nd View' the program dynamically changes the first view to the second
view.
This class allows you to switch to any view you create in one function call,
without doing anything ! Adding a view is as simple as one function call as
well. Now, on to the details ...
Code Details
The core of the everything is located in MultiSplitterView.cpp and MultiSplitterView.h.
Adding a view to the splitter pane is done here:
bool CMultiSplitterView::AddSwitchableView(UINT id,
CRuntimeClass * pView,
CCreateContext* pContext,
CRect & size, bool isFirstView, UINT altId)
{
CWnd* pWin;
DWORD style;
pWin = (CWnd*) pView->CreateObject();
style = WS_CHILD ;
if (isFirstView)
{
style |= WS_VISIBLE ;
}
pWin->Create(NULL, NULL, style, size , this, id, pContext);
if (isFirstView) {
views[pWin] =altId ;
}
else
{
views[pWin] = id;
}
return true;
}
The first param is the id of the view that you associate with the view so you
can look it up easily. The second param is created by calling RUNTIME_CLASS(SomeViewClass)
which returns a pointer to a CRuntimeClass class. The third param is the CCreateContext
given to you by the OnCreateClient funtion in your CMainFrame class. The fourth
param is the dimensions of the window. Now the final two are optional and will
only be used with the first call to AddSwitchableView(). Because the id for
the first param is the id of the pane the first view will be set to, I needed
to pass in the real id that the user of this class is associating with the view,
hence the last param called altId.
Ok, so we pass in all these params, then we create an object that is of the
type of the runtime class that was passed in and cast it to its base class
CWnd
for creation and storage. Notice in the call to Create for the CWnd object I
always use the id passed and the this pointer which associates the view with
the splitter. I then store the pointer to the CWnd as the 'key' within a map<>
and use the id or alternative id as the value for later lookup.
Next we need to switch the view dynamically so that one is seen and another
is hidden.
The following code handles the switching of any number of views:
bool CMultiSplitterView::SwitchView(UINT id, int paneRow, int paneCol)
{
CView* pOldView = (CView*) GetPane(paneRow, paneCol);
if (pOldView == NULL) {
return false;
}
CView* pNewView = (CView*) GetDlgItem(id);
if(pNewView == NULL ) {
return false;
}
CFrameWnd * mainWnd = (CFrameWnd *)AfxGetMainWnd();
if (mainWnd == NULL) {
ASSERT(false);
return false;
}
if(mainWnd->GetActiveView() == pOldView)
mainWnd->SetActiveView(pNewView);
pNewView->ShowWindow(SW_SHOW);
pOldView->ShowWindow(SW_HIDE);
pNewView->SetDlgCtrlID( IdFromRowCol(paneRow, paneCol));
CWnd * bCwnd =(CWnd *)pOldView;
if (views.find(bCwnd) == views.end()) {
return false;
}
UINT oldId = views[bCwnd];
pOldView->SetDlgCtrlID(oldId);
}
Ok, so now the user or the GUI has called SwitchView() with some
id and the row and col of the splitter to which the view belongs. We first get
a pointer to the current view using the row and col provided by the user. Next
we get the new view ( the view we are about to show which is associated with
the id passed in). We verify that they aren't NULL and then get
the CFrameWnd pointer from the main window. We then compare the
active view to the old view to see if they are the same (which they should be)
and set the new view to the view requested. After we hide and show the old and
new view, it is essential that we set the id of the new view to that of the
id from the row and col that the view is associated with since it is the child
window of the pane. We next lookup the id of the old view in the map<>
because it's control id is still the id from IdFromRowCol(). We
then reset the control id to that stored in the map<> so that the view
pointer can be retrieved later.
How to use:
Within your CMainFrame you must first include the MultiViewSplitter.h
header file. Next declare a member var of type CMultiViewSplitter.
Next in OnCreateClient() within CMainFrame:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
CRect r;
GetWindowRect(r);
m_SplitterFirst.CreateStatic(this,1,2);
m_SplitterFirst.CreateView(0,0, RUNTIME_CLASS( CSomeView) ,
CSize(r.Width() *0.14, r.Height()), pContext);
m_SplitterSeconde.CreateStatic(&m_SplitterFirst, 2, 1, WS_CHILD | WS_VISIBLE |
WS_BORDER, m_SplitterFirst.IdFromRowCol(0, 1));
m_SplitterSeconde.AddSwitchableView(m_SplitterSeconde.IdFromRowCol(0, 0),
RUNTIME_CLASS(CFirstView) ,pContext, CRect(0,0,r.Width(), r.Height()) ,
true , FIRST_VIEW);
m_SplitterSeconde.AddSwitchableView(SECOND_VIEW,
RUNTIME_CLASS( CSecondView), pContext,
CRect(0,0,r.Width(), r.Height()*0.60) );
...
}
The only part that really matters for this example is the part after the comment
that says 'Add Switchable views'. I first split the first splitter called m_SplitterFirst
into 2 columns. Then I create the second splitter as a child of the first splitter
and split that into two rows. Now this is all immaterial to you because you
may decide to split your window however you see fit.
For the first call to AddSwitchableView(), be sure to use the id from the splitter
provided by calling m_SplitterSeconde.IdFromRowCol(x, x), and provide the alternate
view ID as the last param and true as the second to last param to indicate this
is the first view.
Inside of the call to RUNTIME_CLASS( x ), add the class name of
whatever class is encapsulating your view also.
Wow, that's it ! Now whenever you want to switch views dynamically, just call
SwitchView( x ), where x is the id of the view. Please download
the example demo project for a full illustration of how this works.
If anyone votes this article as a low score (less that 4 or 5) can you give
me the courtesy of telling me why you gave me that score and what I can do better.
Suggestions, kudos, problems are welcome. Thanks for your time.
Other Useful Functions
GetViewPtr(UINT id, int paneRow, int paneCol) - Gets a base class
CWnd ptr
associated with the splitter.