Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Unlimited Number of Switchable Views within a Splitter Window

0.00/5 (No votes)
19 Feb 2004 1  
Allows someone to have an unlimited number of views associated with a splitter pane

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 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) // id provided is usually diff. so use alternate
   {
     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 function 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 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); // get current view

   if (pOldView == NULL) // serious prob
  {
     return false;
  }

   CView* pNewView = (CView*) GetDlgItem(id); // get new view

   if(pNewView == NULL ) // bad view id or this is already the view we requested
   {
      return false;
   }

   CFrameWnd * mainWnd = (CFrameWnd *)AfxGetMainWnd();

   if (mainWnd == NULL) // serious prob
   {
     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; // upcast to CWnd ptr

  if (views.find(bCwnd) == views.end()) // search for CWnd ptr
  {
  return false;
  }

   UINT oldId = views[bCwnd]; // get id of this view for future lookup

   pOldView->SetDlgCtrlID(oldId); // reset view id, so we can look it up
}

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 its 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));

/******************* Add switchable views **************************/

   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 please tell 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

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here