Click here to Skip to main content
15,915,702 members
Articles / Desktop Programming / MFC
Article

Reusable base class for SplitterWnd

Rate me:
Please Sign up or sign in to vote.
4.93/5 (77 votes)
2 Sep 2001CPOL3 min read 369.3K   11.6K   136   86
A class derived from CSplitterWnd which makes splitting and switching of views simple.

Image 1

Introduction

Have you ever spent time trying to get the splitting windows right, then added a method of switching views and even worse, tried to make the individual splitters to expand over the whole window? And then you want to make the whole damn (sorry) thing persistent for the next time you start the program? Well I did, and every time I had to look up in old code and every time I made errors and every time it took me a long time to get it to work the way I had it planned.

Well, I now sat down and solved it - once for all. By writing a reusable base class. Of course, I had a little help from many useful articles posted by this and other sites. The methods ShowColumn and HideColumn are taken from an article posted by Oleg Galkin on www.codeguru.com, the articles by Caroline Englebienne, Adrian Roman and Dongik Shin helped me writing the methods to support switchable views.

And now I'm ready to share once more. You may use this class for your own projects as you like, but it is AS IS, use at your own risk, I cannot be held responsible for any damage...blah blah blah ...

To use this base class is simple, however there are a few things you need to consider, else you will get one of the many asserts from within the CSplitterWnd.

This class uses only static splitters and will only split each window once, either vertically or horizontally. Once the window is split, you can split one ore both panes again and again and... Have a look at the screen shot above. I simple split the first window into two panes, a left and right one. Then I split the left pane again, this time into a top and bottom pane. The bottom pane is now split again vertically and so on.

To do this, you need to include the source files ST_splitterwnd.h and ST_splitterwnd.cpp into your project.

Then add a ST_SplitterWnd member for each split to your CMainFrame:

ST_SplitterWnd* m_pSplitterWnd;
ST_SplitterWnd* m_pSplitterWnd2;
ST_SplitterWnd* m_pSplitterWnd3;
ST_SplitterWnd* m_pSplitterWnd4;

In CMainFrame::OnCreateClient, you need to create the first ST_SplitterWnd:

m_pSplitterWnd = new ST_SplitterWnd();
m_pSplitterWnd->Create(this,NULL,NULL,pContext,true);

The Create method is defined as follows:

    bool Create(CWnd* pParentWnd, // this will do
      CRuntimeClass* pView1, // Add the view for 
                             // the left(or top) pane or NULL
      CRuntimeClass* pView2, // Add the view for
                             // the right(or bottom) pane or NULL
      CCreateContext* pContext, // pass the pContext pointer
                                // from the OnCreateClient
      bool bVertical, // set to true if the window
                      // is split vertically else false
      int nID = AFX_IDW_PANE_FIRST); // this parameter
                                     // does not need redefinition
{

Note: If you want to split a window into further subdivisions, pass NULL to the pView1 and/or pView2 parameter. If you fail to do this, the application will assert.

The view1 and view2 is used to pass the view class to the SplitterWnd, just like it is done with the 'normal' CSplitterWnd. It has to be passed using the macro RUNTIME_CLASS, e.g.:

RUNTIME_CLASS(CSplitterWndTestView3)

Use the method AddSubDivision to apply a further split:

m_pSplitterWnd2 =
  m_pSplitterWnd->AddSubDivision(LEFT_SIDE,
  RUNTIME_CLASS(CSplitterWndTestView3),NULL,pContext,false);

The above sample will split the left pane horizontally and fill the top pane with a view, while leaving the bottom part empty for further splitting by passing NULL. The method AddSubDivision is defined as follows:

ST_SplitterWnd* AddSubDivision(int nSide, // use LEFT_SIDE,
                                          // RIGHT_SIDE, TOP_SIDE
                                          // or BOTTOM_SIDE
    CRuntimeClass* pView1, // Add the view for the
                           // left(or top) pane or NULL
    CRuntimeClass* pView2, // Add the view for the
                           // right(or bottom) pane or NULL
    CCreateContext* pContext, // pass the pContext pointer
                              // from the OnCreateClient
    bool bVertical); // true for a vertical split,
                     // false for a horizontal split

Basically, the parameters are used in the same way as the Create parameters. The first parameter (nSide) is used to describe the pane in which the split has to be added. Four constants (LEFT_SIDE, RIGHT_SIDE, TOP_SIDE and BOTTON_SIDE) are defined for this purpose. The last parameter tells the method to perform a vertical split (true) or a horizontal split (false). The AddSubDivision returns a pointer to a new instance of a ST_SplitterWnd object which can be split again (and again..):

m_pSplitterWnd3 = m_pSplitterWnd2->AddSubDivision(BOTTOM_SIDE,
                          NULL,
                          RUNTIME_CLASS(CSplitterWndTestView2),
                          pContext,
                          true);
m_pSplitterWnd4 = m_pSplitterWnd3->AddSubDivision(LEFT_SIDE,
                          RUNTIME_CLASS(CSplitterWndTestView4),
                          RUNTIME_CLASS(CSplitterWndTestView5),
                          pContext,
                                     false);

An additional feature of this class is the possibility to switch between views within a pane. In order to do this, you use the method AddView. Note: When using AddView, the pane to which the views must be added must not contain a view, with other words, they must be initialized with NULL just like the pane which will be split once more.

m_nViewNo1 = m_pSplitterWnd->AddView(RIGHT_SIDE,
                     RUNTIME_CLASS(CSplitterWndTestView),
                     pContext);
m_nViewNo2 = m_pSplitterWnd->AddView(RIGHT_SIDE,
                     RUNTIME_CLASS(CSplitterWndTestView6),
                     pContext);

The method AddView is defined as follows:

int AddView(int nSide, // use LEFT_SIDE, RIGHT_SIDE,
                      // TOP_SIDE or BOTTOM_SIDE
  CRuntimeClass * pViewClass,  // Add the view for the
  CCreateContext* pContext);  // pass the pContext pointer
                             // from the OnCreateClient

Basically that will do the job. There are three more methods of interest: SetInitialStatus, SwitchToView and ToggleSide:

  • SetInitialStatus is used to restore the settings, such as size and position of each pane, from the registry.
  • SwitchToView toggles between multiple views within a pane.
  • ToggleSide hides and displays a pane.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer
Switzerland Switzerland
Professional IT developer since 1983. First projects with Cobol, then Pascal, Modula2, C and since Visual C++ 1.0 also with C++ and today C#. Works since 1986 as Consultant, between 1990 and 2008 for Infobrain in Switzerland, from 2008 until 2013 for enValue (also Switzerland) and currently working for Comfone (Bern, Switzerland).

Married, two grown-up daughters, Hobbies : Paragliding, Orienteering, Mountainbiking, Photography

Comments and Discussions

 
QuestionRegistry Entries Pin
ozantopoglu2-Jan-06 23:40
ozantopoglu2-Jan-06 23:40 
Questionhow could u split a 3X3 view, it is a 3 column and 3 row view. Pin
skygg14-Nov-05 20:35
skygg14-Nov-05 20:35 
Questionproblems with ST_SplitterWnd and CFrameWnd Pin
#93709-Nov-05 10:13
#93709-Nov-05 10:13 
GeneralDoesn't Work Pin
Dave22n2-Nov-05 7:23
Dave22n2-Nov-05 7:23 
AnswerI thought so too... Pin
John Boero27-Feb-06 11:43
John Boero27-Feb-06 11:43 
General3 horizontal view Pin
March'26-Oct-05 9:36
March'26-Oct-05 9:36 
GeneralToggling separate panes Pin
rameye20-Oct-05 21:31
rameye20-Oct-05 21:31 
GeneralRe: Toggling separate panes, revisited Pin
rameye21-Oct-05 22:48
rameye21-Oct-05 22:48 
Keeping this contained within one thread, so replying to myself.

Question: What is worse than a splitter window?

Answer: Nested splitter windows

If anyone tried my previously posted solution for mapping events to a pane toggle in a three pane setup.....

Well, you have probably seen that there was still a problem, and also that problem has to do with the interrelationship between pane BC of the first created splitter window, and panes B and C of the sub splitter window created by AddSubDivision().

So I put on the proverbial cap and went back to take a new look and approach for a solution. What I decided to do was derive a new class ST_SplitterWndEx from ST_SplitterWnd. In my new class I added three methods, each one to toggle a distinct pane in our group of three. Now one could just as well argue that I could have foregone the new class derivation, and added three methods to my child window class, which contains the pointers to the splitter windows already. However, my methods make heavy use of the ST_SplitterWnd methods, so that is why I decided to derive from ST_SplitterWnd.

I also decided that within my methods I would only call ST_SplitterWnd public methods. That is in accord to my belief that a class is not kosher unless foreign classes can massage it and get the functionality the class was designed for.

By limiting myself to the public methods of ST_SplitterWnd, I now had the problem of how to implement pane toggling using two of it's methods, IsSideHidden() and ToggleSide(). I also accessed it's public data member m_pSubSplitterWnd.

So here is my solution for the one over two pane setup. This is a template, feel free to redefine A,B,C, and BC to whatever labels make sense in your application. First for reference, a diagram of the panes:

***********
*         *
*    A    *
*         *
***********
*    *    *
*  B *  C *
*    *    *
***********

In your frame window class declaration define two member pointers:
ST_SplitterWndEx* m_pSplitterWnd;
ST_SplitterWnd* m_pSplitterWnd2;

Next, in your frame windows OnCreateClient():
// Instantiate our ST_SplitterWnd derived class
m_pSplitterWnd = new ST_SplitterWndEx();

// Create horizontal split, top pane is view "A"
m_pSplitterWnd->Create(	this,
			RUNTIME_CLASS(CView_A),
			NULL,
			pContext,
			false);

// Split the bottom pane vertically, assign views "B" and "C"
m_pSplitterWnd2 = m_pSplitterWnd->AddSubDivision(BOTTOM_SIDE,
                                                 RUNTIME_CLASS(CView_B),
                                                 RUNTIME_CLASS(CView_C),
                                                 pContext,
                                                 true);	

Also, be sure to destroy the ST_SplitterWndEx object in your frame class destructor, with:
delete m_pSplitterWnd;

I copied directly from my source files and changed the function names to generic labels for this demonstration.

My derived class declaration:
// ST_SplitterWndEx.h: interface for the ST_SplitterWndEx class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_ST_SPLITTERWNDEX_H__D2C06E85_6340_44DC_8F87_293780F35CEA__INCLUDED_)
#define AFX_ST_SPLITTERWNDEX_H__D2C06E85_6340_44DC_8F87_293780F35CEA__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "ST_SplitterWnd.h"

#define A 0
#define BC 1
#define B 0
#define C 1

class ST_SplitterWndEx : public ST_SplitterWnd  
{
public:
	BOOL Toggle_A();
	BOOL Toggle_B();
	BOOL Toggle_C();
	ST_SplitterWndEx();
	virtual ~ST_SplitterWndEx();
};

#endif // !defined(AFX_ST_SPLITTERWNDEX_H__D2C06E85_6340_44DC_8F87_293780F35CEA__INCLUDED_)

My derived class implementation:
// ST_SplitterWndEx.cpp: implementation of the ST_SplitterWndEx class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ST_SplitterWndEx.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

ST_SplitterWndEx::ST_SplitterWndEx()
{
}

ST_SplitterWndEx::~ST_SplitterWndEx()
{
}

BOOL ST_SplitterWndEx::Toggle_A()
{
	BOOL bChanged;

	BOOL bInitial = IsSideHidden(A);
	ToggleSide(A);
	bChanged = IsSideHidden(A);

	if (bChanged != bInitial) // Test for actual change
	{
		return TRUE; // Pane changed
	}
	else return FALSE; // Pane would not change
}

BOOL ST_SplitterWndEx::Toggle_B()
{
	BOOL bChanged;

	BOOL bInitial = m_pSubSplitterWnd[BC]->IsSideHidden(B);
	m_pSubSplitterWnd[BC]->ToggleSide(B);
	bChanged = m_pSubSplitterWnd[BC]->IsSideHidden(B);

	if (bChanged != bInitial) // Test for actual change, bChanged if it's off now
	{
		if (!bChanged) // It's turned on now
			if (IsSideHidden(BC)) // Is lower pane off?
			{
			  if (!m_pSubSplitterWnd[BC]->SideHidden(C)) 
                              // If sister pane not off, it should be
                              // so turn it off
			      m_pSubSplitterWnd[BC]->ToggleSide(C);	  
			  ToggleSide(BC); // Turn on lower pane
			}
		return TRUE; // Pane changed
	}
	else if (m_pSubSplitterWnd[BC]->IsSideHidden(C)) // Didn't change,
                                                         // so check for hidden sister pane
	{
	   if (IsSideHidden(A)) // No sister. Anyone above me?
	      return FALSE; // Nope, I'm here all alone. Return with the bad news.
	   ToggleSide(BC); // Yes, so just turn off bottom pane to hide myself.
	   return TRUE; // Pane changed
	}
	else return FALSE; // Pane would not change
}

BOOL ST_SplitterWndEx::Toggle_C()
{
	BOOL bChanged;

	BOOL bInitial = m_pSubSplitterWnd[BC]->IsSideHidden(C);
	m_pSubSplitterWnd[BC]->ToggleSide(C);
	bChanged = m_pSubSplitterWnd[BC]->IsSideHidden(C);

	if (bChanged != bInitial) // Test for actual change, bChanged if hidden now
	{
	   if (!bChanged) // It's turned on now
		if (IsSideHidden(BC)) // Is lower pane off?
		   {
		      if (!m_pSubSplitterWnd[BC]->IsSideHidden(B)) // Should be off
			  m_pSubSplitterWnd[BC]->ToggleSide(B);	   // so turn it off
		      ToggleSide(BC);                              // Turn on lower pane
		   }
		return TRUE; // Pane changed
	}
	else if (m_pSubSplitterWnd[BC]->IsSideHidden(B)) // Didn't change,
                                                         // check for sister pane hidden
	{
	    if (IsSideHidden(A)) // No sister. Anyone above me?
		return FALSE; // Nope, I'm here all alone. Return with the bad news.
	    ToggleSide(BC); // Sister pane was off, so turn off pane to hide myself.
	    return TRUE; // Pane changed
	}
	else return FALSE; // Pane would not change
}

By using this derived class, my application can now correctly toggle individual panes on and off, using buttons, menu selections, or whatever control I desire. Oh, and one more thing. My three methods return type BOOL, reflecting if the pane actually changed state or not. Pass this information to your CCommandUI update handler, and you can set the state of the control objects that you use to toggle the panes.

I hope this information can help anyone else that's trying to get more use out of splitter windows in their applications. Speaking from my short experience with them so far, they can be a little tricky to manipulate, but the results are worth the attempt. Happy coding!







-- modified at 2:12 Sunday 23rd October, 2005
GeneralASSERT bug Pin
rameye20-Oct-05 11:02
rameye20-Oct-05 11:02 
GeneralEvents Pin
qweasdzxc200423-Sep-05 4:39
qweasdzxc200423-Sep-05 4:39 
Generalbadtiger Pin
badtiger17-Jul-05 3:02
badtiger17-Jul-05 3:02 
GeneralContext Menus with ST_SplitterWnd Pin
bobbyn31417-Jun-05 2:09
bobbyn31417-Jun-05 2:09 
GeneralRe: Context Menus with ST_SplitterWnd Pin
bobbyn31423-Jun-05 0:52
bobbyn31423-Jun-05 0:52 
GeneralPlease HELP : Question about SetDlgCtrl Pin
Paul "Waszka" Wu.6-May-05 12:56
Paul "Waszka" Wu.6-May-05 12:56 
GeneralRe: Please HELP : Question about SetDlgCtrl Pin
Paul "Waszka" Wu.6-May-05 13:25
Paul "Waszka" Wu.6-May-05 13:25 
Generalsuggestion Pin
fredeckbert19-Dec-04 5:48
sussfredeckbert19-Dec-04 5:48 
GeneralPlease tell me if this is true. Pin
hw7704130-Oct-04 16:52
hw7704130-Oct-04 16:52 
QuestionHow to Simply split Left/Right and Right with multiple views Pin
Je Way Huang13-Jul-04 17:03
Je Way Huang13-Jul-04 17:03 
AnswerRe: How to Simply split Left/Right and Right with multiple views Pin
hibachirat26-Jun-05 16:42
hibachirat26-Jun-05 16:42 
GeneralRe: How to Simply split Left/Right and Right with multiple views Pin
Homero De la Garza5-Nov-05 19:39
Homero De la Garza5-Nov-05 19:39 
AnswerRe: Quite simple buddy Pin
wqter18-Jul-05 19:28
wqter18-Jul-05 19:28 
GeneralThanks Pin
Larry Dobson18-May-04 6:35
Larry Dobson18-May-04 6:35 
GeneralLeaks Pin
Larry Dobson18-May-04 6:34
Larry Dobson18-May-04 6:34 
GeneralRe: Leaks Pin
flyonsea26-Sep-04 2:59
flyonsea26-Sep-04 2:59 
Generalgreat job Pin
Franz Brunner22-Apr-04 23:42
Franz Brunner22-Apr-04 23:42 

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.