当前位置: 首页 > 文档资料 > VC 经典教程 >

5.4 非模态对话框

优质
小牛编辑
141浏览
2023-12-01

5.4.1 非模态对话框的特点

与模态对话框不同,非模态对话框不垄断用户的输入,用户打开非模态对话框后,仍然可以与其它界面进行交互。

非模态对话框的设计与模态对话框基本类似,也包括设计对话框模板和设计CDialog类的派生类两部分。但是,在对话框的创建和删除过程中,非模态对话框与模态对话框相比有下列不同之处:

  • 非模态对话框的模板必须具有Visible风格,否则对话框将不可见,而模态对话框则无需设置该项风格。更保险的办法是调用CWnd::ShowWindow(SW_SHOW)来显示对话框,而不管对话框是否具有Visible风格。

  • 非模态对话框对象是用new操作符在堆中动态创建的,而不是以成员变量的形式嵌入到别的对象中或以局部变量的形式构建在堆栈上。通常应在对话框的拥有者窗口类内声明一个指向对话框类的指针成员变量,通过该指针可访问对话框对象。

  • 通过调用CDialog::Create函数来启动对话框,而不是CDialog::DoModal,这是模态对话框的关键所在。由于Create函数不会启动新的消息循环,对话框与应用程序共用同一个消息循环,这样对话框就不会垄断用户的输入。Create在显示了对话框后就立即返回,而DoModal是在对话框被关闭后才返回的。众所周知,在MFC程序中,窗口对象的生存期应长于对应的窗口,也就是说,不能在未关闭屏幕上窗口的情况下先把对应的窗口对象删除掉。由于在Create返回后,不能确定对话框是否已关闭,这样也就无法确定对话框对象的生存期,因此只好在堆中构建对话框对象,而不能以局部变量的形式来构建之。

  • 必须调用CWnd::DestroyWindow而不是CDialog::EndDialog来关闭非模态对话框。调用CWnd::DestroyWindow是直接删除窗口的一般方法。由于缺省的CDialog::OnOK和CDialog::OnCancel函数均调用EndDialog,故程序员必须编写自己的OnOK和OnCancel函数并且在函数中调用DestroyWindow来关闭对话框。

  • 因为是用new操作符构建非模态对话框对象,因此必须在对话框关闭后,用delete操作符删除对话框对象。在屏幕上一个窗口被删除后,框架会调用CWnd::PostNcDestroy,这是一个虚拟函数,程序可以在该函数中完成删除窗口对象的工作,具体代码如下
    void CModelessDialog::PostNcDestroy
    {
    delete this; //删除对象本身
    }
    这样,在删除屏幕上的对话框后,对话框对象将被自动删除。拥有者对象就不必显式的调用delete来删除对话框对象了。

  • 必须有一个标志表明非模态对话框是否是打开的。这样做的原因是用户有可能在打开一个模态对话框的情况下,又一次选择打开命令。程序根据标志来决定是打开一个新的对话框,还是仅仅把原来打开的对话框激活。通常可以用拥有者窗口中的指向对话框对象的指针作为这种标志,当对话框关闭时,给该指针赋NULL值,以表明对话框对象已不存在了。

提示:在C++编程中,判断一个位于堆中的对象是否存在的常用方法是判断指向该对象的指针是否为空。这种机制要求程序员将指向该对象的指针初始化为NULL值,在创建对象时将返回的地址赋给该指针,而在删除对象时将该指针置成NULL值。

根据上面的分析,我们很容易把Register程序中的登录数据对话框改成非模态对话框。这样做的好处在于如果用户在输入数据时发现编辑视图中有错误的数据,那么不必关闭对话框,就可以在编辑视图中进行修改。

请读者按下面几步操作:

在登录数据对话框模板的属性对话框的More Styles页中选择Visible项。

在RegisterView.h头文件的CRegisterView类的定义中加入
public:
CRegisterDialog* m_pRegisterDlg;

在RegisterView.h头文件的头部加入对CRegisterDialog类的声明
class CRegisterDialog;
加入该行的原因是在CRegisterView类中有一个CRegisterDialog类型的指针,因此必须保证CRegisterDialog类的声明出现在CRegisterView之前,否则编译时将会出错。解决这个问题有两种办法,一种办法是保证在#include “RegisterView.h”语句之前有#include “RegisterDialog.h”语句,这种办法造成了一种依赖关系,增加了编译负担,不是很好;另一种办法是在CRegisterView类的声明之前加上一个对CRegisterDialog的声明来暂时“蒙蔽”编译器,这样在有#include “RegisterView.h”语句的模块中,除非要用到CRegisterDialog类,否则不用加入#include “RegisterDialog.h”语句。

在RegisterDialog.cpp文件的头部的#include语句区的末尾添加下面两行
#include "RegisterDoc.h"
#include "RegisterView.h"

利用ClassWizard为CRegisterDialog类加入OnCancel和PostNcDestroy成员函数。加入的方法是进入ClassWizard后选择Message Maps页,并在Class name栏中选择CRegisterDialog。然后,在Object IDs栏中选择IDCANCEL后,在Messages栏中双击BN_CLICKED,这就创建了OnCancel。要创建PostNcDestroy,先在Object IDs栏中选择CRegisterDialog,再在Messages栏中双击PostNcDestroy即可。

分别按清单5.10和5.11,对CRegisterView类和CRegisterDialog类进行修改。

 

清单5.10 CRegisterView类的部分代码

CRegisterView::CRegisterView()

{

// TODO: add construction code here

 

m_pRegisterDlg=NULL; //指针初始化为NULL

}

 

void CRegisterView::OnEditRegister()

{

// TODO: Add your command handler code here

 

 

if(m_pRegisterDlg)

m_pRegisterDlg->SetActiveWindow(); //激活对话框

else

{

//创建非模态对话框

m_pRegisterDlg=new CRegisterDialog(this);

m_pRegisterDlg->Create(IDD_REGISTER,this);

}

}

 

 

清单5.11 CRegisterDialog的部分代码

void CRegisterDialog::PostNcDestroy()

{

// TODO: Add your specialized code here and/or call the base class

 

delete this; //删除对话框对象

}

 

void CRegisterDialog::OnCancel()

{

// TODO: Add extra cleanup here

 

((CRegisterView*)m_pParent)->m_pRegisterDlg=NULL;

DestroyWindow(); //删除对话框

}

CRegisterView::OnEditRegister函数判断登录数据对话框是否已打开,若是,就激活对话框,否则,就创建该对话框。该函数中主要调用了下列函数:

调用CWnd::SetActiveWindow激活对话框,该函数的声明为
CWnd* SetActiveWindow( );
该函数使本窗口成为活动窗口,并返回原来活动的窗口。

调用CDialog::Create来显示对话框,该函数的声明为
BOOL Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );
参数nIDTemplate是对话框模板的ID。pParentWnd指定了对话框的父窗口或拥有者。

 

当用户在登录数据对话框中点击“取消”按钮后,CRegisterDialog::OnCancel将被调用,在该函数中调用CWnd::DestroyWindow来关闭对话框,并且将CRegisterView的成员m_pRegisterDlg置为NULL以表明对话框被关闭了。调用DestroyWindow导致了对CRegisterDialog::PostNcDestroy的调用,在该函数中用delete操作符删除了CRegisterDialog对象本身。

编译并运行Register,现在登录数据对话框已经变成一个非模态对话框了。

5.4.2 窗口对象的自动清除

一个MFC窗口对象包括两方面的内容:一是窗口对象封装的窗口,即存放在m_hWnd成员中的HWND(窗口句柄),二是窗口对象本身是一个C++对象。要删除一个MFC窗口对象,应该先删除窗口对象封装的窗口,然后删除窗口对象本身。

删除窗口最直接方法是调用CWnd::DestroyWindow或::DestroyWindow,前者封装了后者的功能。前者不仅会调用后者,而且会使成员m_hWnd保存的HWND无效(NULL)。如果DestroyWindow删除的是一个父窗口或拥有者窗口,则该函数会先自动删除所有的子窗口或被拥有者,然后再删除父窗口或拥有者。在一般情况下,在程序中不必直接调用DestroyWindow来删除窗口,因为MFC会自动调用DestroyWindow来删除窗口。例如,当用户退出应用程序时,会产生WM_CLOSE消息,该消息会导致MFC自动调用CWnd::DestroyWindow来删除主框架窗口,当用户在对话框内按了OK或Cancel按钮时,MFC会自动调用CWnd::DestroyWindow来删除对话框及其控件。

窗口对象本身的删除则根据对象创建方式的不同,分为两种情况。在MFC编程中,会使用大量的窗口对象,有些窗口对象以变量的形式嵌入在别的对象内或以局部变量的形式创建在堆栈上,有些则用new操作符创建在堆中。对于一个以变量形式创建的窗口对象,程序员不必关心它的删除问题,因为该对象的生命期总是有限的,若该对象是某个对象的成员变量,它会随着父对象的消失而消失,若该对象是一个局部变量,那么它会在函数返回时被清除。

对于一个在堆中动态创建的窗口对象,其生命期却是任意长的。初学者在学习C++编程时,对new操作符的使用往往不太踏实,因为用new在堆中创建对象,就不能忘记用delete删除对象。读者在学习MFC的例程时,可能会产生这样的疑问,为什么有些程序用new创建了一个窗口对象,却未显式的用delete来删除它呢?问题的答案就是有些MFC窗口对象具有自动清除的功能。

如前面讲述非模态对话框时所提到的,当调用CWnd::DestroyWindow或::DestroyWindow删除一个窗口时,被删除窗口的PostNcDestroy成员函数会被调用。缺省的PostNcDestroy什么也不干,但有些MFC窗口类会覆盖该函数并在新版本的PostNcDestroy中调用delete this来删除对象,从而具有了自动清除的功能。此类窗口对象通常是用new操作符创建在堆中的,但程序员不必操心用delete操作符去删除它们,因为一旦调用DestroyWindow删除窗口,对应的窗口对象也会紧接着被删除。

不具有自动清除功能的窗口类如下所示。这些窗口对象通常是以变量的形式创建的,无需自动清除功能。

所有标准的Windows控件类。

从CWnd类直接派生出来的子窗口对象(如用户定制的控件)。

切分窗口类CSplitterWnd。

缺省的控制条类(包括工具条、状态条和对话条)。

模态对话框类。

 

具有自动清除功能的窗口类如下所示,这些窗口对象通常是在堆中创建的。

主框架窗口类(直接或间接从CFrameWnd类派生)。

视图类(直接或间接从CView类派生)。

 

读者在设计自己的派生窗口类时,可根据窗口对象的创建方法来决定是否将窗口类设计成可以自动清除的。例如,对于一个非模态对话框来说,其对象是创建在堆中的,因此应该具有自动清除功能。

综上所述,对于MFC窗口类及其派生类来说,在程序中一般不必显式删除窗口对象。也就是说,既不必调用DestroyWindow来删除窗口对象封装的窗口,也不必显式地用delete操作符来删除窗口对象本身。只要保证非自动清除的窗口对象是以变量的形式创建的,自动清除的窗口对象是在堆中创建的,MFC的运行机制就可以保证窗口对象的彻底删除。

如果需要手工删除窗口对象,则应该先调用相应的函数(如CWnd::DestroyWindow)删除窗口,然后再删除窗口对象.对于以变量形式创建的窗口对象,窗口对象的删除是框架自动完成的.对于在堆中动态创建了的非自动清除的窗口对象,必须在窗口被删除后,显式地调用delete来删除对象(一般在拥有者或父窗口的析构函数中进行).对于具有自动清除功能的窗口对象,只需调用CWnd::DestroyWindow即可删除窗口和窗口对象。注意,对于在堆中创建的窗口对象,不要在窗口还未关闭的情况下就用delete操作符来删除窗口对象.

提示:在非模态对话框的OnCancel函数中可以不调用CWnd::DestroyWindow,取而代之的是调用CWnd::ShowWindow(SW_HIDE)来隐藏对话框.在下次打开对话框时就不必调用Create了,只需调用CWnd::ShowWindow(SW_SHOW)来显示对话框.这样做的好处在于对话框中的数据可以保存下来,供以后使用.由于拥有者窗口在被关闭时会调用DestroyWindow删除每一个所属窗口,故只要非模态对话框是自动清除的,程序员就不必担心对话框对象的删除问题.