Custom Splitter Window using MFC class CSplitterWnd
This article shows a demonstration of how to create an explorer interface on a dialog using the MFC classCSplitterWnd. An explorer interface is two controls ? Master and Slave. The master?s content is not changed but the slave?s is. In this particular project the master is a list box containing several ?folders? and when you select one the data in the slave (another list box) is changed.
The splitter window can only split to two views (CView instances), and can only be placed inside a frame (CFrameWnd instance). That?s why we must create a frame on the dialog, create two views and place the controls inside the views, then add the views to the splitter window and add the splitter window to the frame.
If we create an ordinary frame on a dialog it will be SUNKEN and we don?t want that because it doesn?t look good. That?s why we created a CFrameWnd derived class called CFlatFrameWnd which?s only job is to look flat. To do this we remove its WS_EX_CLIENTEDGE style before it?s created. It is a very simple class that is declared and used only in the .cpp file of dialog.
The views that contain the controls cannot be ordinary views (just CView), because they need to do some specific actions. That?s why we created the CHolderView class that does these things and holds the controls.
These actions are:
- Receive the control it holds as a parameter because when it is sized it needs to resize the control as well. That?s why it has a handler for the WM_SIZE message.
- Dispatch all commands that it receives from its child windows to the parent dialog to handle. That?s why we have overridden the OnCommand virtual function.
- Receive the parent dialog as a parameter to know where to dispatch the commands.
So after creating these controls we need to create the helper classes we need to add all of them on the dialog. To do this we must add a function to the dialog. We called it CreateViews. It creates the frame and views, combines them and sets them on the dialog. The controls which will be split must be created there dynamically as their parent must be the view class. We suggest you use the defined control ids in the HolderView.h file IDC_MAIN ? for the master control and IDC_SLAVE for the slave control. This however is not obligatory it is just recommended.
Here is what you need to do in order to use this code:
- Add the holder view files to your project
- In the dialog class .cpp file include the holder view header
- In the beginning of the dialog class .h file add this code ?class CFlatFrameWnd;?
- Add a member variable of type CFlatFrameWnd like this ?CFlatFrameWnd m_pFrame? and member variables for the controls you?d like to split.
- On the dialog template add an invisible control with id IDC_VTEMPLATE. It will serve only to get its rectangle and so the control structure you?ll add will be there.Add the function CreateViews from cviews.cpp to you dialog class
- Fill the function with the appropriate code as shown with the comments.
- In the InitDalog method call CreateViews and then initialize the controls (if they need extra initialization after they?re created)
So, how to write the CreateViews function?
First to create a view on a splitter window we need a create context which is described with the CCrateContext class. In this context the only thing we need to set is the view which the splitter will hold. Then the splitter will automatically create an instance of this class when the AddView method is called. The other members of the class must be set to NULL like this:
ccc.m_pNewViewClass = RUNTIME_CLASS(CHolderView);
ccc.m_pCurrentDoc = NULL;
ccc.m_pNewDocTemplate = NULL;
ccc.m_pLastView = NULL;
ccc.m_pCurrentFrame = NULL;
Also we must register a simple window class for the frame. It must only be an empty window. Then we get the rectangle of the frame from the invisible control on the dialog template, and create the frame as a child of the dialog box. This is the code used:
m_pFrame = new CFlatFrameWnd;
m_pFrame->Create(strMyClass,"", WS_CHILD, rect, this);
After this we create the splitter with 1 row and 2 columns on the frame and add its views. To add a view apart from the create context a size is needed. We use just any size because it is not important at this time as the control will be resized soon.
m_cSplitter.ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
m_cSplitter.CreateView(0,0, RUNTIME_CLASS(CHolderView), CSize(100,100), &ccc);
m_cSplitter.CreateView(0,1, RUNTIME_CLASS(CHolderView),CSize(100,100), &ccc);
To get a view from CSplitterWnd we must use it?s GetPane method which gets a view at a specified position. We get each view and then create the control on it. Here is a code extracted from the sample project that shows this action:
CHolderView* pView = (CHolderView*)m_cSplitter.GetPane(0,0);
Finally we set the splitter?s size to cover the entire frame. The views in it will automatically be resize and resize the controls that they?re holding.
What more can you create using this code?
Except a program like this one that is fairly simple you can create a tree control as a master and a list view control as a slave. That?s how you can make almost an exact copy of windows explorer?s interface.
The setWnd method of CHolderView sets the active control on it. It is not said that it must be the only one. For example you can switch between different controls on the view. Suppose you want to add different looks for your slave control ? a list and an edit. Just create a list box and an edit control on the view and when the appropriate event is raised, change the active control with setWnd and the control will be changed.
Project Demo Files: SplitterWnd.zip
Source Files: SplitterSource.zip
Article Contributed by Borislav