原文链接地址:https://www.codeproject.com/Articles/595602/Splitter-Control-for-Dialog

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(); // Here, I ignore some code we not care about.
// You can reference the sample code for details. 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);//, RGB(255, 0, 0));
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(, , ));
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:

 //  register windows for splitter
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)); // relayout the splitter to make them good look
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)
//{{AFX_MSG_MAP(CSplitterControlDemoDlg)
// ...
ON_NOTIFY(SPN_MAXMINPOS, IDC_SPLITTER2, OnMaxMinInfo)
ON_NOTIFY(SPN_MAXMINPOS, IDC_SPLITTER1, OnMaxMinInfo)
// ...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

Implement the message handle function:

 void CSplitterControlDemoDlg::OnMaxMinInfo(NMHDR* pNMHDR, LRESULT* pResult)
{
// Get current pos of the child controls
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); // return the pos limit
SPC_NM_MAXMINPOS* pNewMaxMinPos = (SPC_NM_MAXMINPOS*)pNMHDR;
if (IDC_SPLITTER1 == pNMHDR->idFrom)
{
pNewMaxMinPos->lMin = rcTree.left + ;
pNewMaxMinPos->lMax = rcCancel.left - STD_GAP;
}
else
{
pNewMaxMinPos->lMin = rcList.top + ;
pNewMaxMinPos->lMax = rcEdit.bottom - ;
}
}

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.

 //  Notify event : tell the parent to do some special things
// some times, the parent window can not register the child control for reason
// it does not created yet.
// so, SPN_DELTA event give the parent window a chance to change the child control's pos.
#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);//, RGB(255, 0, 0));

And then, add message maps:

 BEGIN_MESSAGE_MAP(CSplitterControlDemoDlg, CDialog)
// ...
ON_NOTIFY(SPN_DELTA, IDC_SPLITTER1, OnSplitter1Delta)
// ...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

At last, implement it:

 void CSplitterControlDemoDlg::OnSplitter1Delta(NMHDR* pNMHDR, LRESULT* pResult)
{
// this function just want to show you how to use the delta event
*pResult = ; SPC_NM_DELTA* pDelta = (SPC_NM_DELTA*)pNMHDR;
if (NULL == pDelta)
{
return;
} m_wndSplitter1.ChangePos(&m_edHelp, SPLS_LINKED_LEFT, pDelta->lDelta);
}

完整代码

SplitterControlDemoDlg.h

 // SplitterControlDemoDlg.h : header file
// #if !defined(AFX_SPLITTERCONTROLDEMODLG_H__DF3D3AA3_8536_469C_B6A6_32FDC5549ABD__INCLUDED_)
#define AFX_SPLITTERCONTROLDEMODLG_H__DF3D3AA3_8536_469C_B6A6_32FDC5549ABD__INCLUDED_ #if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000 #include "SplitterControl.h" #define STD_GAP 8
#define STD_BUTTON_WIDTH 90
#define STD_BUTTON_HEIGHT 25 /////////////////////////////////////////////////////////////////////////////
// CSplitterControlDemoDlg dialog
class CSplitterControlDemoDlg : public CDialog
{
// Construction
public:
CSplitterControlDemoDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data
//{{AFX_DATA(CSplitterControlDemoDlg)
enum { IDD = IDD_SPLITTERCONTROLDEMO_DIALOG };
CButton m_btnCancel;
CEdit m_edHelp;
CStatic m_stTitle;
CEdit m_txtContent;
CListCtrl m_lstItem;
CTreeCtrl m_wndType;
HICON m_hIcon;
CSplitterControl m_wndSplitter1;
CSplitterControl m_wndSplitter2;
//}}AFX_DATA // ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSplitterControlDemoDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL // Implementation
protected:
void InitSampleData();
void Resize();
void MoveDlgItem(int nD, const CRect& rcPos, BOOL bRepaint); // Generated message map functions
//{{AFX_MSG(CSplitterControlDemoDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnItemchangedList(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnMaxMinInfo(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnSplitter1Delta(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnSize(UINT nType, int cx, int cy);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
}; //{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_SPLITTERCONTROLDEMODLG_H__DF3D3AA3_8536_469C_B6A6_32FDC5549ABD__INCLUDED_)

SplitterControlDemoDlg.cpp代码

 // SplitterControlDemoDlg.cpp : implementation file
// #include "stdafx.h"
#include "SplitterControlDemo.h"
#include "SplitterControlDemoDlg.h" #ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif /////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About class CAboutDlg : public CDialog
{
public:
CAboutDlg(); // Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA // ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL // Implementation
protected:
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
}; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
} void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
} BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////////
// CSplitterControlDemoDlg dialog CSplitterControlDemoDlg::CSplitterControlDemoDlg(CWnd* pParent /*=NULL*/)
: CDialog(CSplitterControlDemoDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CSplitterControlDemoDlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
} void CSplitterControlDemoDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CSplitterControlDemoDlg)
DDX_Control(pDX, IDCANCEL, m_btnCancel);
DDX_Control(pDX, IDC_EDIT_HELP, m_edHelp);
DDX_Control(pDX, IDC_STATIC_TITLE, m_stTitle);
DDX_Control(pDX, IDC_EDIT, m_txtContent);
DDX_Control(pDX, IDC_LIST, m_lstItem);
DDX_Control(pDX, IDC_TREE, m_wndType);
//}}AFX_DATA_MAP
} BEGIN_MESSAGE_MAP(CSplitterControlDemoDlg, CDialog)
//{{AFX_MSG_MAP(CSplitterControlDemoDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_NOTIFY(TVN_SELCHANGED, IDC_TREE, OnSelchangedTree)
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST, OnItemchangedList)
ON_WM_SIZE()
ON_NOTIFY(SPN_MAXMINPOS, IDC_SPLITTER2, OnMaxMinInfo)
ON_NOTIFY(SPN_MAXMINPOS, IDC_SPLITTER1, OnMaxMinInfo)
ON_NOTIFY(SPN_DELTA, IDC_SPLITTER1, OnSplitter1Delta)
ON_WM_GETMINMAXINFO()
//}}AFX_MSG_MAP
END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////////
// CSplitterControlDemoDlg message handlers BOOL CSplitterControlDemoDlg::OnInitDialog()
{
CDialog::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
} // Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here
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);//, RGB(255, 0, 0));
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(, , ));
if (FALSE == bRet)
{
AfxMessageBox("m_wndSplitter2 create failed");
} InitSampleData(); // register windows for splitter
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)); // relayout the splotter to make them good look
this->m_wndSplitter1.Relayout();
this->m_wndSplitter2.Relayout(); this->Resize(); return TRUE; // return TRUE unless you set the focus to a control
} void CSplitterControlDemoDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
} // If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CSplitterControlDemoDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), ); // Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + ) / ;
int y = (rect.Height() - cyIcon + ) / ; // Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
} // The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CSplitterControlDemoDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
} void CSplitterControlDemoDlg::InitSampleData()
{
m_wndType.ModifyStyle(, TVS_LINESATROOT | TVS_HASBUTTONS
| TVS_SHOWSELALWAYS | TVS_HASLINES );
HTREEITEM hRoot = m_wndType.InsertItem("Local folder");
HTREEITEM h1 = m_wndType.InsertItem("Inbox", hRoot);
HTREEITEM h2 = m_wndType.InsertItem("Outbox", hRoot);
HTREEITEM h3 = m_wndType.InsertItem("Send Items", hRoot);
m_wndType.SetItemData(hRoot, );
m_wndType.SetItemData(h1, );
m_wndType.SetItemData(h2, );
m_wndType.SetItemData(h3, ); m_lstItem.ModifyStyle(, LVS_REPORT);
m_lstItem.InsertColumn(, "From", LVCFMT_LEFT, );
m_lstItem.InsertColumn(, "Subject", LVCFMT_LEFT, ); m_edHelp.SetWindowText("Yes, I'm the help edit.");
} void CSplitterControlDemoDlg::OnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
// TODO: Add your control notification handler code here *pResult = ; HTREEITEM h = m_wndType.GetSelectedItem();
DWORD nID = m_wndType.GetItemData(h); m_lstItem.DeleteAllItems();
m_lstItem.DeleteColumn();
m_lstItem.DeleteColumn(); switch(nID)
{
case :break;
case :
m_lstItem.InsertColumn(, "From", LVCFMT_LEFT, );
m_lstItem.InsertColumn(, "Subject", LVCFMT_LEFT, );
m_lstItem.InsertItem(, "Dad");
m_lstItem.SetItemText(, , "Dad's letter");
m_lstItem.SetItemData(, ); m_lstItem.InsertItem(, "AnhPhong");
m_lstItem.SetItemText(, , "Hi, how are you ?");
m_lstItem.SetItemData(, ); m_lstItem.InsertItem(, "TrungHau");
m_lstItem.SetItemText(, , "Reply to Hi");
m_lstItem.SetItemData(, );
break;
case :
m_lstItem.InsertColumn(, "Subject", LVCFMT_LEFT, );
m_lstItem.InsertColumn(, "Recipcent", LVCFMT_LEFT, );
m_lstItem.InsertItem(, "Reply to Dad's letter");
m_lstItem.SetItemData(, ); m_lstItem.SetItemText(, , "Dad");
m_lstItem.InsertItem(, "I'm fine, and you !");
m_lstItem.SetItemText(, , "AnhPhong");
m_lstItem.SetItemData(, );
break;
case :
m_lstItem.InsertColumn(, "From", LVCFMT_LEFT, );
m_lstItem.InsertColumn(, "Subject", LVCFMT_LEFT, );
m_lstItem.InsertItem(, "TrungHau");
m_lstItem.SetItemText(, , "Hi");
m_lstItem.SetItemData(, );
break;
}
} void CSplitterControlDemoDlg::OnItemchangedList(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
*pResult = ; CString strContent = "";
POSITION pos = m_lstItem.GetFirstSelectedItemPosition();
if (pos != NULL)
{
int nCurSel = m_lstItem.GetNextSelectedItem(pos);
int n = m_lstItem.GetItemData(nCurSel);
switch(n)
{
case : strContent = "content of Dad's letter";
break;
case : strContent = "content of Hi, how are you ?";
break;
case : strContent = "content of Reply to Hi ";
break; case : strContent = "content of Reply to Dad's letter";
break;
case : strContent = "content of I'm fine, and you !";
break; case : strContent = "content of Hi";
break; }
}
m_txtContent.SetWindowText(strContent);
} void CSplitterControlDemoDlg::OnSize(UINT nType, int cx, int cy)
{
this->Resize();
} void CSplitterControlDemoDlg::MoveDlgItem(int nID, const CRect& rcPos, BOOL bRepaint)
{
CWnd* pWnd = this->GetDlgItem(nID);
if (NULL == pWnd)
{
return;
} pWnd->MoveWindow(rcPos, bRepaint); CRect rcsp;
m_wndSplitter2.GetWindowRect(rcsp);
this->ScreenToClient(rcsp);
} void CSplitterControlDemoDlg::Resize()
{
CRect rcDlg;
this->GetClientRect(rcDlg); CRect rcTitle;
rcTitle.left = rcDlg.left + STD_GAP;
rcTitle.right = rcDlg.right - STD_GAP;
rcTitle.top = rcDlg.top + STD_GAP;
rcTitle.bottom = rcTitle.top + STD_BUTTON_HEIGHT;
this->MoveDlgItem(IDC_STATIC_TITLE, rcTitle, TRUE); CRect rcOK;
rcOK.right = rcTitle.right;
rcOK.bottom = rcDlg.bottom - STD_GAP;
rcOK.top = rcOK.bottom - STD_BUTTON_HEIGHT;
rcOK.left = rcOK.right - STD_BUTTON_WIDTH;
this->MoveDlgItem(IDOK, rcOK, TRUE); CRect rcCancel;
rcCancel.right = rcOK.left - STD_GAP;
rcCancel.left = rcCancel.right- STD_BUTTON_WIDTH;
rcCancel.top = rcOK.top;
rcCancel.bottom = rcOK.bottom;
this->MoveDlgItem(IDCANCEL, rcCancel, TRUE); if (FALSE == IsWindow(m_wndSplitter1.GetSafeHwnd()))
{
return;
} CRect rcSplit1;
m_wndSplitter1.GetWindowRect(rcSplit1);
this->ScreenToClient(rcSplit1);
rcSplit1.bottom = rcOK.top - STD_GAP;
this->m_wndSplitter1.MoveWindow(rcSplit1, TRUE); CRect rcSplit2;
m_wndSplitter2.GetWindowRect(rcSplit2);
this->ScreenToClient(rcSplit2);
rcSplit2.right = rcOK.right;
this->m_wndSplitter2.MoveWindow(rcSplit2, TRUE); CRect rcTree;
LONG lTreeWidth = rcTree.Width();
rcTree.left = rcTitle.left;
rcTree.right = rcSplit1.left;
rcTree.top = rcTitle.bottom + STD_GAP;
rcTree.bottom = rcOK.top - STD_GAP;
this->MoveDlgItem(IDC_TREE, rcTree, TRUE); CRect rcList;
rcList.top = rcTree.top;
rcList.bottom = rcSplit2.top;
rcList.left = rcSplit1.right;
rcList.right = rcOK.right;
this->MoveDlgItem(IDC_LIST, rcList, TRUE); CRect rcEdit;
rcEdit.left = rcList.left;
rcEdit.right = rcList.right;
rcEdit.top = rcSplit2.bottom;
rcEdit.bottom = rcOK.top - STD_GAP;
this->MoveDlgItem(IDC_EDIT, rcEdit, TRUE); CRect rcHelp;
rcHelp.left = rcTree.left;
rcHelp.right = rcTree.right;
rcHelp.top = rcOK.top;
rcHelp.bottom = rcOK.bottom;
this->MoveDlgItem(IDC_EDIT_HELP, rcHelp, TRUE);
} void CSplitterControlDemoDlg::OnMaxMinInfo(NMHDR* pNMHDR, LRESULT* pResult)
{
// Get current pos of the child controls
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); // return the pos limit
SPC_NM_MAXMINPOS* pNewMaxMinPos = (SPC_NM_MAXMINPOS*)pNMHDR;
if (IDC_SPLITTER1 == pNMHDR->idFrom)
{
pNewMaxMinPos->lMin = rcTree.left + ;
pNewMaxMinPos->lMax = rcCancel.left - STD_GAP;
}
else
{
pNewMaxMinPos->lMin = rcList.top + ;
pNewMaxMinPos->lMax = rcEdit.bottom - ;
}
} void CSplitterControlDemoDlg::OnSplitter1Delta(NMHDR* pNMHDR, LRESULT* pResult)
{
// this function just want to show you how to use the delta event
*pResult = ; SPC_NM_DELTA* pDelta = (SPC_NM_DELTA*)pNMHDR;
if (NULL == pDelta)
{
return;
} m_wndSplitter1.ChangePos(&m_edHelp, SPLS_LINKED_LEFT, pDelta->lDelta);
}

SplitterControl.h

 #ifndef __CSplitterControl_H_
#define __CSplitterControl_H_ #if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// SplitterControl.h : header file
// #include "AFXTEMPL.h" /////////////////////////////////////////////////////////////////////////////
// Splitter style
#define SPS_HORIZONTAL 0x00000000 // Horizontal look splitter
#define SPS_VERTICAL 0x00000001 // Vertical look splitter(this is the default style)
#define SPS_DELTA_NOTIFY 0x00000002 // if need notify the parent to handle delta message // Linked side(used for Vertical style)
#define SPLS_LINKED_RIGHT 0x00000000 // linked window at the right of the splitter(right pos change automatic)
#define SPLS_LINKED_LEFT 0x00000001 // linked window at the left of the splitter(right pos change automatic) // Linked side(used for Horizontal style)
#define SPLS_LINKED_UP SPLS_LINKED_LEFT // linked window at the top of the splitter(right pos change automatic)
#define SPLS_LINKED_DOWN SPLS_LINKED_RIGHT // linked window at the bottom of the splitter(right pos change automatic) // Notify event : to get the max/min pos limit
// usualy, the max/min pos depend on the parent window size, so we'd better get it from parent.
#define SPN_MAXMINPOS (WM_USER + 1)
struct SPC_NM_MAXMINPOS
{
NMHDR hdr;
LONG lMax;
LONG lMin;
}; // Notify event : tell the parent to do some special things
// some times, the parent window can not register the child control for reason it does not created yet.
// so, SPN_DELTA event give the parent window a chance to change the child control's pos.
#define SPN_DELTA (WM_USER + 2)
struct SPC_NM_DELTA
{
NMHDR hdr;
LONG lDelta;
}; /////////////////////////////////////////////////////////////////////////////
// CSplitterControl
//
class CSplitterControl : public CStatic
{
// Attributes
protected:
DWORD m_dwSplitterStyle; // Splitter Style BOOL m_bMouseIsDown; // Record the mouse is down or up
CPoint m_ptCurrent; // Current positon
CPoint m_ptOrigin; // Positon when mouse down
HCURSOR m_hCursor; // Cursor when mouse move COLORREF m_clrSplitterColor; // Color of splitter
LONG m_lSplitterWidth; // line width of splitter int m_iMaxPos; // Max pos the splitter
int m_iMinPos; // Min pos the splitter typedef CList<CWnd*, CWnd*> CLinkedWndList;
CLinkedWndList m_listLinkedWnds[]; // Registered linked window // Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSplitterControl)
//}}AFX_VIRTUAL // Implementation
public:
// Constructor/Destructor
CSplitterControl();
virtual ~CSplitterControl(); // Create a splitter
BOOL Create(DWORD dwStaticStyle, const CRect& rect, CWnd* pParent, UINT nID,
DWORD dwSplitterControlStyle = SPS_VERTICAL,
COLORREF clrSplitterColor = RGB(, , ),
LONG lSplitterLineWidth = ); // Register linked window
BOOL RegisterLinkedWindow(DWORD dwLinkedSide, CWnd* pWnd); // Usualy used in the SPN_DELTA notify event handle function
void ChangePos(CWnd* pWnd, DWORD dwLinkedSide, LONG lDelta); // Relayout all linked windows
// you can call it in CDialog::OnInitDialog.
// if you have some special requirement, you can over write this function in sub class.
virtual void Relayout(); // Generated message map functions
protected:
// Draw the splitter lines
// if the default implement is not suit for you, over write it.
virtual void DrawSplitterLine(CDC* pDC); // To get the left most and the right most pos
// this function called before you change the splitter pos.
// it just send message to parent window, but there are some many other ways,
virtual void GetMaxMinPos(int& iMax, int& iMin); // Move self to a new pos
void MoveSelfWindowToPoint(const CPoint& pt); //{{AFX_MSG(CSplitterControl)
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
//}}AFX_MSG DECLARE_MESSAGE_MAP()
}; ///////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif//__CSplitterControl_H_

SplitterControl.cpp

 // SplitterControl.cpp : implementation file
// #include "stdafx.h"
#include "SplitterControl.h" #ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif /////////////////////////////////////////////////////////////////////////////
// CSplitterControl
CSplitterControl::CSplitterControl()
{
m_dwSplitterStyle = SPS_VERTICAL;
m_bMouseIsDown = FALSE;
m_ptCurrent = CPoint(, );
m_ptOrigin = CPoint(, );
m_hCursor = NULL;
m_iMinPos = INT_MIN;
m_iMaxPos = INT_MAX;
m_clrSplitterColor = RGB(, , );
m_lSplitterWidth = ;
} CSplitterControl::~CSplitterControl()
{
m_listLinkedWnds[SPLS_LINKED_LEFT].RemoveAll();
m_listLinkedWnds[SPLS_LINKED_RIGHT].RemoveAll();
} BEGIN_MESSAGE_MAP(CSplitterControl, CStatic)
//{{AFX_MSG_MAP(CSplitterControl)
ON_WM_MOUSEMOVE()
ON_WM_SETCURSOR()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
//}}AFX_MSG_MAP
END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////////
// CSplitterControl message handlers
BOOL CSplitterControl::Create(DWORD dwStaticStyle, const CRect& rect, CWnd* pParent, UINT nID,
DWORD dwSplitterControlStyle, COLORREF clrSplitterColor, LONG lSplitterLineWidth)
{
// Save styles
m_dwSplitterStyle = dwSplitterControlStyle; // Sace splitter color
m_clrSplitterColor = clrSplitterColor; // load the cursor
m_hCursor = ::LoadCursor(NULL, m_dwSplitterStyle&SPS_VERTICAL?IDC_SIZEWE:IDC_SIZENS); // just create the static(no title)
return (CStatic::Create("", (dwStaticStyle | SS_NOTIFY | WS_CHILD), rect, pParent, nID));
} void CSplitterControl::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bMouseIsDown)
{
// erase the old splitter
CWindowDC dc(NULL);
this->DrawSplitterLine(&dc); // calc the new pos of the splitter
CPoint pt = point;
this->ClientToScreen(&pt); CWnd* pParent = GetParent();
ASSERT(pParent);
ASSERT(IsWindow(pParent->m_hWnd));
pParent->ScreenToClient(&pt); // split position limit
pt.x = (pt.x < m_iMinPos)?m_iMinPos:pt.x;
pt.y = (pt.y < m_iMinPos)?m_iMinPos:pt.y;
pt.x = (pt.x > m_iMaxPos)?m_iMaxPos:pt.x;
pt.y = (pt.y > m_iMaxPos)?m_iMaxPos:pt.y; // save the current pos, this value will be used when the mouse up
pParent->ClientToScreen(&pt);
m_ptCurrent = pt; // repaint the splitter
this->DrawSplitterLine(&dc);
} CStatic::OnMouseMove(nFlags, point);
} BOOL CSplitterControl::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
// when the move is on the client of the splitter this event while be fired
// so just change the cursor
if (nHitTest == HTCLIENT)
{
(void)::SetCursor(m_hCursor);
return (TRUE);
} return CStatic::OnSetCursor(pWnd, nHitTest, message);
} void CSplitterControl::OnLButtonDown(UINT nFlags, CPoint point)
{
CStatic::OnLButtonDown(nFlags, point); // get the max/min pos of the splitter first
this->GetMaxMinPos(this->m_iMaxPos, this->m_iMinPos); // Record the mouse status
m_bMouseIsDown = TRUE; SetCapture(); // Get the move split start pos
CRect rcWnd;
GetWindowRect(rcWnd);
m_ptOrigin = m_ptCurrent = rcWnd.CenterPoint(); // draw the splitter
CWindowDC dc(NULL);
this->DrawSplitterLine(&dc);
} void CSplitterControl::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_bMouseIsDown)
{
// erase the old picture
this->ClientToScreen(&point);
CWindowDC dc(NULL);
this->DrawSplitterLine(&dc); // move spliter control self to the new pos
m_bMouseIsDown = FALSE;
CWnd* pParent = GetParent();
if (pParent && IsWindow(pParent->m_hWnd))
{
CPoint pt = m_ptCurrent;
pParent->ScreenToClient(&pt);
this->MoveSelfWindowToPoint(pt); } // relayout all registerd windows
this->Relayout(); // if need notify the parent
if (m_dwSplitterStyle & SPS_DELTA_NOTIFY)
{
if (pParent && IsWindow(pParent->m_hWnd))
{
CPoint ptDelta = m_ptCurrent - m_ptOrigin;
SPC_NM_DELTA tNotify;
tNotify.hdr.hwndFrom = m_hWnd;
tNotify.hdr.idFrom = GetDlgCtrlID();
tNotify.hdr.code = SPN_DELTA;
tNotify.lDelta = (m_dwSplitterStyle&SPS_VERTICAL)?ptDelta.x:ptDelta.y;
(void)pParent->SendMessage(WM_NOTIFY, tNotify.hdr.idFrom, (LPARAM)&tNotify);
}
}
} CStatic::OnLButtonUp(nFlags, point);
ReleaseCapture();
} void CSplitterControl::DrawSplitterLine(CDC* pDC)
{
CPoint pt = this->m_ptCurrent;
COLORREF clrSplitter = this->m_clrSplitterColor; int nRop = pDC->SetROP2(R2_NOTXORPEN); CRect rcWnd;
GetWindowRect(rcWnd); CPen pen(, , clrSplitter);
CPen* pOldPen = pDC->SelectObject(&pen); if (m_dwSplitterStyle&SPS_VERTICAL)
{
pDC->MoveTo(pt.x - m_lSplitterWidth, rcWnd.top);
pDC->LineTo(pt.x - m_lSplitterWidth, rcWnd.bottom); pDC->MoveTo(pt.x + m_lSplitterWidth, rcWnd.top);
pDC->LineTo(pt.x + m_lSplitterWidth, rcWnd.bottom);
}
else // m_dwSplitterStyle == SPS_HORIZONTAL
{
pDC->MoveTo(rcWnd.left, pt.y - m_lSplitterWidth);
pDC->LineTo(rcWnd.right, pt.y - m_lSplitterWidth); pDC->MoveTo(rcWnd.left, pt.y + m_lSplitterWidth);
pDC->LineTo(rcWnd.right, pt.y + m_lSplitterWidth);
} pDC->SetROP2(nRop);
pDC->SelectObject(pOldPen);
} void CSplitterControl::MoveSelfWindowToPoint(const CPoint& pt)
{
CWnd* pParent = GetParent();
if (!pParent || !::IsWindow(pParent->m_hWnd))
{
return;
} CRect rc;
GetWindowRect(rc);
pParent->ScreenToClient(rc); // calc the new rect
if (m_dwSplitterStyle&SPS_VERTICAL)
{
rc.OffsetRect((pt.x - (rc.left + rc.right) / ), );
}
else
{
rc.OffsetRect(, (pt.y - (rc.top + rc.bottom) / ));
} MoveWindow(rc);
} void CSplitterControl::GetMaxMinPos(int& iMax, int& iMin)
{
CWnd* pParent = GetParent();
ASSERT(pParent);
ASSERT(IsWindow(pParent->m_hWnd)); CRect rcParent;
pParent->GetClientRect(rcParent); // try to get the max/min pos limit from parent window
SPC_NM_MAXMINPOS nmMinmax;
nmMinmax.hdr.hwndFrom = m_hWnd;
nmMinmax.hdr.idFrom = GetDlgCtrlID();
nmMinmax.hdr.code = SPN_MAXMINPOS;
nmMinmax.lMax = (m_dwSplitterStyle&SPS_VERTICAL)?rcParent.right:rcParent.bottom;
nmMinmax.lMin = (m_dwSplitterStyle&SPS_VERTICAL)?rcParent.left:rcParent.top;
(void)pParent->SendMessage(WM_NOTIFY, nmMinmax.hdr.idFrom, (LPARAM)&nmMinmax); // return
iMax = nmMinmax.lMax;
iMin = nmMinmax.lMin;
} BOOL CSplitterControl::RegisterLinkedWindow(DWORD dwLinkSide, CWnd* pWnd)
{
// check parameters
if (NULL == pWnd)
{
return (FALSE);
} // check parameters
if ((SPLS_LINKED_LEFT != dwLinkSide) && (SPLS_LINKED_RIGHT != dwLinkSide))
{
return (FALSE);
} // make sure the spliiter and pWnd have same parent
if (pWnd->GetParent()->m_hWnd != this->GetParent()->m_hWnd)
{
return (FALSE);
} // save it
POSITION pos = m_listLinkedWnds[dwLinkSide].AddTail(pWnd);
if (NULL == pos)
{
return (FALSE);
} return (TRUE);
} void CSplitterControl::Relayout()
{
CWnd* pParent = GetParent();
ASSERT(pParent);
ASSERT(IsWindow(pParent->m_hWnd)); CRect rcSelf;
this->GetWindowRect(rcSelf); // relayout all registered window
for (int i = ; i < ; i++)
{
for (POSITION pos = this->m_listLinkedWnds[i].GetHeadPosition(); NULL != pos;)
{
// get the window object
CWnd* pWnd = this->m_listLinkedWnds[i].GetNext(pos);
if (NULL == pWnd)
{
continue;
} // calc the new pos(the code is not very good)
CRect rcLinked;
pWnd->GetWindowRect(rcLinked);
if (SPS_VERTICAL&m_dwSplitterStyle)
{
if (SPLS_LINKED_LEFT == i)
{
rcLinked.right = rcSelf.left;
}
else
{
rcLinked.left = rcSelf.right;
}
}
else
{
if (SPLS_LINKED_LEFT == i)
{
rcLinked.bottom = rcSelf.top;
}
else
{
rcLinked.top = rcSelf.bottom;
}
} // move it to new pos and then update
pParent->ScreenToClient(rcLinked);
pWnd->MoveWindow(rcLinked, TRUE);
pWnd->Invalidate();
}
}
} void CSplitterControl::ChangePos(CWnd* pWnd, DWORD dwLinkedSide, LONG lDelta)
{
if (NULL == pWnd)
{
return;
} if (FALSE == ::IsWindow(pWnd->GetSafeHwnd()))
{
return;
} CWnd* pParent = pWnd->GetParent();
if (NULL == pParent)
{
return;
} if (FALSE == ::IsWindow(pParent->m_hWnd))
{
return;
} CRect rcWnd;
pWnd->GetWindowRect(rcWnd);
pParent->ScreenToClient(rcWnd); if (SPS_VERTICAL & m_dwSplitterStyle)
{
if (SPLS_LINKED_LEFT & dwLinkedSide) // Same as SPLS_LINKED_UP
{
rcWnd.right += lDelta;
}
else
{
rcWnd.left += lDelta;
}
}
else
{
if (SPLS_LINKED_UP & dwLinkedSide) // Same as SPLS_LINKED_LEFT
{
rcWnd.bottom += lDelta;
}
else
{
rcWnd.top += lDelta;
}
} pWnd->MoveWindow(rcWnd, TRUE);
}

Splitter Control for Dialog的更多相关文章

  1. Splitter

    <!DOCTYPE html> <html> <head> <title>PDemo</title> </head> <b ...

  2. A Complete ActiveX Web Control Tutorial

    A Complete ActiveX Web Control Tutorial From: https://www.codeproject.com/Articles/14533/A-Complete- ...

  3. Total Commander 8.52 Beta 1

    Total Commander 8.52 Beta 1http://www.ghisler.com/852_b1.php 10.08.15 Release Total Commander 8.52 b ...

  4. 屏幕 Dynpro

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  5. ALV详解:OO ALV

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  6. wx

    wx The classes in this module are the most commonly used classes for wxPython, which is why they hav ...

  7. SAP Container Controls(容器)

    BC_CONTROLS_TUTORIAL 效果 代码 REPORT bc_controls_tutorial. *------------------------------------------- ...

  8. SAP 实例 4 CFW

    *&---------------------------------------------------------------------* *& Report demo_cfw ...

  9. arcmap Command

    The information in this document is useful if you are trying to programmatically find a built-in com ...

随机推荐

  1. nuget必备插件(待续)

    DevLib.ExtensionMethods Extend Z.ExtensionMethods

  2. C# TTS-文本转语音

    System.Speech 命名空间包含支持语音识别的类型,你可以从Visual Studio很方便的添加相关组件的引用. System.Speech相关介绍:https://msdn.microso ...

  3. JAVA学习笔记--接口

    一.抽象类和抽象方法 在谈论接口之前,我们先了解一下抽象类和抽象方法.我们知道,在继承结构中,越往下继承,类会变得越来越明确和具体,而往上回溯,越往上,类会变得越抽象和通用.我们有时候可能会需要这样一 ...

  4. Javascript中Generator(生成器)

    阅读目录 Generator的使用: yield yield* next()方法 next()方法的参数 throw方法() return()方法: Generator中的this和他的原型 实际使用 ...

  5. presto 配置mysql.properties异常Database (catalog) must not be specified in JDBC URL for MySQL connector

    在presto 0.210 以后配置mysql.properties的时候,对于jdbc-url属性配置后面要加上对应要链接的database connection-url=jdbc:mysql:// ...

  6. ES6的新特性(16)——Generator 函数的语法

    Generator 函数的语法 简介 基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generator 函数的语法和 API,它的 ...

  7. strace 命令

    介绍 strace常用来跟踪进程执行时的系统调用和所接收的信号. 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核 ...

  8. 20181016-4 Alpha阶段第1周/共2周 Scrum立会报告+燃尽图 03

    此作业链接地址见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2248 Scrum master:王硕 一.小组介绍 组长:王一可 组员 ...

  9. 团队Alpha冲刺(十)

    目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:翟丹丹 组员7:何家伟 组员8:政演 组员9:黄鸿杰 组员10:刘一好 组员11:何宇恒 展示 ...

  10. BETA事后总结

    目录 所有成员 项目宣传视频链接 贡献比例 工作流程 组员分工 本组 Beta 冲刺站立会议博客链接汇总 燃尽图 原计划.达成情况及原因分析 组员:胡绪佩 组员:周政演 组员:庄卉 组员:何家伟 组员 ...