对话框属性表与向导

一、CPropertySheet 类

1、类简介

CPropertySheet的对象表示属性表,它通常由一个或更多CPropertyPage对象组成。尽管该类不是从CDialog类派生出来的,但是对CPropertySheet对象的管理和对CDialog对象的管理非常相似。例如,属性表的创建需要两部构造法:首先调用构造函数,接着调用DoModal创建模态属性表,或者调用Create创建非模态属性表。

同样,属性表对象与外部对象之间的数据交换过程和对话框对象与外部对象之间的数据交换过程也非常类似。其主要区别在于,一般通过CPropertyPage对象的成员变量完成属性表的设置,而非通过CPropertySheet对象本身的成员变量。

2、类CPropertySheet成员

1)CPropertySheet:构造CPropertySheet 对象。

2)Construct:构造CPropertySheet 对象

3)GetActiveIndex:获取属性表当前活动页的索引

4)GetPageIndex:获取属性表指定页的索引

5)GetPageCount:获取属性表总的页数

6)GetPage:获取指定页的指针

7)GetActivePage:返回当前活动页对象

8)SetActivePage:用于设置活动页对象

9)SetTitle:设置属性表标题

10)GetTabControl:获取标签控件指针

11)SetFinishText:设置“完成”按钮的文本

12)SetWizardButtons:启用向导按钮

13)SetWizardMode:启用向导模式

14)EnableStackedTabs:指定属性表使用堆叠标签还是滚动标签

15)DoModal:创建模态属性表

16)Create:创建非模态属性表

17)AddPage:向属性表添加属性页

18)RemovePage:从属性表移除属性页

19)PressButton:模仿属性表指定按钮的选择

20)EndDialog:结束模态属性表

二、CPropertyPage类

该类实现了对属性页的封装,同时还提供了一些非常有用的成员函数和成员变量,通过这些成员,可以很方便地操作和定制属性页的各种特性。

1、类简介

类CPropertyPage从对话框类派生,他的对象表示单个的属性表页,如同使用标准的对话框一样,在属性表编程中,一般使用从该类派生的属性页类。它的使用和对话框类非常相似,但是,由于属性表本身不含数据成员,因此,实际的数据交换发生在属性表页数据成员和相应的控件之间。

2、重要成员函数介绍

1)CPropertyPage::OnApply

此函数为属性页的虚函数,当单击“应用”按钮时,由框架负责调用此函数,它的定义如下:

BOOL CPropertyPage::OnApply()
{
    ASSERT_VALID(this);
    OnOK();
    return TRUE;
}

从其实现过程看,他首先调用CPropertyPage::OnOK,然后返回TRUE,这一点非常重要,因为CPropertyPage::OnApply的返回值决定了用户对数据所做的更改是否被接受。从下文CPropertyPage::OnOK的默认实现中可以看到,此函数除了返回TRUE外,并没有做任何有意义的工作。因此,在属性表编程中,常常需要重载此函数,已完成需要的任务。

2)CPropertyPage::OnOK

此函数为属性页的虚函数,当单击”确定“、”应用“、以及”关闭“等按钮是,在框架用OnKillActive后,即会立刻调用此函数,它的定义如下:

void CPropertyPage::OnOK()
{
    ASSERT_VALID(this);
}
从其实现过程看,此函数并没有做任何有意义的工作,只是简单将页标识为干净页,以反映数据在OnKillActive中得到更新。当用户关闭属性表时,如果想对当前活动页执行指定的行为,则可以重载函数。

3)CPropertyPage::OnSetActive

此函数为属性页的虚函数,当用户激活某页时,由框架负责调用。其实现如下

BOOL CPropertyPage::OnSetActive()
{
    ASSERT_VALID(this);
    if (m_bFirstSetActive)
        m_bFirstSetActive = FALSE;
    else
        UpdateData(FALSE);
    return TRUE;
}

其中,m_bFirstSetActive为属性页类的内部成员变量,它在构造属性页过程中获得TRUE值。OnSetActive通过m_bFirstSetActive判断是否为首次激活该页,如果不是,它将调用UpdateData更新页的显示状态。

此函数的缺省实现是:如果在此之前,还没有创建该页的窗口,那么,它将为该页创建窗口,并使它成为活动页。当然,可以重载此函数,以便当激活某页时,完成初始化该页的任务。不过需要注意的是,重载该函数以后,在做任何处理之前,必须保证对缺省版本的调用。

4)CPropertyPage::OnKillActive
此函数为属性页的虚函数,当某页不再是活动页时,由框架负责调用。其实现如下:

BOOL CPropertyPage::OnKillActive()
{
    ASSERT_VALID(this);
    if (!UpdateData())
    {
        TRACE0("UpdateData failed during page deactivation\n");
        return FALSE;
    }

    return TRUE;
}

从该函数的实现过程来看,它主要完成的任务是,将属性页中控件所做的设置拷贝到属性页中控件所对应的成员变量中。如果数据没有被成功地更新,则将产生一个对话框数据验证(DDV)错误,但属性页仍应保持焦点。在此成员函数成功返回之后,框架将调用该页的OnOK成员函数。

5)CPropertyPage::OnNotify

实际上,框架调用OnAppply、OnOK、OnSetAvtive、以及OnKillActive等成员函数时,都是通过对属性页通知消息的相应,他们对应的通知消息分别为PSN_APPLY(用户按下”应用“、”确定“等时发送的均是此消息)、PSN_SETACTIVE以及PSN_KILLACTIVE等。这些消息以WM_NOTIFY的形式发送,然后,由CPropertyPage::OnNotify进行判断,以作出合适的处理。该函数的实现如下:


BOOL CPropertyPage::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    ASSERT(pResult != NULL);
    NMHDR* pNMHDR = (NMHDR*)lParam;
    …
    //默认处理
    switch (pNMHDR->code)
    {
    case PSN_SETACTIVE:
        {
            CPropertySheet* pSheet = DYNAMIC_DOWNCAST(CPropertySheet,
GetParent());
            if (pSheet != NULL && !(pSheet->m_nFlags &
WF_CONTINUEMODAL) && !(pSheet->m_bModeless))
                *pResult = -1;
            else
                *pResult = OnSetActive() ? 0 : -1;
        }
        break;
    case PSN_KILLACTIVE:
        *pResult = !OnKillActive();
        break;
    case PSN_APPLY:
        *pResult = OnApply() ? PSNRET_NOERROR : PSNRET_INVALID_NOCHANGEPAGE;
        break;
    case PSN_RESET:
        OnReset();
        break;
    case PSN_QUERYCANCEL:
        *pResult = !OnQueryCancel();
        break;
     …
    default:
        return FALSE; // not handled
    }
    return TRUE; // handled
}

此函数对属性页接收到的各种以WM_NOTIFY的形式发送的消息加以辨别,并进行适当的处理,实际上,这些消息均源于属性表的按钮,可是怎么最后折回到属性页呢?实际上,”应用“、”确定“等按钮是属性表的子控件,而非属性页的子控件。

三、属性表创建

1、创建模态属性表

创建模态属性表和创建模态对话框一样简单,通常按照如下步骤进行即可:

1)对于每个属性页,使用资源编辑器创建包含网页内容和特征的对话框模板,将其标题设为用户期望显示在属性表页顶部标签上的

字符串;

2)对于每个属性页,使用类向导从CPropertyPage派生属性页类用于封装属性页,在属性页模板上添加需要的控件,

并且添加相应的数据成员;

3)使用类向导从CPropertySheet派生属性表类,然后,在属性表类中添加属性页类内嵌成员,并在属性表类构造函数中调用CPropertySheet::AddPage,这将各属性表页按照既定的顺序添加到属性表中,接着,在想要的类中添加属性表类内嵌成员;

4)最后调用属性表的DoModal成员函数创建属性表,并显示出来。

当然,属性表创建之后,还有另外的一些工作需要完成,如对“应用”按钮的处理等。

2、创建非模态属性表

创建非模态属性表所使用的创建函数不是DoModal,而是CPropertySheet::Create,而且创建过程也不像模态属性表那样简单,还需要做一些额外的工作,其中最重要的一项工作就是当属性表处于打开状态时,必须完成属性表和它正在修改的外部对象之间的数据交换,至于何时将非模态属性表的设置应用到外部对象中,这里给出两种简单的方法。

1)每当用户更改任何值时,就应用当前属性页设置;

2)提供“应用”按钮,允许用户将对属性页所做的更改累积起来,最后将它们一起提交给外部对象。

属性表和属性页窗口之间的控件关系是独立的,只是在传统的属性表制作中,两者窗口的 背景色都使用默认的灰色,因此很难发现二者在控件的差异,不过可以通过重载函数CPropertyPage::OnSetActive在对属性页初始化时使二者的空间位置一致。一个简单的实现如下:

BOOL CPageX::OnSetActive()
{
    CRect rc,p;
    this->GetClientRect(&rc);
    this->GetParent()->GetClientRect(&p);
    //求出对话框与属性页窗口的位置差
    int diff=p.bottom-rc.bottom;
    //将页面向左上方靠,与父窗口的位置一样
    rc.SetRect(0,0,p.Width(),p.Height()-diff);
    //-diff 让属性页的按钮有空间显示
    MoveWindow(&rc);
    return CPropertyPage::OnSetActive();
}
四、应用按钮的处理

1、应用按钮处理方法一——属性表处理

这种处理方法的本质就是在属性表中对”应用“按钮进行相应处理,不过要手工完成这个过程。下面以为派生属性表类CFontSheet添加“应用”按钮的处理函数OnApply为例,讲述这一方法。

1)添加ON_BN_CLICKED 映射

在类CFontSheet 的实现文件中添加ON_BN_CLICKED 映射,注意添加的位置:

BEGIN_MESSAGE_MAP(CFontSheet, CPropertySheet)
    //{{AFX_MSG_MAP(CFontSheet)
        // NOTE - the ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
    ON_BN_CLICKED (ID_APPLY_NOW, OnApply)
END_MESSAGE_MAP()
2)声明处理函数

在类CFontSheet 的头文件中添加按钮处理函数,同样需要注意其位置:

protected:
    //{{AFX_MSG(CFontSheet)
        // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSG
    afx_msg void OnApply ();
    DECLARE_MESSAGE_MAP()
3)添加处理函数体

实现按钮的处理函数,一般实现形式如下

void CFontSheet::OnApply ()
{
    GetActivePage ()->UpdateData (TRUE);
    …
    pView->SendMessage (WM_USER_APPLY, wParam, lParam);
    Page1.SetModified (FALSE);
    Page2.SetModified (FALSE);
    …
}

它首先调用UpdateData,以更新活动页的成员变量值,这样,应用程序的其他部分就可以取得用户设定的最新值;

然后,向需要这些数据的类发送自定义消息,以便这些类能够使用最新的数据来完成进一步的操作;

最后,将该属性表的所有页设定为未被修改,因为只有这样,属性表的“应用”按钮才可以变灰。

2、应用按钮处理方法二——属性页处理

默认的OnApply什么都不做,甚至“应用”按钮是灰色的。因此,必须对此虚函数进行重载。对于给定的页,通常在检测到用户对页作出修改后,将以TRUE为参数调用SetModified来设置页的“脏”标志。如果用户按下了”确定“或者”应用“按钮,则框架的调用策略如下:

1)如果在默认活动页(属性表最初显示后处于活动的页)之后(属性表上显示的页的先后顺序)的某页为当前活动页时,用户单击了“应用”按钮,那么框架首先调用默认活动页的OnApply 函数,然后调用该页的OnApply 函数;

2)如果在默认活动页之前的某页为当前活动页时,用户单击了“应用”按钮,那么框架首先调用该页的OnApply 函数,然后调用默认活动页的OnApply 函数。

接下来,以CPage1Font为某一属性页类为例,来讲述这种方法的实现过程。利用类向导为CPage1Font属性页重在OnApply函数,CFontSheet为属性表类,最终实现如下:

BOOL CPage1Font::OnApply()
{
    // TODO: Add your specialized code here and/or call the base class
    CMainFrame* pMFrm=(CMainFrame*)AfxGetMainWnd();
    pMFrm->GetActiveView()->SendMessage(WM_USERAPPLY);
    return TRUE;
}

其中,函数OnApply向视图类发送了用户自定义的消息WM_USERAPPLY。然后,在视图类中添加消息WM_USERAPPLY的处理函数,

在其中可以完成用户期望的操作。需要注意的是,OnApply必须返回TRUE以便接受用户对数据的更改。

当用户按下”应用“按钮时,所引起的调用如下:

CFontSheet::OnCommand
CPropertySheet::OnCommand
CWnd::OnCommand
CFontSheet::OnCmdMsg
CPropertySheet::OnCmdMsg
CWnd::OnCmdMsg
CCmdTarget::OnCmdMsg
CWnd::WindowProc
CWnd::DefWindowProc
::CallWindowProc
上述过程再次说明,“应用”按钮是属性表的子控件。单击“应用”按钮以后,首先将单击消息交给父窗口—属性表处理,当在属性表中找不到相应的处理函数时,将会引发对CWnd::DefWindowProc 的调用,此函数会将消息交由属性页进行处理。

这里有个简单的示例:点击打开链接







  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值