Introduction
Yes, that is another-another splitter control. This control based the control of this (Another splitter control for dialog). I have taken some changes of the origin code to make it much more easy to use. The new splitter-control can auto change the linked control's pos automatically, when the user changes the split-bar. That is it!
Background
I'm working for a GUI application which has many dialogs. But the developer before me makes all of the dialogs fixed size. Sometimes, that is ok. But the others will make the interface be hard to use. I have to move the scroll bar to see the part which be hidden by the fixed sized dialog. When I have a chance to modify it, I decide to make some change. From that time on, I look for a splitter control which can be used in the dialog.
There are many kinds of splitter-control in CodeProject. I like the one which is written by Hung Nguyen very much: Another splitter control for dialog. The commit dialog of the svn uses that one too. When I used this control in many of my application, I found that it has some small problem that I have to call ChangePos
function in many places. In other words, it cannot move the relation control automatically. So I make a new one to solve this problem.
Using the Code
Step 1: Add a Picture Control on Your Dialog at the Resource Editor.
Put a picture control on your dialog, give it id IDC_SPLITTER1
. Change the size of the control to make it look like a horizontal bar. Double click the control of IDC_SPLITTER1
, change the property as below. And then add a vertical one the same way, give it id IDC_SPLITTER2
.
Actually, all the operations above just want to make us not calc the splitter size. You can specify the size of the splitter by the CSplitterControl::Create
function.
Step 2: Add Splitters to the Dialog Class
Add SplitterControl.h and SplitterControl.cpp to your project. Insert #include "splittercontrol.h"
to the .h file of the dialog
class.
And then, add member variables:
private:
CSplitterControl m_wndSplitter1;
CSplitterControl m_wndSplitter2;
In the function of OnInitDialog
, create the splitters.
There are some notices here:
- Use
SPS_VERTICAL
or SPS_HORIZONTAL
to special the splitter style. - You can special a RGB color (the default id RGB(120, 120, 120)) for the splitter line.
- You can special the splitter line width (the default is 1).
- The
width(SPS_VERTICAL)
or height(SPS_HORIZONTAL)
of the splitter depend on the width
of rect
when you call Create
function.
BOOL CSplitterControlDemoDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rc;
CWnd* pWnd;
pWnd = GetDlgItem(IDC_SPLITTER1);
pWnd->GetWindowRect(rc);
ScreenToClient(rc);
BOOL bRet = m_wndSplitter1.Create(WS_CHILD | WS_VISIBLE, rc,
this, IDC_SPLITTER1, SPS_VERTICAL|SPS_DELTA_NOTIFY);
if (FALSE == bRet)
{
AfxMessageBox("m_wndSplitter1 create failed");
}
pWnd = GetDlgItem(IDC_SPLITTER2);
pWnd->GetWindowRect(rc);
ScreenToClient(rc);
bRet = m_wndSplitter2.Create(WS_CHILD | WS_VISIBLE, rc,
this, IDC_SPLITTER2, SPS_HORIZONTAL, RGB(0, 0, 255));
if (FALSE == bRet)
{
AfxMessageBox("m_wndSplitter2 create failed");
}
Step 3: Add Linked Windows
The splitter can move the linked window pos automatically as the user changes the splitter's pos. So we should specify which window needs to change the pos.CSplitterControl::RegisterLinkedWindow
function take the work. Check the example below:
this->m_wndSplitter1.RegisterLinkedWindow(SPLS_LINKED_LEFT, GetDlgItem(IDC_TREE));
this->m_wndSplitter1.RegisterLinkedWindow(SPLS_LINKED_RIGHT, GetDlgItem(IDC_LIST));
this->m_wndSplitter1.RegisterLinkedWindow(SPLS_LINKED_RIGHT, GetDlgItem(IDC_EDIT));
this->m_wndSplitter1.RegisterLinkedWindow(SPLS_LINKED_RIGHT, &m_wndSplitter2);
this->m_wndSplitter2.RegisterLinkedWindow(SPLS_LINKED_UP, GetDlgItem(IDC_LIST));
this->m_wndSplitter2.RegisterLinkedWindow(SPLS_LINKED_DOWN, GetDlgItem(IDC_EDIT));
this->m_wndSplitter1.Relayout();
this->m_wndSplitter2.Relayout();
Remember that SPLS_LINKED_LEFT
means the control is on the left side of the splitter. SPLS_LINKED_RIGHT
means the right. The two used for the splitter of vertical. If the control linked to a splitter of vertical with SPLS_LINKED_LEFT
, that means the right pos of the control will be changed by the splitter. SPLS_LINKED_RIGHT
is similar to SPLS_LINKED_LEFT
.
SPLS_LINKED_UP
and SPLS_LINKED_DOWN
as the name means the control will be at the up side of a horizontal splitter.
We have almost finished, after we have linked the controls to the splitter. In order to make the interface look better, we should call CSplitterControl::Relayout
function to make the initializing layout. You can call CSplitterControl::Relayout
function at any time you need.
Step 4: Splitter's Limit Pos
Usually, we need to set the splitter's moving range. That is not very important in the Document-View based application. But in the dialog based application, we have to process the window's edge ourselves. So, the limit pos of the splitter is very important for dialog based application.
In the sizabled
dialog, the limit pos of the splitter is not fixed. Once we change the dialog size, we have to change the new limit pos of the splitter. That is not very good. In my splitter control, they send a notify message to the parent window before you are ready to change the splitter pos every time. So if you want to use this, set the limit pos, just handle the notify message. The notify message is named SPN_MAXMINPOS
. There is some sample code here.
Add message handle function in the .h file of the dialog
class for the notify message (SPN_MAXMINPOS
):
afx_msg void OnMaxMinInfo(NMHDR* pNMHDR, LRESULT* pResult);
Map the message:
BEGIN_MESSAGE_MAP(CSplitterControlDemoDlg, CDialog)
ON_NOTIFY(SPN_MAXMINPOS, IDC_SPLITTER2, OnMaxMinInfo)
ON_NOTIFY(SPN_MAXMINPOS, IDC_SPLITTER1, OnMaxMinInfo)
END_MESSAGE_MAP()
Implement the message handle function:
void CSplitterControlDemoDlg::OnMaxMinInfo(NMHDR* pNMHDR, LRESULT* pResult)
{
CRect rcTree;
CRect rcList;
CRect rcEdit;
CRect rcCancel;
m_wndType.GetWindowRect(rcTree);
m_lstItem.GetWindowRect(rcList);
m_txtContent.GetWindowRect(rcEdit);
m_btnCancel.GetWindowRect(rcCancel);
this->ScreenToClient(rcTree);
this->ScreenToClient(rcList);
this->ScreenToClient(rcEdit);
this->ScreenToClient(rcCancel);
SPC_NM_MAXMINPOS* pNewMaxMinPos = (SPC_NM_MAXMINPOS*)pNMHDR;
if (IDC_SPLITTER1 == pNMHDR->idFrom)
{
pNewMaxMinPos->lMin = rcTree.left + 50;
pNewMaxMinPos->lMax = rcCancel.left - STD_GAP;
}
else
{
pNewMaxMinPos->lMin = rcList.top + 50;
pNewMaxMinPos->lMax = rcEdit.bottom - 50;
}
}
Step 5: Some Special Used Method
Not everything will automatically be ok. The splitter needs us to register all of the linked controls to it when the dialog is initializing. So if a control is not created at that time, we cannot register it to the splitter. So the splitter provided another way to change the pos of these controls. The kernel function is CSplitterControl::ChangePos
. This function accepts a parameter dwLinkedSide
to specify the control is which side of the splitter. And the lDelta
usually is from the notify message SPN_DELTA
.
The SPN_DELTA
notify message is sent when the user releases the mouse. It is defined in the SplitterControl.h.
#define SPN_DELTA (WM_USER + 2)
struct SPC_NM_DELTA
{
NMHDR hdr;
LONG lDelta;
};
There is some sample code to show how to use this notify message:
Firstly, we need to use the SPS_DELTA_NOTIFY
style as we create the splitter.
BOOL bRet = m_wndSplitter1.Create(WS_CHILD | WS_VISIBLE, rc,
this, IDC_SPLITTER1, SPS_VERTICAL|SPS_DELTA_NOTIFY);
And then, add message maps:
BEGIN_MESSAGE_MAP(CSplitterControlDemoDlg, CDialog)
ON_NOTIFY(SPN_DELTA, IDC_SPLITTER1, OnSplitter1Delta)
END_MESSAGE_MAP()
At last, implement it:
void CSplitterControlDemoDlg::OnSplitter1Delta(NMHDR* pNMHDR, LRESULT* pResult)
{
*pResult = 0;
SPC_NM_DELTA* pDelta = (SPC_NM_DELTA*)pNMHDR;
if (NULL == pDelta)
{
return;
}
m_wndSplitter1.ChangePos(&m_edHelp, SPLS_LINKED_LEFT, pDelta->lDelta);
}
Thanks
Thanks to Hung Nguyen and his(or her) article, Another splitter control for dialog and the demo project. That helped me a lot, and my sample code is based on that too. Oh, I'm so lazy.
Points of Interest
The sample way always exists.
History
- 2013-05-19: First version