65.9K
CodeProject 正在变化。 阅读更多。
Home

对话框的分隔条控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (19投票s)

2013年5月21日

CPOL

5分钟阅读

viewsIcon

62163

downloadIcon

6185

一个对话框的分隔条控件,可以自动更改关联窗口的位置。

引言

是的,这是另一个-另一个分割条控件。该控件基于这个(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.hSplitterControl.cpp 添加到你的项目中。在 dialog 类的 .h 文件中插入 #include "splittercontrol.h"

然后,添加成员变量

private:
    CSplitterControl    m_wndSplitter1;
    CSplitterControl    m_wndSplitter2;

OnInitDialog 函数中创建分割条。

这里有一些注意事项

  • 使用 SPS_VERTICALSPS_HORIZONTAL 来指定分割条的样式。
  • 你可以为分割条线指定一个 RGB 颜色(默认 ID 为 RGB(120, 120, 120))。
  • 你可以指定分割条线的宽度(默认为 1)。
  • 分割条的 width(SPS_VERTICAL)height(SPS_HORIZONTAL) 取决于调用 Create 函数时 rectwidth
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:添加链接的窗口

分割条可以在用户更改分割条位置时自动移动链接窗口的位置。所以我们应该指定哪个窗口需要更改 posCSplitterControl::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_RIGHTSPLS_LINKED_LEFT 类似。

SPLS_LINKED_UPSPLS_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:初版
© . All rights reserved.