一文搞懂如何创建基于对话框的模态对话框和非模态对话框

Windows应用程序工作的基本流程是从用户那里得到数据,经过相应的处理之后,再把处理结果输出到屏幕、打印机或者其他的输出设备上。那么,应用程序是如何从用户那里得到数据,并且再将修改后的数据显示给用户的呢?这就需要用到 Windows应用程序中一个很重要的用户接口 ——对话框


对话框其实就是一个窗口,不仅可以接收消息,而且还可以被移动和关闭,甚至可以在它的客户区中进行绘图。我们也可以将对话框看成是一个大容器,在它上面能够放置各种各样的标准控件和扩展控件,使程序支持用户输入的手段更加丰富。

常用控件介绍

控件 功能 控件类
静态文本框(Static Text) 显示文本, 一般不能接受输入信息 CStatic
图像控件(Picture) 显式位图、 图标、 方框和图元文件, 一般不能接受输入信息 CStatic
编辑框(Edit Box) 输入并编辑正文,支持单行和多行编辑 CEdit
按钮(Button) 响应用户的输入,触发相应的事件 CButton
单选按钮(Radio Button) 用来从两个或多个选项中选中一项 CButton
复选框(Check Box) 用作选择标记,可以有选中、未选中和不确定三种状态 CButton
组框(Group Box) 显示正文和方框,主要用来将相关的一些控件(用千共同的目的)组织 在一起 CButton
列表框(List Box) 显示一个列表,用户可以从该列表中选择一项或多项 CListBox
组合框(Combo Box) 是一个编辑框和一个列表框的组合。分为简易式、下拉式和下拉列表式 CComboBox
滚动条(Scroll Bar) 主要用来从一个预定义范围值中迅速而有效地选取一个整数值 CScrollBar

对话框的种类

对话框分为两类: 模态(Modal)对话框和非模态(Modeless)对话框。

模态对话框

模态对话框是指当其显示时,程序会暂停执行,直到关闭这个模态对话框后,才能继续执行程序中其他任务。例如, 在Word中利用【文件/打开】菜单命令显示一个 “打开 ”对话框后,再用鼠标去选择其他菜单,或者进行该对话框以外的任何操作时,就会听到那个声音(大家都懂),这是因为 “打开 “ 对话框是一个模态对话框。
模态对话框垄断了用户的输入,当一个模态对话框打开时,用户只能与该对话框进行交互,而其他用户界面对象接收不到输入信息。
我们平时所遇到的大部分对话框都是模态对话框。

非模态对话框

非模态对话框显示时,允许转而执行程序中其他任务,而不用关闭这个对话框。 典型的例子是Windows提供的记事本程序中的 “查找”对话框,该对话框不会垄断用户的输入,打开“查找”对话框后,仍可以与其他用户界面对象进行交互,用户可以一边查找,一边修改文章,这样,就大大方便了我们的使用,提高了效率。

对话框的新建和显示

首先我们用VS2017新建一个基于对话框的MFC工程,并命名为Dialog.建好项目之后,进行测试运行,会见到如下图所示结果:
新建工程运行结果
如果想在程序中创建自己的对话框, 可以通过插入一个对话框资源来完成。 具体方法是:视图->其他窗口->资源视图(Ctrl + Shift+E)
在这里插入图片描述
结果如图所示: VS2017自动将其标识设IDD_DIALOG1, 并添加到资源视图选项卡中的 Dialog 项下,同时在资源编辑窗口中打开了这个新对话框资源,
在这里插入图片描述
可以看到,这个新建的 IDD_DIALOG1对话框中有两个按钮:确定取消, 并通过它们的属性对话框可以发现 它们的ID分别为IDOK和IDCANCEL。VS2017已经为这两个按钮提供了默认的消息响应函数OnOK 和 OnCancel, 它们实现的主要功能都是样的,就是关闭对话框,因此,当程序运行时,单击这两个按钮中的仔何一个都可以关闭对话框。但是,单击这两个按钮关闭对话框后,返回的结果值是不一样的, 在程序中,通常根据该返回值来判断用户单击的是哪个按钮,从而确定用户的行为:是确定还是取消当前操作。
我们选中IDD_DIALOG1这个对话框资源本身,打开其属性对话框,将其Caption属性修改为 “测试 ”, 以下统称这个对话框为测试对话框。
在MFC中,对资源的操作通常都是通过一个与资源相关的类来完成的。 对话框资源也有一个相应的基类:CDialogEx。由于CDialogEx类派生于CWnd类,所以它是一个与窗口相关的类,主要用来在屏幕上显示一个对话框。由此可知,实际上,对话框本身也是 一个窗口界面。
既然在MFC中,对资源的操作是通过一个类来完成的,那么就需要创建一个类与这个新建的对话框资源相关联。为此,我们双击刚刚新建的测试对话框将出现如图所示的对话框,利用这个对话框就可以为新建的测试对话框资源创建—个关联的类。
在这里插入图片描述
我们将类名命名为CTestDlg,其.h、.cpp文件名称默认和类名一样。如下图所示:
在这里插入图片描述
点击确定,这时,在 Dialog程序的类视图选项卡中就可以看到这个新类。可以看到,这个CTestDlg 新类有两个重要成员函数,其中一个就是它的构造函数,其定义代码如下:

CTestDlg::CTestDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_DIALOG1, pParent)
{

}   

可以看到,CTestDlg 类的构造函数首先调用其基类:CDialogEx的构造函数,并传递两个参数:一个是CTestDlg 类的IDD成员,一个是父窗口指针。打开 CTestDlg 类的头文件,就可以发现这个 IDD 就是这个对话框资源的ID, 代码如下:

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_DIALOG1 };
#endif

CTestDlg 类的另一个函数是:DoDataExchange, 主要用来完成对话框数据的交换和校验,其定义代码如下所示:

void CTestDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

现在,我们就有了一个类 (CTestDlg) 与IDD_DIALOG1这个对话框资源相关联了,就像程序中 CAboutDlg 类与lDD_ABOUTBOX 这个对话框资源相关联一样。接下来,我们希望在程序中显示这个对话框窗口,为此,可以为Dialog程序主界面下的确定按钮添加一个事件,当用户单击这个按钮时就显示这个测试对话框窗口。接下来如何显示这个测试对话框就要看看我们自己的设置了,是模态对话框还是非模态呢?如何实现呢?我们一一进行展示。

模态对话框的创建

首先实现模态对话框的创建。创建模态对话框需要调用CDialog 类的成员函数:DoModal, (如下图所示:)该函数的功能就是创建并显示一个模态对话框,其返回值将作为CDialog类的另一个成员函数:EndDialog 的参数,后者的功能就是关闭模态对话框。
图1
在正式显示模态对话框之前我们先为主窗口的确定按钮添加一个事件,来实现模态对话框的显示。我们双击确定按钮,在DialogDlg.cpp文件看到如下代码:

void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	CDialogEx::OnOK();
}

我们在这个按钮触发事件下添加代码,实现显示模态对话框,代码如下:

void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	CDialogEx::OnOK();
	CTestDlg dlg;
	dlg.DoModal();
}

上述代码中,首先定义一个对话框对象:dlg, 然后利用这个对象调用DoModal函数以产生一个模态对话框。另外,在视类中并不知道这个CTestDlg 对话框是什么样的数据类型,所以还必须在视类的源文件中包含这个CTestDlg类的头文件,结果如下图所示,其中红色箭头所指的那行代码就是需要添加的内容。
在这里插入图片描述
然后我们编译运行,首先弹出主界面,如下图所示:
在这里插入图片描述
然后点击确定,结果如图所示:
在这里插入图片描述
当前只能运行此模态对话框,且停止主窗口的运行,直到模态对话框退出,才允许主窗口运行。

非模态对话框的创建

如果要创建非模态对话框,则需要利用CDialog类的Create成员函数。该函数具有以下两种形式的声明:

virtual BOOL Create(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL);
virtual BOOL Create(UINT nIDTemplate, CWnd* pParentWnd = NULL);

也就是说,Create函数的第一个参数可以是对话框资源的ID(nIDTemplate参数,或者也可以是对话框模板的名称(lps zTemplateName参数)。这个函数的第二个参数指定了对话框的父窗口,如果其值是NULL,对话框的父窗口就是主应用程序窗口。对本例来说,如果这个父窗口参数值是NULL, 对话框的父窗口就是Dialog窗口。这里,我们仍在主窗口的确定按钮事件下实现创建非模态对话框的功能,则首先需要将上面创建模态对话框的代码注释起来,然后在其后面添加创建非模态对话框的代码,结果如下:

void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	CDialogEx::OnOK();
	/*CTestDlg dlg;
	dlg.DoModal();*/
	CTestDlg dlg;
	dlg.Create(IDD_DIALOG1,this);
}

编译运行程序, 单击确定,发现并未出现测试对话框窗口。这里,我们一定要注意,当利用Create函数创建非模态对话框时,还需要调用ShowWindow函数将这个对话框显示出来。那为什么上面利用DoModal函数创建对话框时不需要呢?这是因为DoModal函数本身就有显示模态对话框的作用,所以对模态对话框来说,不需要再调用 ShowWindow函数来显示对话框了,但非模态对话框需要调用此函数。因此,我们在上述所示代码的最后再加上下而这行代码:

dlg.ShowWindow(SW_SHOW);

编译并运行程序,单击确定,发现仍没有出现测试对话框。噫~~问题出在哪里了呢?我们回头看看上面的代码,发现这里创建的非模态对话框对象 dlg 是一个局部对象,当程序执行时,会依次执行各条代码,当OnBnClickedOk函数执行结束时,dlg这个对象的生命周期也就结束了,它就会销毁与之相关联的对话框资源。那为什么上面创建模态对话框时就可以使用局部对象呢?上面也已经说过了,在创建模态对话框时,当执行到调用 DoModal函数以显示这个对话框时,程序就会暂停执行,直到模态对话框关闭之后,程序才继续向下执行。也就是说,当模态对话框显示时,程序中创建的dlg这个对象的生命周期并未结束。因此,在创建非模态对话框时,不能把对话框对象定义为局部对象。查阅资料发现对于这个问题,有两种解决办法:一是把这个对话框对象定义为视类的成员变量; 另一种方式是将它定义为指针,在堆上分配内存。 因为,在堆上分配的内存,与程序的整个生命周期是一致的,当然这里是指程序中不主动销毁的情况。那我们就试一下吧(不理解的就去查阅一下资料,我也是查了大量资料才知道的)
这里,我们采用后一种方式,修改已有代码,结果如下代码所示:


void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	//CDialogEx::OnOK();
	/*CTestDlg dlg;
	dlg.DoModal();*/

	/*CTestDlg dlg;
	dlg.Create(IDD_DIALOG1,this);
	dlg.ShowWindow(SW_SHOW);*/
	CTestDlg *pDlg = new CTestDlg;
	pDlg->Create(IDD_DIALOG1, this);
	pDlg->ShowWindow(SW_SHOW);
}

编译运行,见证奇迹的时刻,如图所示:
在这里插入图片描述
然而这中方法又引入了新的问题:我们必须释放pDlg占用的资源,否则会造成内存泄漏! 况且这里pDlg还是一个局部指针变量,当它的生命周期结束时,在程序中就无法再引用它所指向的那块内存了。
解决方法同样有两个:一是在主对话框类的定义中添加私有成员变量,然后在主对话框类的析构函数中调用delete函数释放它指向的内存;二是在主对话框类中重载PostNcDestroy虚函数,释放this指针指向的内存。
我们先用第一种方法具体操作如下:

  1. 先在DialogDlg.h文件中加入下面一行代码
    在这里插入图片描述
  2. 添加私有成员变量和析构函数
    在这里插入图片描述
    在这里插入图片描述
  3. 在按钮事件下编写代码:
void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	//CDialogEx::OnOK();
	/*CTestDlg dlg;
	dlg.DoModal();*/

	/*CTestDlg dlg;
	dlg.Create(IDD_DIALOG1,this);
	dlg.ShowWindow(SW_SHOW);*/
	//CTestDlg *pDlg = new CTestDlg;
	if (NULL == pDlg) {
		pDlg = new CTestDlg();
		pDlg->Create(IDD_DIALOG1);
	}
	pDlg->ShowWindow(SW_SHOW);
}

CDialogDlg::~CDialogDlg() {
	if (NULL != pDlg) {
		delete pDlg;
	}
}

  • 运行结果:
    在这里插入图片描述

我们再用第二种方法释放内存,具体操作如下:

  • 先将第一种方法的代码注释掉,如下图所示:
    在这里插入图片描述
    在这里插入图片描述

  • 为主对话框重载PostNcDestroy虚函数
    在这里插入图片描述

  • 编写代码:

void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	//CDialogEx::OnOK();
	/*CTestDlg dlg;
	dlg.DoModal();*/

	/*CTestDlg dlg;
	dlg.Create(IDD_DIALOG1,this);
	dlg.ShowWindow(SW_SHOW);*/
	//CTestDlg *pDlg = new CTestDlg;
	if (NULL == pDlg) {
		pDlg = new CTestDlg();
		pDlg->Create(IDD_DIALOG1);
	}
	pDlg->ShowWindow(SW_SHOW);
}
void CDialogDlg::PostNcDestroy()
{
	// TODO: 在此添加专用代码和/或调用基类

	CDialogEx::PostNcDestroy();
	delete pDlg;
}

  • 运行结果:
    在这里插入图片描述

至此,我们的模态对话框和非模态对话框就创建完成了!!

如果这篇文章对你的学习起到一定的帮助,记得点个赞哦!!

发布了29 篇原创文章 · 获赞 83 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/l59565455/article/details/104803182