【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 2371|回复: 4

非模态对话框-vc++编程指南

[复制链接]
发表于 2006-1-13 11:13:11 | 显示全部楼层 |阅读模式
5.4 非模态对话框<BR><BR> 5.4.1 非模态对话框的特点 <BR><BR>  与模态对话框不同,非模态对话框不垄断用户的输入,用户打开非模态对话框后,仍然可以与其它界面进行交互。 <BR><BR>  非模态对话框的设计与模态对话框基本类似,也包括设计对话框模板和设计CDialog类的派生类两部分。但是,在对话框的创建和删除过程中,非模态对话框与模态对话框相比有下列不同之处: <BR><BR> 非模态对话框的模板必须具有Visible风格,否则对话框将不可见,而模态对话框则无需设置该项风格。更保险的办法是调用CWnd::ShowWindow(SW_SHOW)来显示对话框,而不管对话框是否具有Visible风格。 <BR><BR> 非模态对话框对象是用new操作符在堆中动态创建的,而不是以成员变量的形式嵌入到别的对象中或以局部变量的形式构建在堆栈上。通常应在对话框的拥有者窗口类内声明一个指向对话框类的指针成员变量,通过该指针可访问对话框对象。 <BR><BR> 通过调用CDialog::Create函数来启动对话框,而不是CDialog:oModal,这是模态对话框的关键所在。由于Create函数不会启动新的消息循环,对话框与应用程序共用同一个消息循环,这样对话框就不会垄断用户的输入。Create在显示了对话框后就立即返回,而DoModal是在对话框被关闭后才返回的。众所周知,在MFC程序中,窗口对象的生存期应长于对应的窗口,也就是说,不能在未关闭屏幕上窗口的情况下先把对应的窗口对象删除掉。由于在Create返回后,不能确定对话框是否已关闭,这样也就无法确定对话框对象的生存期,因此只好在堆中构建对话框对象,而不能以局部变量的形式来构建之。 <BR><BR> 必须调用CWnd:estroyWindow而不是CDialog::EndDialog来关闭非模态对话框。调用CWnd:estroyWindow是直接删除窗口的一般方法。由于缺省的CDialog::OnOK和CDialog::OnCancel函数均调用EndDialog,故程序员必须编写自己的OnOK和OnCancel函数并且在函数中调用DestroyWindow来关闭对话框。 <BR><BR> 因为是用new操作符构建非模态对话框对象,因此必须在对话框关闭后,用delete操作符删除对话框对象。在屏幕上一个窗口被删除后,框架会调用CWnd:ostNcDestroy,这是一个虚拟函数,程序可以在该函数中完成删除窗口对象的工作,具体代码如下<BR>void CModelessDialog:ostNcDestroy<BR>{<BR>delete this; //删除对象本身<BR>}<BR>这样,在删除屏幕上的对话框后,对话框对象将被自动删除。拥有者对象就不必显式的调用delete来删除对话框对象了。 <BR><BR> 必须有一个标志表明非模态对话框是否是打开的。这样做的原因是用户有可能在打开一个模态对话框的情况下,又一次选择打开命令。程序根据标志来决定是打开一个新的对话框,还是仅仅把原来打开的对话框激活。通常可以用拥有者窗口中的指向对话框对象的指针作为这种标志,当对话框关闭时,给该指针赋NULL值,以表明对话框对象已不存在了。 <BR><BR> 提示:在C++编程中,判断一个位于堆中的对象是否存在的常用方法是判断指向该对象的指针是否为空。这种机制要求程序员将指向该对象的指针初始化为NULL值,在创建对象时将返回的地址赋给该指针,而在删除对象时将该指针置成NULL值。  <BR><BR>  根据上面的分析,我们很容易把Register程序中的登录数据对话框改成非模态对话框。这样做的好处在于如果用户在输入数据时发现编辑视图中有错误的数据,那么不必关闭对话框,就可以在编辑视图中进行修改。 <BR><BR>  请读者按下面几步操作: <BR><BR>  在登录数据对话框模板的属性对话框的More Styles页中选择Visible项。 <BR><BR>  在RegisterView.h头文件的CRegisterView类的定义中加入<BR>public:<BR>CRegisterDialog* m_pRegisterDlg; <BR><BR>  在RegisterView.h头文件的头部加入对CRegisterDialog类的声明<BR>class CRegisterDialog;<BR>加入该行的原因是在CRegisterView类中有一个CRegisterDialog类型的指针,因此必须保证CRegisterDialog类的声明出现在CRegisterView之前,否则编译时将会出错。解决这个问题有两种办法,一种办法是保证在#include “RegisterView.h”语句之前有#include “RegisterDialog.h”语句,这种办法造成了一种依赖关系,增加了编译负担,不是很好;另一种办法是在CRegisterView类的声明之前加上一个对CRegisterDialog的声明来暂时“蒙蔽”编译器,这样在有#include “RegisterView.h”语句的模块中,除非要用到CRegisterDialog类,否则不用加入#include “RegisterDialog.h”语句。 <BR><BR>  在RegisterDialog.cpp文件的头部的#include语句区的末尾添加下面两行<BR>#include "RegisterDoc.h"<BR>#include "RegisterView.h" <BR><BR>  利用ClassWizard为CRegisterDialog类加入OnCancel和PostNcDestroy成员函数。加入的方法是进入ClassWizard后选择Message Maps页,并在Class name栏中选择CRegisterDialog。然后,在Object IDs栏中选择IDCANCEL后,在Messages栏中双击BN_CLICKED,这就创建了OnCancel。要创建PostNcDestroy,先在Object IDs栏中选择CRegisterDialog,再在Messages栏中双击PostNcDestroy即可。 <BR><BR>  分别按清单5.10和5.11,对CRegisterView类和CRegisterDialog类进行修改。 <BR><BR> 清单5.10 CRegisterView类的部分代码 <BR><BR> CRegisterView::CRegisterView() <BR><BR> { <BR><BR> // TODO: add construction code here <BR><BR>   <BR><BR> m_pRegisterDlg=NULL; //指针初始化为NULL <BR><BR> } <BR><BR>   <BR><BR> void CRegisterView::OnEditRegister()  <BR><BR> { <BR><BR> // TODO: Add your command handler code here <BR><BR>   <BR><BR>   <BR><BR> if(m_pRegisterDlg) <BR><BR> m_pRegisterDlg-&gt;SetActiveWindow(); //激活对话框 <BR><BR> else <BR><BR> { <BR><BR> //创建非模态对话框 <BR><BR> m_pRegisterDlg=new CRegisterDialog(this); <BR><BR> m_pRegisterDlg-&gt;Create(IDD_REGISTER,this); <BR><BR> } <BR><BR> }<BR>  清单5.11 CRegisterDialog的部分代码 <BR><BR> void CRegisterDialog:ostNcDestroy()  <BR><BR> { <BR><BR> // TODO: Add your specialized code here and/or call the base class <BR><BR> delete this; //删除对话框对象 <BR><BR> } <BR><BR> void CRegisterDialog::OnCancel()  <BR><BR> { <BR><BR> // TODO: Add extra cleanup here <BR><BR> ((CRegisterView*)m_pParent)-&gt;m_pRegisterDlg=NULL; <BR><BR> DestroyWindow(); //删除对话框  <BR><BR> } <BR><BR>  CRegisterView::OnEditRegister函数判断登录数据对话框是否已打开,若是,就激活对话框,否则,就创建该对话框。该函数中主要调用了下列函数: <BR><BR>  调用CWnd::SetActiveWindow激活对话框,该函数的声明为<BR>CWnd* SetActiveWindow( );<BR>该函数使本窗口成为活动窗口,并返回原来活动的窗口。 <BR><BR>  调用CDialog::Create来显示对话框,该函数的声明为<BR>BOOL Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );<BR>参数nIDTemplate是对话框模板的ID。pParentWnd指定了对话框的父窗口或拥有者。 <BR><BR>  当用户在登录数据对话框中点击“取消”按钮后,CRegisterDialog::OnCancel将被调用,在该函数中调用CWnd:estroyWindow来关闭对话框,并且将CRegisterView的成员m_pRegisterDlg置为NULL以表明对话框被关闭了。调用DestroyWindow导致了对CRegisterDialog:ostNcDestroy的调用,在该函数中用delete操作符删除了CRegisterDialog对象本身。 <BR><BR>  编译并运行Register,现在登录数据对话框已经变成一个非模态对话框了。 <BR><BR>
 楼主| 发表于 2006-1-13 11:15:25 | 显示全部楼层
5.4.2 窗口对象的自动清除 <BR><BR>  一个MFC窗口对象包括两方面的内容:一是窗口对象封装的窗口,即存放在m_hWnd成员中的HWND(窗口句柄),二是窗口对象本身是一个C++对象。要删除一个MFC窗口对象,应该先删除窗口对象封装的窗口,然后删除窗口对象本身。 <BR><BR>  删除窗口最直接方法是调用CWnd:estroyWindow或:estroyWindow,前者封装了后者的功能。前者不仅会调用后者,而且会使成员m_hWnd保存的HWND无效(NULL)。如果DestroyWindow删除的是一个父窗口或拥有者窗口,则该函数会先自动删除所有的子窗口或被拥有者,然后再删除父窗口或拥有者。在一般情况下,在程序中不必直接调用DestroyWindow来删除窗口,因为MFC会自动调用DestroyWindow来删除窗口。例如,当用户退出应用程序时,会产生WM_CLOSE消息,该消息会导致MFC自动调用CWnd:estroyWindow来删除主框架窗口,当用户在对话框内按了OK或Cancel按钮时,MFC会自动调用CWnd:estroyWindow来删除对话框及其控件。 <BR><BR>  窗口对象本身的删除则根据对象创建方式的不同,分为两种情况。在MFC编程中,会使用大量的窗口对象,有些窗口对象以变量的形式嵌入在别的对象内或以局部变量的形式创建在堆栈上,有些则用new操作符创建在堆中。对于一个以变量形式创建的窗口对象,程序员不必关心它的删除问题,因为该对象的生命期总是有限的,若该对象是某个对象的成员变量,它会随着父对象的消失而消失,若该对象是一个局部变量,那么它会在函数返回时被清除。 <BR><BR>  对于一个在堆中动态创建的窗口对象,其生命期却是任意长的。初学者在学习C++编程时,对new操作符的使用往往不太踏实,因为用new在堆中创建对象,就不能忘记用delete删除对象。读者在学习MFC的例程时,可能会产生这样的疑问,为什么有些程序用new创建了一个窗口对象,却未显式的用delete来删除它呢?问题的答案就是有些MFC窗口对象具有自动清除的功能。 <BR><BR>  如前面讲述非模态对话框时所提到的,当调用CWnd:estroyWindow或:estroyWindow删除一个窗口时,被删除窗口的PostNcDestroy成员函数会被调用。缺省的PostNcDestroy什么也不干,但有些MFC窗口类会覆盖该函数并在新版本的PostNcDestroy中调用delete this来删除对象,从而具有了自动清除的功能。此类窗口对象通常是用new操作符创建在堆中的,但程序员不必操心用delete操作符去删除它们,因为一旦调用DestroyWindow删除窗口,对应的窗口对象也会紧接着被删除。 <BR><BR>  不具有自动清除功能的窗口类如下所示。这些窗口对象通常是以变量的形式创建的,无需自动清除功能。 <BR><BR>  所有标准的Windows控件类。 <BR><BR>  从CWnd类直接派生出来的子窗口对象(如用户定制的控件)。 <BR><BR>  切分窗口类CSplitterWnd。 <BR><BR>  缺省的控制条类(包括工具条、状态条和对话条)。 <BR><BR>  模态对话框类。 <BR><BR>  具有自动清除功能的窗口类如下所示,这些窗口对象通常是在堆中创建的。 <BR><BR> 主框架窗口类(直接或间接从CFrameWnd类派生)。 <BR><BR> 视图类(直接或间接从CView类派生)。 <BR><BR>   <BR><BR>  读者在设计自己的派生窗口类时,可根据窗口对象的创建方法来决定是否将窗口类设计成可以自动清除的。例如,对于一个非模态对话框来说,其对象是创建在堆中的,因此应该具有自动清除功能。 <BR><BR>  综上所述,对于MFC窗口类及其派生类来说,在程序中一般不必显式删除窗口对象。也就是说,既不必调用DestroyWindow来删除窗口对象封装的窗口,也不必显式地用delete操作符来删除窗口对象本身。只要保证非自动清除的窗口对象是以变量的形式创建的,自动清除的窗口对象是在堆中创建的,MFC的运行机制就可以保证窗口对象的彻底删除。 <BR><BR>  如果需要手工删除窗口对象,则应该先调用相应的函数(如CWnd:estroyWindow)删除窗口,然后再删除窗口对象.对于以变量形式创建的窗口对象,窗口对象的删除是框架自动完成的.对于在堆中动态创建了的非自动清除的窗口对象,必须在窗口被删除后,显式地调用delete来删除对象(一般在拥有者或父窗口的析构函数中进行).对于具有自动清除功能的窗口对象,只需调用CWnd:estroyWindow即可删除窗口和窗口对象。注意,对于在堆中创建的窗口对象,不要在窗口还未关闭的情况下就用delete操作符来删除窗口对象. <BR><BR> 提示:在非模态对话框的OnCancel函数中可以不调用CWnd:estroyWindow,取而代之的是调用CWnd::ShowWindow(SW_HIDE)来隐藏对话框.在下次打开对话框时就不必调用Create了,只需调用CWnd::ShowWindow(SW_SHOW)来显示对话框.这样做的好处在于对话框中的数据可以保存下来,供以后使用.由于拥有者窗口在被关闭时会调用DestroyWindow删除每一个所属窗口,故只要非模态对话框是自动清除的,程序员就不必担心对话框对象的删除问题.  <BR>
 楼主| 发表于 2006-1-13 11:15:43 | 显示全部楼层
<>5.5 标签式对话框<BR><BR>  在设计较为复杂的对话框时,常常会遇到这种情况:对某一事物的设置或选项需要用到大量的控件,以至于一个对话框放不下,而这些控件描述的是类似的属性,不能分开。用普通的对话框技术,这一问题很难解决。 <BR><BR>  MFC提供了对标签式对话框的支持,可以很好的解决上述问题。标签式对话框实际上是一个包含了多个子对话框的对话框,这些子对话框通常被称为页(Page)。每次只有一个页是可见的,在对话框的顶端有一行标签,用户通过单击这些标签可切换到不同的页。显然,标签式对话框可以容纳大量的控件。在象Word和Developer Studio这样复杂的软件中,用户会接触到较多的标签式对话框,一个典型的标签式对话框如图5.10所示。<BR>&gt;<BR>5.5.1 标签式对话框的创建 <BR><BR>  为了支持标签式对话框,MFC提供了CPropertySheet类和CPropertyPage类。前者代表对话框的框架,后者代表对话框中的某一页。CPropertyPage是CDialog类的派生类,而CPropertySheet是CWnd类的派生类。虽然CPropertySheet不是CDialog类的派生类,但使用CPropertySheet对象的方法与使用CDialog对象是类似的。标签式对话框是一种特殊的对话框,因此,和普通对话框相比,它的设计与实现既有许多相似之处,又有一些不同的特点。 <BR><BR>  创建一个标签式对话框一般包括以下几个步骤: <BR><BR>  分别为各个页创建对话框模板,去掉缺省的OK和Cancel按钮。每页的模板最好具有相同的尺寸,如果尺寸不统一,则框架将根据最大的页来确定标签对话框的大小。在创建模板时,需要在模板属性对话框中指定下列属性: <BR><BR>  指定标题(Caption)的内容。标题的内容将显示在该页对应的标签中。 <BR><BR>  选择TitleBar、Child、ThinBorder和Disable属性。 <BR><BR>  根据各个页的模板,用ClassWizard分别为每个页创建CPropertyPage类的派生类。这一过程与创建普通对话框类的过程类似,不同的是在创建新类对话框中应在Base class一栏中选择CPropertyPage而不是CDialog。 <BR><BR>  用ClassWizard为每页加入与控件对应的成员变量,这个过程与为普通对话框类加入成员变量类似。 <BR><BR>  程序员可直接使用CPropertySheet类,也可以从该类派生一个新类。除非要创建一个非模态对话框,或要在框架对话框中加入控件,否则没有必要派生一个新类。如果直接使用CPropertySheet类,则一个典型的标签式对话框的创建代码如清单5.12所示,该段代码也演示了标签式对话框与外界的数据交换。这些代码通常是放在显示对话框的命令处理函数中。可以看出,对话框框架的创建过程及对话框与外界的数据交换机制与普通对话框是一样的,不同之处是还需将页对象加入到CPropertySheet对象中。如果要创建的是模态对话框,应调用CPropertySheet:oModal,如果想创建非模态对话框,则应该调用CPropertySheet::Create。 <BR><BR>  若从CPropertySheet类派生了一个新类,则应该将所有的页对象以成员变量的形式嵌入到派生类中,并在派生类的构造函数中调用CPropertySheet::AddPage函数来把各个页添加到对话框中。这样,在创建标签式对话框时就不用做添加页的工作了。 <BR><BR> 清单5.12 典型的标签式对话框创建代码 <BR></P>
<> void CMyView:oModalPropertySheet() <BR><BR> { <BR><BR> CPropertySheet propsheet; <BR><BR> CMyFirstPage pageFirst; // derived from CPropertyPage <BR><BR> CMySecondPage pageSecond; // derived from CPropertyPage <BR><BR> // Move member data from the view (or from the currently <BR><BR> // selected object in the view, for example). <BR><BR> pageFirst.m_nMember1 = m_nMember1;  <BR><BR> pageFirst.m_nMember2 = m_nMember2; <BR><BR> pageSecond.m_strMember3 = m_strMember3; <BR><BR> pageSecond.m_strMember4 = m_strMember4; <BR><BR><BR> propsheet.AddPage(&amp;pageFirst); <BR><BR> propsheet.AddPage(&amp;pageSecond); <BR><BR> if (propsheet.DoModal() == IDOK) <BR><BR> { <BR><BR> m_nMember1 = pageFirst.m_nMember1; <BR><BR> m_nMember2 = pageFirst.m_nMember2; <BR><BR> m_strMember3 = pageSecond.m_strMember3; <BR><BR> m_strMember4 = pageSecond.m_strMember4;  <BR><BR> . . .  <BR><BR> } <BR><BR> } </P>
 楼主| 发表于 2006-1-13 11:16:08 | 显示全部楼层
5.5.2 标签式对话框的运行机制 <BR><BR>  标签式对话框的初始化包括框架对话框的初始化和页的初始化。页的初始化工作可在OnInitDialog函数中进行,而框架对话框的初始化应该在OnCreate函数中完成。 <BR><BR>  根据CPropertySheet:oModal返回的是IDOK还是IDCANCEL,程序可判断出关闭对话框时按的是OK还是Cancel按钮,这与普通对话框是一样的。 <BR><BR>  如果标签式对话框是模态对话框,在其底部会有三个按钮,依次为OK、Cancel和Apply(应用)按钮,如果对话框是非模态的,则没有这些按钮。OK和Cancel按钮的意义与普通对话框没什么两样,Apply按钮则是标签对话框所特有的。普通的模态对话框只有在用户按下了OK按钮返回后,对话框的设置才能生效,而设计Apply按钮的意图是让用户能在不关闭对话框的情况下使对话框中的设置生效。由此可见,Apply的作用与前面例子中登录数据的“添加”按钮类似,用户不必退出对话框,就可以反复进行设置,这在某些应用场合下是很有用的。 <BR><BR>  为了对上述三个按钮作出响应,CPropertyPage类提供了OnOK、OnCancel和OnApply函数,用户可覆盖这三个函数以完成所需的工作。需要指出的是这三个函数并不是直接响应按钮的BN_CLICKED消息的,但在按钮按下后它们会被间接调用。这些函数的说明如下: <BR><BR>  virtual void OnOK( );<BR>在按下OK或Apply按钮后,该函数将被调用。缺省的OnOK函数几乎什么也不干,象数据交换和关闭对话框这样的工作是在别的地方完成的,这与普通对话框的OnOK函数是不同的。 <BR><BR>  virtual void OnCancel( );<BR>在按下Cancel按钮后,该函数将被调用。缺省的OnCancel函数也是几乎什么都不干。 <BR><BR>  virtual BOOL OnApply( );<BR>在按下OK或Apply按钮后,该函数将被调用。缺省的OnApply会调用OnOK函数。函数的返回值如果是TRUE,则对话框中的设置将生效,否则无效。 <BR><BR>  按理说,CPropertySheet类也应该提供上述函数,特别是OnApply。但奇怪的是,MFC并未考虑CPropertySheet类的按钮响应问题。读者不要指望能通过ClassWizard来自动创建按钮的BN_CLICKED消息处理函数,如果需要用到这类函数,那么只好手工创建了。 <BR><BR>  下列几个CPropertyPage类的成员函数也与标签对话框的运行机制相关。 <BR><BR>  void SetModified( BOOL bChanged = TRUE );<BR>该函数用来设置修改标志。若参数bChanged为TRUE,则表明对话框中的设置已改动,否则说明设置未改动。该函数的一个主要用途是允许或禁止Apply按钮。在缺省情况下,Apply按钮是禁止的。只要一调用SetModified(TRUE),Apply按钮就被允许,而调用SetModified(FALSE)并不一定能使Apply按钮禁止,只有在所有被标为改动过的页都调用了SetModified(FALSE)后,Apply按钮才会被禁止。另外,该函数对OnApply的调用也有影响,当Apply按钮被按下后,只有那些被标为改动过的页的OnApply函数才会被调用。在调用该函数之前,程序需要判断页中的内容是否已被修改,可以通过处理诸如BN_CLICKED、EN_CHANG这样的控件通知消息来感知页的内容的改变。 <BR><BR>  virtual BOOL OnSetActive( );<BR>当页被激活或被创建时,都会调用该函数。该函数的缺省行为是若页还未创建,就创建之,若页已经创建,则将其激活,并调用UpdateData(FALSE)更新控件。用户可覆盖该函数完成一些刷新方面的工作。 <BR><BR>  virtual BOOL OnKillActive( );<BR>当原来可见的页被覆盖或被删除时,都会调用该函数。该函数的缺省行为是调用UpdateData(TRUE)更新数据。用户可覆盖该函数完成一些特殊数据的有效性检查工作。 <BR><BR>  需要说明的是,标签对话框中的所有页不一定都会被创建。实际上,那些从未打开过的页及其控件是不会被创建的。因此,在CPropertyPage类的派生类中,只有在确定了页已存在后,才能调用与对话框及控件相关的函数(如UpdateData)。如果收到控件通知消息,或OnSetActive函数被调用,则说明页已经存在。正是由于上述原因,使得标签式对话框的内部数据交换只能在OnSetActive和OnKillActive函数中进行。 <BR><BR> 5.5.3 标签式对话框的具体实例 <BR><BR>  通过上面的分析,读者对标签式对话框已经比较了解了。现在,让我们在前面做过的Register程序中加入一个标签式对话框来试验一下其功能。 <BR><BR>  在Register程序的登录数据对话框中有“个人情况”和“单位情况”两组控件,显然,我们可以创建一个标签式对话框并把两组控件分别放到两个页中。为了简单起见,我们仅要求输入姓名和单位名,简化后的标签式对话框如图5.11所示。 <BR>&gt;<BR>通过对标签式对话框的分析,读者已经知道CPropertySheet类未对Apply按钮的控件通知消息进行处理,这是一个不足之处。Register的新版本将向读者演示如何在CPropertySheet类的派生类中手工加入Apply按钮的BN_CLICKED消息处理函数。另外,新版本还演示了对话框与外部对象交流的一种较好办法,即通过发送用户定义消息来向外部对象传递信息。在登录数据对话框中,与外界交流的方法是在对话框内部直接访问派生的视图对象,这样做的优点是方便快捷,缺点则是对外界依赖较大,不利于移植。而用发送用户定义消息的方法则可以避免这个缺点。 <BR><BR>  具体工作请按下面几步进行: <BR><BR> 在菜单资源中的Edit菜单的“登录数据...”项的后面插入一个名为“标签式对话框...”的菜单项,并指定其ID为ID_EDIT_PROPDLG。然后用ClassWizard,在CRegisterView类内为该菜单命令创建命令处理函数OnEditPropdlg,该函数将用来显示标签式对话框。 <BR><BR> 为标签式对话框的第一页创建对话框模板。去掉缺省的OK和Cancel按钮。注意应选择中文语种和宋体字体。在属性对话框中,指定对话框的ID为IDD_PERSONAL,标题为“个人情况”,在Styles页中,选中TitleBar项,并在Style栏中选择Child,在Border栏中选择ThinBorder。在More Styles页中,选中Disable。然后,在模板中加入控件,如图5.11和表5.6所示。 <BR><BR>   <BR><BR> 表5.6 <BR><BR> 控件类型   控件ID   控件标题  <BR> 静态正文   缺省   姓名:  <BR> 编辑框   IDC_NAME  <BR>    <BR> 用ClassWizard为模板IDD_PERSONAL创建CPropertyPage类的派生类,类名为CPersonalPage。在该类中为控件IDC_NAME加入对应的成员变量,变量名为m_strName,类型为CString。为控件IDC_NAME加入EN_CHANGE消息处理函数OnChangeName,当编辑框的内容被改变时,控件会向对话框发出EN_CHANGE消息。在OnChangeName中,应该使Apply按钮允许。 <BR><BR> 仿照步2,为标签式对话框的第二页创建对话框模板。指定其ID为IDD_UNIT,标题为“单位情况”。在模板中加入的控件如图5.11和表5.7所示。 <BR><BR> 表5.7 <BR><BR> 控件类型   控件ID   控件标题  <BR> 静态正文   缺省   工作单位:  <BR> 编辑框   IDC_UNIT  <BR>    <BR>  用ClassWizard为模板IDD_UNIT创建CPropertyPage类的派生类,类名为CUnitPage。在该类中为控件IDC_UNIT加入对应的成员变量,变量名为m_strUnit,类型为CString。为控件IDC_UNIT加入EN_CHANGE消息处理函数OnChangeUnit。 <BR><BR>  用ClassWizard创建一个CPropertySheet的派生类,类名为CRegisterSheet。 <BR><BR>  在CRegisterApp类的头文件的开头加入下面一行<BR>#define WM_USER_OUTPUT (WM_USER+200)<BR>WM_USER_OUTPUT不是标准的Windows消息,而是一个用户定义消息。在本例中,当标签式对话框的Apply按钮被按下后,程序会向编辑视图发送该消息,编辑视图对应的消息处理函数应该输出对话框的数据。用户定义消息的编码范围是WM_USER—0x7FFF。 <BR><BR>  请读者按清单5.13、5.14、5.15修改程序,限于篇幅,这里仅列出了需要修改的部分源代码。 <BR><BR> 清单5.13 CPersonalPage类和CUnitPage类的部分代码 <BR>
<> void CPersonalPage::OnChangeName()  <BR><BR> { <BR><BR> // TODO: Add your control notification handler code here <BR><BR>   <BR><BR> SetModified(TRUE); //使Apply按钮允许 <BR><BR> UpdateData(TRUE); <BR><BR> } <BR><BR>   <BR><BR> void CUnitPage::OnChangeUnit()  <BR><BR> { <BR><BR> // TODO: Add your control notification handler code here <BR><BR>   <BR><BR> SetModified(TRUE); //使Apply按钮允许 <BR><BR> UpdateData(TRUE); <BR><BR> } </P>
<><BR>  当页中的编辑框的内容被改变时,页会收到EN_CHANGE消息,这将导致OnChangeName或OnChangeUnit被调用。对该消息的处理是使Apply按钮允许并调用UpdateData(TRUE)更新数据。 <BR><BR> 清单5.14 CRegisterSheet类的部分代码 <BR></P>
<> //文件RegisterSheet.h <BR><BR> class CRegisterSheet : public CPropertySheet <BR><BR> { <BR><BR> . . . . . . <BR><BR> // Construction <BR><BR> public: <BR><BR> CRegisterSheet(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0); <BR><BR> CRegisterSheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0); <BR><BR>   <BR> public: <BR><BR> CPersonalPage m_PersonalPage; <BR><BR> CUnitPage m_UnitPage; <BR><BR> . . . . . . <BR><BR> protected: <BR><BR> //{{AFX_MSG(CRegisterSheet) <BR><BR> // NOTE - the ClassWizard will add and remove member functions here. <BR><BR> //}}AFX_MSG <BR><BR> afx_msg void OnApplyNow(); <BR><BR> DECLARE_MESSAGE_MAP()  <BR><BR> }; <BR>  <BR><BR> //文件RegisterSheet.cpp <BR><BR> #include "stdafx.h" <BR><BR> #include "Register.h" <BR><BR> #include "ersonalPage.h" <BR><BR> #include "UnitPage.h" <BR><BR> #include "RegisterSheet.h"    <BR><BR> CRegisterSheet::CRegisterSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage) <BR><BR> :CPropertySheet(pszCaption, pParentWnd, iSelectPage) <BR><BR> { <BR><BR> AddPage(&amp;m_PersonalPage); //向标签对话框中添加页 <BR><BR> AddPage(&amp;m_UnitPage); <BR><BR> } <BR><BR> BEGIN_MESSAGE_MAP(CRegisterSheet, CPropertySheet) <BR><BR> //{{AFX_MSG_MAP(CRegisterSheet) <BR><BR> // NOTE - the ClassWizard will add and remove mapping macros here. <BR><BR> //}}AFX_MSG_MAP <BR><BR> <BR> ON_BN_CLICKED(ID_APPLY_NOW, OnApplyNow) <BR><BR> END_MESSAGE_MAP() <BR>  <BR><BR> void CRegisterSheet::OnApplyNow() <BR><BR> { <BR><BR> CFrameWnd* pFrameWnd = (CFrameWnd*) AfxGetMainWnd(); <BR><BR> //获取指向视图的指针 <BR><BR> CView* pView = pFrameWnd-&gt;GetActiveFrame()-&gt;GetActiveView();  <BR><BR> //发送用户定义消息,在视图中输出信息 <BR><BR> pView-&gt;SendMessage(WM_USER_OUTPUT, (WPARAM)this); <BR><BR> m_PersonalPage.SetModified(FALSE); <BR><BR> m_UnitPage.SetModified(FALSE); //使Apply按钮禁止 <BR><BR> } </P>
<><BR>  在CRegisterSheet类内嵌入了CPersonalPage和CUnitPage对象,在该类的构造函数中调用CPropertySheet::AddPage将两个页添加到对话框中。 <BR></P><BR>
 楼主| 发表于 2006-1-13 11:16:28 | 显示全部楼层
标签式对话框的OK、Cancel和Apply按钮的ID分别是IDOK、IDCANCEL和ID_APPLY_NOW。在按下Apply按钮后,CRegisterSheet对象应该作出响应,由于ClassWizard不能为CRegisterSheet类提供Apply按钮的BN_CLICKED消息处理函数,故必须手工声明和定义消息处理函数OnApplyNow,并在消息映射表中手工加入ID_APPLY_NOW的BN_CLICKED消息映射,该映射是通过ON_BN_CLICKED宏实现的。 <BR><BR>  函数OnApplyNow用CWnd::SendMessage向视图发送用户定义消息WM_USER_OUTPUT,并调用CPropertyPage::SetModified(FALSE)来禁止Apply按钮。在发送消息时,将this指针作为wParam参数一并发送,这是因为视图对象需要指向CRegisterSheet对象的指针来访问该对象。该函数演示了如何在程序的任意地方获得当前活动视图的方法:首先,调用AfxGetMainWnd()返回程序主窗口的CWnd类指针,然后将该指针强制转换成CFrameWnd类型,接着调用CFrameWnd::GetActiveFrame返回当前活动的框架窗口的一个CFrameWnd型指针,最后调用CFrameWnd::GetActiveView返回当前活动视图的一个Cview型指针。 <BR><BR>  在函数OnApplyNow中主要调用了下列函数: <BR><BR>  CWnd* AfxGetMainWnd( );<BR>该函数返回一个指向程序的主窗口CWnd指针。程序的主窗口可以是一个框架窗口,也可以是一个对话框。 <BR><BR>  virtual CFrameWnd* GetActiveFrame( );<BR>函数返回一个CFrameWnd型的指针。如果是MDI(多文档界面)程序,则该函数将返回当前活动的子框架窗口,如果是SDI(单文档界面)程序,该函数将返回主框架窗口本身。 <BR><BR>  CView* GetActiveView( ) const;<BR>返回一个指向当前活动视图的Cview型指针。 <BR><BR>  LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );<BR>用于向本窗口发送消息。SendMessage会直接调用发送消息的处理函数,直到发送消息被处理完后该函数才返回。参数message说明了要发送的消息,wParam和lParam则提供了消息的附加信息。 <BR><BR> 清单5.15 CRegisterView类的部分代码 <BR>
<> //文件RegisterView.h <BR><BR> class CRegisterView : public CEditView <BR><BR> { <BR>. . . . . . <BR><BR> // Generated message map functions <BR><BR> protected: <BR><BR> //{{AFX_MSG(CRegisterView) <BR><BR> afx_msg void OnEditRegister(); <BR><BR> afx_msg void OnEditPropdlg(); <BR><BR> //}}AFX_MSG <BR><BR> afx_msg LRESULT OnOutput(WPARAM wParam, LPARAM lParam); <BR><BR> DECLARE_MESSAGE_MAP() <BR><BR> };   <BR><BR> //文件RegisterView.cpp <BR><BR> #include "stdafx.h" <BR><BR> #include "Register.h" <BR><BR> #include "RegisterDoc.h" <BR><BR> #include "RegisterView.h" <BR><BR> #include "RegisterDialog.h" <BR><BR> #include "ersonalPage.h" <BR><BR> #include "UnitPage.h" <BR><BR> #include "RegisterSheet.h" <BR><BR> BEGIN_MESSAGE_MAP(CRegisterView, CEditView) <BR><BR> . . . . . . <BR><BR> ON_MESSAGE(WM_USER_OUTPUT, OnOutput) <BR><BR> END_MESSAGE_MAP() <BR><BR>   <BR><BR> void CRegisterView::OnEditPropdlg()  <BR><BR> { <BR><BR> // TODO: Add your command handler code here <BR><BR>   <BR><BR> CRegisterSheet RegisterSheet("登录");  <BR><BR> RegisterSheet.m_PersonalPage.m_strName="张颖峰"; <BR><BR> RegisterSheet.m_UnitPage.m_strUnit="南京邮电学院"; <BR><BR>   <BR><BR> if(RegisterSheet.DoModal()==IDOK) <BR><BR> OnOutput((WPARAM)&amp;RegisterSheet,0); <BR><BR> } <BR><BR><BR> //用户定义消息WM_USER_OUTPUT的处理函数 <BR><BR> LRESULT CRegisterView::OnOutput(WPARAM wParam, LPARAM lParam) <BR><BR> { <BR><BR> CRegisterSheet *pSheet=(CRegisterSheet*)wParam; <BR><BR> CString str; <BR><BR> GetWindowText(str); <BR><BR> str+="\r\n"; <BR><BR> str+="姓名:"; <BR><BR> str+=pSheet-&gt;m_PersonalPage.m_strName; <BR><BR> str+="\r\n"; <BR><BR> str+="工作单位:"; <BR><BR> str+=pSheet-&gt;m_UnitPage.m_strUnit; <BR><BR> str+="\r\n"; <BR><BR> SetWindowText(str); <BR><BR> return 0; <BR><BR> } </P>
<><BR>  OnEditPropdlg函数负责初始化和创建标签式对话框,这一过程与创建普通对话框差不多。如果用户是按OK按钮返回的,则调用OnOutput函数输出数据。 <BR><BR>  CRegisterView类的OnOutput函数负责处理标签对话框发来的用户定义消息WM_USER_OUTPUT。用户定义消息的处理函数只能用手工的方法加入。用户定义消息的消息映射是用ON_MESSAGE宏来完成的。 <BR><BR>  函数OnOutput的两个参数wParam和lParam分别对应消息的wParam和lParam值。该函数从wParam参数中获得指向CRegisterSheet对象的指针,然后将该对象中的数据输出到视图中。 <BR><BR>  编译并运行Register,试一试自己设计的标签式对话框。 <BR></P>
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 04:02

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表