对话框的分隔条控件






4.92/5 (19投票s)
一个对话框的分隔条控件,可以自动更改关联窗口的位置。
引言
是的,这是另一个-另一个分割条控件。该控件基于这个(Another splitter control for dialog)的控件。我对原始代码做了一些修改,使其更容易使用。新的分割条控件可以在用户更改分割条时自动更改链接控件的位置。就是这样!
背景
我正在开发一个有很多对话框的 GUI 应用程序。但之前的开发者将所有对话框都设计成了固定大小。有时这没问题,但有时这会让界面难以使用。我必须滚动滚动条才能看到被固定大小对话框隐藏的部分。当我有机会修改它时,我决定做一些改变。从那时起,我一直在寻找一个可以在对话框中使用的分割条控件。
CodeProject 上有很多种类的分割条控件。我非常喜欢 Hung Nguyen 编写的那个:《Another splitter control for dialog》。svn 的提交对话框也使用了那个。当我在我的许多应用程序中使用这个控件时,我发现它有一些小问题,我不得不在很多地方调用 ChangePos
函数。换句话说,它不能自动移动相关的控件。所以我做了一个新的来解决这个问题。
Using the Code
步骤 1:在资源编辑器中为你的对话框添加一个图片控件。
在你的对话框上放置一个图片控件,给它 ID IDC_SPLITTER1
。更改控件的大小,使其看起来像一个水平条。双击 IDC_SPLITTER1
控件,更改属性如下。然后以同样的方式添加一个垂直的,给它 ID IDC_SPLITTER2
。
实际上,以上所有操作只是为了让我们不必计算分割条的大小。你可以通过 CSplitterControl::Create
函数指定分割条的大小。
步骤 2:为对话框类添加分割条
将 SplitterControl.h 和 SplitterControl.cpp 添加到你的项目中。在 dialog
类的 .h 文件中插入 #include "splittercontrol.h"
。
然后,添加成员变量
private:
CSplitterControl m_wndSplitter1;
CSplitterControl m_wndSplitter2;
在 OnInitDialog
函数中创建分割条。
这里有一些注意事项
- 使用
SPS_VERTICAL
或SPS_HORIZONTAL
来指定分割条的样式。 - 你可以为分割条线指定一个 RGB 颜色(默认 ID 为 RGB(120, 120, 120))。
- 你可以指定分割条线的宽度(默认为 1)。
- 分割条的
width(SPS_VERTICAL)
或height(SPS_HORIZONTAL)
取决于调用Create
函数时rect
的width
。
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(0, 0, 255));
if (FALSE == bRet)
{
AfxMessageBox("m_wndSplitter2 create failed");
}
步骤 3:添加链接的窗口
分割条可以在用户更改分割条位置时自动移动链接窗口的位置。所以我们应该指定哪个窗口需要更改 pos
。CSplitterControl::RegisterLinkedWindow
函数承担了这个工作。请查看下面的示例。
// 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();
请记住,SPLS_LINKED_LEFT
表示控件在分割条的左侧。SPLS_LINKED_RIGHT
表示右侧。这两个用于垂直分割条。如果控件通过 SPLS_LINKED_LEFT
链接到垂直分割条,则表示控件的右侧位置将由分割条更改。SPLS_LINKED_RIGHT
与 SPLS_LINKED_LEFT
类似。
SPLS_LINKED_UP
和 SPLS_LINKED_DOWN
如其名称所示,控件将位于水平分割条的上方。
在我们将控件链接到分割条后,我们几乎就完成了。为了使界面看起来更好,我们应该调用 CSplitterControl::Relayout
函数来进行初始布局。你可以随时调用 CSplitterControl::Relayout
函数。
步骤 4:分割条的限制位置
通常,我们需要设置分割条的移动范围。这在基于文档/视图的应用程序中不是很重要。但在基于对话框的应用程序中,我们必须自己处理窗口的边缘。所以,分割条的限制位置对于基于对话框的应用程序非常重要。
在 sizabled
对话框中,分割条的限制位置不是固定的。一旦我们更改了对话框的大小,我们就必须更改分割条的新限制位置。这不太好。在我的分割条控件中,每次准备更改分割条位置之前,它们都会向父窗口发送一个通知消息。所以如果你想使用这个功能,设置限制位置,只需处理通知消息。通知消息名为 SPN_MAXMINPOS
。这里有一些示例代码。
在 dialog
类的 .h 文件中为通知消息(SPN_MAXMINPOS
)添加消息处理函数。
afx_msg void OnMaxMinInfo(NMHDR* pNMHDR, LRESULT* pResult);
映射消息
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()
实现消息处理函数
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 + 50;
pNewMaxMinPos->lMax = rcCancel.left - STD_GAP;
}
else
{
pNewMaxMinPos->lMin = rcList.top + 50;
pNewMaxMinPos->lMax = rcEdit.bottom - 50;
}
}
步骤 5:一些特殊使用方法
并非所有事情都会自动完成。分割条需要我们在对话框初始化时向其注册所有链接的控件。所以如果控件在此时未创建,我们就无法将其注册到分割条。因此,分割条提供了另一种更改这些控件位置的方法。内核函数是 CSplitterControl::ChangePos
。该函数接受一个参数 dwLinkedSide
来指定控件在分割条的哪一侧。而 lDelta
通常来自通知消息 SPN_DELTA
。
SPN_DELTA
通知消息在用户释放鼠标时发送。它在 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;
};
这里有一些示例代码,展示了如何使用此通知消息。
首先,在创建分割条时,我们需要使用 SPS_DELTA_NOTIFY
样式。
BOOL bRet = m_wndSplitter1.Create(WS_CHILD | WS_VISIBLE, rc,
this, IDC_SPLITTER1, SPS_VERTICAL|SPS_DELTA_NOTIFY);//, RGB(255, 0, 0));
然后,添加消息映射。
BEGIN_MESSAGE_MAP(CSplitterControlDemoDlg, CDialog)
// ...
ON_NOTIFY(SPN_DELTA, IDC_SPLITTER1, OnSplitter1Delta)
// ...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
最后,实现它。
void CSplitterControlDemoDlg::OnSplitter1Delta(NMHDR* pNMHDR, LRESULT* pResult)
{
// this function just want to show you how to use the delta event
*pResult = 0;
SPC_NM_DELTA* pDelta = (SPC_NM_DELTA*)pNMHDR;
if (NULL == pDelta)
{
return;
}
m_wndSplitter1.ChangePos(&m_edHelp, SPLS_LINKED_LEFT, pDelta->lDelta);
}
谢谢
感谢 Hung Nguyen 和他的文章《Another splitter control for dialog》及其演示项目。这对我帮助很大,我的示例代码也基于此。哦,我真是太懒了。
关注点
示例方法总是有用的。
历史
- 2013-05-19:初版