A Splitter Window Control


This article contains updated code for the splitter window control created by myself several months ago. Also I provide here some descriptions where and how to use such control. I must admit that the previous version of the control was rather unreliable and it failed to work correctly in many cases. This version is much more stable, although I can’t guarantee that it will work in all conditions you want. In this article I do not try to show the difference of code between this and the previous version of the control. I just describe it from the scratch.


Where and how to use the control

After the publication of the first version of the control I received some messages, which told me that some people had not understood the idea of this component. I was asked questions like:

I have a dialog box derived class in an MFC project. The class has two members: m_TreeCtrl and m_ListCtrl created with help of the MFC Class Wizard. The question is how to put the members into the splitter window control?

Here is my answer. The control I have created is supposed to be used in other tasks. Actually, to create a splitter window control to be used in an MFC dialog box is a much easier task (if not take into account the ability to place ActiveX controls into panes). You can even use portions of code from my control. You do not have to deal with COM. All you have to do is to provide a way of letting know the splitter control the window handles of other controls on the dialog you want to place in panes. When the user changes the sizes of panes you would call SetWindowPos function.

The control is being described in this article is supposed to be used in ActiveX control containers such as forms in Visual Basic. Personally myself I am going to use the control in ActiveX scripting hosts with forms support. An example if such program is provided as a testing program. The task of the control is to manipulate other ActiveX controls, placed on the same container, to be used as panes.



In the Results\Release folder you will find a lot of files. One of them is “SplitterWindowControl.ocx “ one. Register it.

In the same folder you can also find a testing program – ATnT. This program
is an example of an ActiveX scripting host created with help of the HyperHost
library provided by Dundas Software company, plus one of my own libraries.  In
fact, ATnT is loosely based on HyperHost's ATnTLite sample.

To test splitter control with the ATnT program, run the program first. Invoke the File | Open menu item. Select ATnT.mdb file and press [Open] button. You will see “Main Form”, on the first page of which there will be two splitter controls. The first splitter control has a list view in its left pane and another splitter control in its right pane. The second splitter control manipulates two other list view controls.

You can also try to open “Splitters.afm” file with the ATnT program. The form in this file demonstrates two splitter controls, placed on different pages. You can also notice that the program can open module files (scripts to control the “Application” object exposed by the program), plus files of ActiveX document servers registered on your computer. At the end of the article there is a chapter, which describes what the ATnT sample is, what it demonstrates and how to obtain its source code.


What is the splitter window control?

I hope you know everything about CSplitterWnd class. I will not explain here what it is. This is MFC code guru site is not it? He, who does not know, can always learn it from the documentation. This control has a splitter window. The splitter window in this control always consists of two panes (if you improve it to use more and send me your updates I would appreciate that). The splitter window can have two rows or two columns. The control has 4 properties. It fires no events and has no methods.


[id(1)] BSTR strFirstControlName;

[id(2)] BSTR strSecondControlName;

[id(3)] boolean bVertical;

[id(4)] short intFirstPaneSize;

[id(5)] long _pointer;

The strFirstControlName property tells the control the name of other control to put into the first pane. The strSecondControlName property tells the control the name of other control to put into the second pane. If bVertical is TRUE, then we have a splitter with two columns, if it is FALSE, we have a splitter with two rows. The intFirstPaneSize property contains width or height of the first pane, depending on the value of the bVertical property. The pointer property is a read only property, which contains the pointer to this control. Here is how the pointer is returned:

long CSplitterWindowControlCtrl::GetPointer()
// TODO: Add your property handler here
return long(this);


How to use the control

You can place the control on a form. Then place another two controls to use in panes (see the picture bellow).

Notice that you do not have to align your controls with the panes. But I do recommend you to put them above the panes. You can not use windowless controls as panes.

Having put the controls on the form, you must attach them to the splitter window control. You can do this manually by assigning their names to the “strFirstControlName” and “strSecondControlName” properties respectively, or by invoking the property page for the control.

You can pick the control you need from the comboboxes.

When you switch your form into the user mode the attached controls will be placed into panes. Do not forget to turn off the border from the controls your are going to attach to the splitter control. This will make the picture better.



Implementation of the control

The idea is a simple one.

  1. We place a splitter window on our control.
  2. Provide an ability to find other controls on the form.
  3. When the control container is in the user mode we access the “pane” controls by their extended “Name” properties and change their position when user changes sizes of panes.

The CSplitterWnd class is very capricious. It was designed to be used in frames, and to use CView derived classes as panes. To save my time I had to satisfy it with these conditions. The RecreateSplitterWindow demonstrates the sequence of code to create the splitter window. As you can see the control uses a CFrameWnd derived object to cover all the control’s client area. The splitter window is placed into the frame one. Two CView derived objects are placed into panes. The view objects do not have documents. The frame window and the view ones were created just to satisfy MFC version of the CSplitterWnd class.

void CSplitterWindowControlCtrl::RecreateSplitterWindow()
 CRect rectClient;
 m_wndFrame.Create(NULL, NULL, WS_CHILD|WS_VISIBLE, rectClient, this);
 int nRows,nColumns;
 CPoint ptPanes[2];
 CSize sizePanes[2];
m_pSplitter=new CControlsSplitterWnd;
CCreateContext context;
context.m_pCurrentDoc = NULL;
m_pSplitter->CreateView(ptPanes[0].x, ptPanes[0].y, RUNTIME_CLASS(CSplitterControlView),sizePanes[0],&context);
 m_pSplitter->CreateView(ptPanes[1].x, ptPanes[1].y, RUNTIME_CLASS(CSplitterControlView),sizePanes[1],&context);


Here is the list of window classes participating in the implementation of the control:

  1. CSplitterWindowControlCtrl – is a COleControl derived class, which represents the ActiveX splitter control.
  2. CSplitterControlFrame – is a CFrameWnd derived class, instance of which is placed on the CSplitterWindowControlCtrl’s one.
  3. CControlsSplitterWnd – is a CSplitterWnd derived class. An instance of this class is placed in the client area of the CSplitterControlFrame.
  4. CSplitterControlView – this class provides views for the splitter window.


Now we will learn how to find other controls, placed on the same form along with our control. We will ask the form to provide us with this information and store it in a CControlInfoArray.

struct CControlInfo
CString m_strName;
IOleObjectPtr m_pOleObject;
CControlInfo& operator=(const CControlInfo& other)
return *this;
class CControlInfoArray : public CArray<CControlInfo,CControlInfo&>
int IndexOf(LPCTSTR lpstrName);

The CControlInfo structure stores the extended name property of a control (the one, which is given to the control by the container) and the IOleObject interface of the control. The GetOtherControlsOnTheContainer function fills a CControlInfoArray with the information about controls. Let’s see how does the thing work.

  1. When a container places an object onto itself, it gives the one different site interfaces, through which the object can communicate with the container. In our case IOleClientSite is used. There are other interfaces IOleControlSite, IOleControlSiteWindowless, IBoundObjectSite etc.
  2. Any good container implements IOleContainer interface, through which we can get IUnknown pointers to the objects the container contains.
  3. If the container provides extended properties and events it does it by adding an “extender” COM object, which aggregates the control. The IDispatch interface to the “extender” object is provided through, the corresponding to the object, site interface.
  4. It seems to be a standard that containers distinguish the controls by the unique strings they associate with each control. The string is accessible through the “Name” extended property. Using the IDispatch of the “extender” object we can obtain values of the extended properties.


void CSplitterWindowControlCtrl::GetOtherControlsOnTheContainer(
CControlInfoArray& arrayControls)
IOleContainerPtr pOleContainer;
IEnumUnknownPtr pEnumUnk;
IUnknownPtr pUnk;
while(pEnumUnk->Next(1,&pUnk,NULL) == S_OK)
IOleObjectPtr pOleObject;
hr = pUnk->QueryInterface(IID_IOleObject,(void**)&pOleObject);
//ActiveX control
IOleClientSitePtr pClientSite;
IDispatchPtr pAmbientDispatch;
CControlInfo info;
CString CSplitterWindowControlCtrl::GetExtendedName(IDispatchPtr pDispatch)
COleVariant var;
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
DISPID dispID=0;
LPWSTR lpName[1] = { (WCHAR *)L"Name" };
hr = pDispatch->GetIDsOfNames(IID_NULL,
return _T("");
return CString(var.bstrVal);


Having implemented the feature of getting an array of controls, placed on the from, we can easily find the IOleObject interface to the control if we know it’s name.

When user works with the splitter the RecalcLayout method is called. We tell the parent control (as you remember the splitter window is placed on a frame and the frame is placed on our splitter control window) to delay control reposition. It means that in a few milliseconds the CControlsSplitterWnd::AdjustControlsToPanes() method will be called.

void CControlsSplitterWnd::RecalcLayout()


The AdjustControlsToPanes method first detects if the container is in the user mode. If it is, it obtains information about other controls placed on the same container and invokes a method to reposition the “pane” controls.

void CControlsSplitterWnd::AdjustControlsToPanes()
CSplitterWindowControlCtrl* pControl= GetParentActiveXControl();
CControlInfoArray arrayControls;
CWnd* pWndPane1=GetPane(0,0);
CWnd* pWndPane2=NULL;
if(!pWndPane1 || !pWndPane2)

The purpose of the PlaceControlOnPane method is to adjust the position of the control to the position of the pane in the splitter window. The method obtains IOleObject interface by the “Name” extended property, obtains other interfaces needed to do the task and does the reposition. The reposition process consists of three parts:

  1. First we determine if the “pane” control supports IOleInPlaceObject interface. Having this interface we obtain the window handle of the “pane” control. Since our splitter control uses a lot of windows we can not use the windowless controls as panes. Since the control container knows nothing about all this tricks and not ready for them, there are cases when our control exists but the attached controls do not. In that cases we just postpone the call to the AdjustControlsToPanes function.
  2. We obtain client coordinates of the pane window and convert them into another ones to call SetObjectRects and SetExtent for the control used as pane.
  3. We change extents of the “pane” control. We do this twice. First we set extents to zero, because if we do not do this the call to SetObjectRects can lead to some “dirty places” because the container will not expect this call and will not invalidate some portions of its client area. Then we call SetObjectRects method to change the position of the control. And finally call SetExtents with correct extent of the control.
void CControlsSplitterWnd::PlaceControlOnPane(LPCTSTR lpstrControlName,
CWnd* pWndPane,
CControlInfoArray& arrayControls)
int nPos=arrayControls.IndexOf(lpstrControlName);
IOleInPlaceObjectPtr pOleInPlaceObject;
IOleObjectPtr pOleObject=arrayControls[nPos].m_pOleObject;
HWND hwndControl=NULL;
CRect rectClient;
//Convert coordinates to the container's ones
CRect rectObject(rectClient);
IOleClientSitePtr pClientSite;
CWnd wndContainer;
CSize size;
CClientDC dc(this);

And now the most weird thing. It was not difficult to make a property page for the control. But to fill comboboxes on the page with the controls to attach, I needed pointer to the control. Both the control and the property page are placed in the same DLL, so I can use the pointer without restrictions. But how can I obtain the pointer? The property page seems can edit many controls at the same time, but I never saw that any container used this possibility. Containers use their own property sheets when editing selections. From within the COlePropertyPage class I can get the array of IDispatch interfaces to the controls being edited by the page. I can not use CcmdTarget::FromIDispatch to get the pointer to the control. This method returns NULL. I failed to find any other quick solution than making the “_pointer” property of the splitter control.

BOOL CSplitterWindowControlPropPage::OnInitDialog()
// TODO: Add extra initialization here
long pointer=0;
CCmdTarget* pCmdTarget=(CCmdTarget*)pointer;
CControlInfoArray arrayControls;
for(int i=0; i<arrayControls.GetSize(); i++)
return FALSE;


Usual CSplitterWnd class uses CView objects as panes and these objects are child windows of the splitter window. In our case controls are not child windows and if we leave the splitter window unchanged we will not be able to see the tracking line when we change the size of panes. To fix this we override OnInverTracker for our custom splitter window.

void CControlsSplitterWnd::OnInvertTracker(const CRect& rect)
//ASSERT((GetStyle() & WS_CLIPCHILDREN) == 0);
CRect rectContainer(rect);
CWnd wndContainer;
IOleClientSitePtr pOleClientSite=GetParentActiveXControl()->GetClientSite();
DWORD dwOldStyle=wndContainer.GetStyle();
// pat-blt without clip children on
CDC* pDC = wndContainer.GetDC();
// invert the brush pattern (looks just like frame window sizing)
CBrush* pBrush = CDC::GetHalftoneBrush();
HBRUSH hOldBrush = NULL;
if (pBrush != NULL)
hOldBrush = (HBRUSH)SelectObject(pDC->m_hDC, pBrush->m_hObject);
if (hOldBrush != NULL)
SelectObject(pDC->m_hDC, hOldBrush);



ATnT sample

The ATnT sample is a demo program for the HyperHost MFC extension library created by Dundas Software. It demonstrates a very simple ActiveX scripting host capable to store forms and script modules in a database. The database for the program stores information about people, phone numbers used by the people, phone calls made by the people and how the services are paid. The sample opens the database, reads the script modules stored in it, runs the scripts. The scripts define the behavior of the sample.

Libraries used by the ATnT program:

  1. HyperHost (http://www.dundas.com/hyperhost) – provides classes to create ActiveX scripting hosts. Contains code for IActiveScriptSite, work with Microsoft Script Debugger, script editor with syntax highlighting and IntelliSense, work with Microsoft Forms ActiveX designer, plus a lot of code to solve many COM tasks in MFC programs.
  2. Ultimate Toolbox (http://www.dundas.com/UTB/default.htm) – set of classes to help MFC developers to create robust desktop applications.
  3. ActiveXDocumentContainer – a little bit improved code published by myself in Internet. Used to create ActiveX document containers in MFC programs with Visual Studio 5. MFC version shipped with Visual Studio 6 has its own set of classes to create document containers.

In my life I have created two big applications, which use the script hosting capabilities. Both programs are desktop applications to access some databases (SQL Server 6.5 and Microsoft Access database). Since the development of databases and the client programs I did myself I was free in choosing the way the client applications should be implemented.

My task was not just implementation of the solutions for some current problems. My applications had to be ready to solve other future problems. For instance if you are designing application for accountants and want to sell it to many companies, your program must be ready for all accounting rules used by the companies. If you are sure that in the companies, which you want to sell your program to, some power users know VBScript or JScript languages you might want to choose the ActiveX script hosting technology. In this case you would build your program as a collection of some COM objects and expose the ones to the scripting engines. Power users would be able with help of scripts to modify the behavior of your program if it needs modification. If the set of objects, exposed by your program, is powerful - the limits of abilities of your product will be hard to calculate.

Personally myself, I used to associate a group of script modules with a group of users of my program. When a user logs into the database, my client programs used to load the scripts and run them. Scripts used to manipulate the exposed COM objects of my application. In this case the same C++ code was used to provide different program behavior for different groups of people.

ATnT is a sample of such programs. Of course it is greatly simplified. It does not understand user groups. To see how it works just hold the Ctrl key pressed while opening the ATnT.mdb file. Instead of loading the "Main Form" you will see a project window, which contains all forms and modules stored in the database. These forms and modules are not the ones, put by Access. The forms and modules have been created with the ATnT itself. The picture below demonstrates the project window.

By clicking an item in the tree you can open a form or module. You can try to play with the program by editing forms and providing some other code for the Application module. You can also test how the sample tries to combine the abilities of the script hosting and document containment by pressing the [W] and [X] buttons on the toolbar.

I will not explain here all details of the application. If you have some questions concerning the application you might want to drop me a message. I will just provide some notes.

Form notes:

  1. In the script modules for forms the main object is HostForm. It has all other controls placed on the form (buttons, list views) as properties.
  2. UserForm - is the container itself. It represents the form's window.
  3. You can access the Application object from the form module. You might want to do this to open another form or to get reference to already open form or to access the database.


Application notes:

  1. Application is the main object. You can open forms, ActiveX documents and access the database using this object.
  2. PhoneCallRegistrar is an object, which exposes functionality of a device, registering phone calls, to scripts. PhoneCallRegistrar is a dynamic property of the Application object. If the application had some more devices we could expose them to scripts as properties of the Application object and these properties could be able to fire events (samples: Thermometer_OnTemperatureChanged, Barometer_OnPressureChanged).

In modules you can use the "$$Typelib:" keyword in comments to let the program know that you want to use constants and enums from a type library.


'$$TypeLib: Excel.Sheet

'$$TypeLib: COMCTL.ListViewCtrl.1

You can place these comments anywhere you want.


Well, the most things are described. If you have any ideas, information, code snippets use andrew@skif.net. If you have questions about COM use the same address. If you are interested in Dundas products, you might want to go http://www.dundas.com



Download source - 454 KB

Date Last Updated: March 24, 1999


  • There are no comments yet. Be the first to comment!

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • For many organizations, moving enterprise applications to the public cloud can be a very attractive proposition, but planning the best way to move your applications is mission–critical. As an alternative to the costly option of re–architecting the application for a cloud environment, you can follow a "lift and shift" model that's significantly cheaper and almost always a lot quicker. In order to have a successful "lift and shift" migration, read this white paper to learn a few rules you should …

  • Entire organizations suffer when their networks can't keep up and new opportunities are put on hold. Waiting on service providers isn't good business. In these examples, learn how to simplify network management so that your organization can better manage costs, adapt quickly to business demands, and seize market opportunities when they arise.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date