第5章
对话框



对话框(dialog box)是Windows应用程序中一种常用的资源。它其实是一个“窗口”,是Windows程序与用户交互的一个手段,它的主要功能是输出信息和接收用户的输入。它可以只是一个简单的OK消息框,也可以是一个复杂的数据输入表单。在对话框内一般都有一些控件(control),对话框依靠这些控件与用户进行交互。
本章主要讲解对话框的原理和编程方法,下一章将详细介绍各种控件的使用。
5.1对话框概述

Windows应用程序虽然提供了菜单和工具栏等界面元素,但就用户交互输入功能而言,菜单和工具栏的功能是有限的。
对话框除了用来显示提示信息(例如程序启动时显示版权和运行进度),主要用于接收用户的输入数据。在MFC中,对话框的功能被封装在CDialog类中,而CDialog类是CWnd类的派生类。作为窗口,对话框具有窗口的一切功能。对话框的一个典型应用是通过菜单命令或工具栏按钮打开一个对话框,当然也可以编写基于对话框的应用程序,将对话框作为一个程序的主界面。
5.1.1对话框的类型
尽管不同对话框的外观、大小和对话框的控件千差万别,但从对话框的工作方式看,对话框可分为模态对话框和非模态对话框两种类型。
当一个模态对话框打开后,在其关闭之前,用户不能转向其他用户界面对象,而只能与该对话框进行交互。
大家平时接触到的对话框大多数是模态对话框。例如,选择“文件”|“另存为”菜单命令后,“另存为”对话框被打开,用户不能再做其他工作,只有保存完文件或取消保存文件、关闭对话框窗口后才能做其他工作。
非模态对话框恰恰相反,当打开一个非模态对话框,对话框停留在屏幕上时,仍然允许用户与其他窗口进行交互。最典型的非模态对话框是在Word中使用的“查找和替换”对话框,打开该对话框后,可以交替地进行文档编辑和查找替换操作。
5.1.2对话框的CDialog类
为了方便实现对话框功能,MFC提供了一系列对话框类,并实现了对话框消息响应和处理机制。CDialog类是对话框类中最重要的类,用户在程序中创建的对话框类一般都是CDialog类的派生类。CDialog类还是其他所有对话框类的基类,其派生关系如图5.1所示。对话框类为程序员提供了管理对话框的编程接口。



图5.1CDialog类
的派生关系

CDialog类从CWnd类派生而来,所以它继承了CWnd类的成员函数,具有CWnd类的基本功能,可以编写代码移动、显示或隐藏对话框,并能根据对话框的特点增加新的成员函数,扩展它的功能。表5.1列出了CDialog类中经常要使用的成员函数,在用户的CDialog类的派生类中可以直接调用。大部分的成员函数是虚函数,可以在用户的派生类中重新定义,以实现特定的目的。除了CDialog类成员函数,CWnd和CWinApp类也提供了一些成员函数用于对话框的管理,常用的函数见表5.1。


表5.1有关对话框的常用处理函数



成 员 函 数功能

CDialog:: CDialog() 构造一个CDialog对象
CDialog::DoModal()激活模态对话框,显示对话框窗口,直到对话框窗口关闭时返回
CDialog::Create()根据对话框资源模板创建非模态对话框窗口并立即返回。如果对话框不是Visible属性,还需通过调用CWnd::ShowWindow()函数显示非模态对话框窗口
CDialog::OnOK()单击OK按钮时调用该函数,接收对话框输入数据,关闭对话框。默认实现时调用EndDialog()函数关闭对话框和使DoModal()返回值IDOK
CDialog::OnCancel()单击Cancel按钮或按Esc键时调用该函数,不接收对话框输入数据,关闭对话框。默认实现时调用EndDialog()函数关闭对话框和使DoModal()返回值IDCANCEL
CDialog::OnInitDialog()WM_INITDIALOG的消息处理函数。在调用DoModal()或Create()函数时系统发送WM_INITDIALOG消息,在显示对话框前调用该函数进行初始化工作
CDialog::EndDialog()关闭对话框窗口
CWnd::ShowWindow()显示或隐藏对话框窗口
CWnd::DestroyWindow()关闭并销毁非模态对话框
CWnd::UpdateData()通过调用DoDataExchange()设置或获取对话框控件的数据。单击OK按钮关闭模态对话框或默认OnInitDialog()时该函数被自动调用来设置控件的初始值
CWnd::DoDataExchange()被UpdateData()调用以实现对话框的数据交换与校验,不能直接调用
CWnd::PostNcDestroy()这个虚函数在窗口销毁时被自动调用
CWnd::SetActiveWindow()激活窗口
CWnd::UpdateWindow()更新客户区



5.1.3对话框的组成
可以通过应用程序向导生成基于对话框的应用程序,在文档/视图结构应用程序中也大量使用对话框。在Windows中对话框是作为一种资源被使用,从MFC编程的角度看,对话框主要由以下两部分组成。
(1) 对话框模板资源: 对话框模板资源定义了对话框的特性(例如大小、位置和风格)以及对话框中每个控件的类型和位置。
(2) 对话框类: 对话框类用来实现对话框的功能。由于对话框行使的功能各不相同,所以一般需要从CDialog类派生一个新类,以提供编程接口来管理相应的对话框。
因此在程序中要设计一个对话框,首先要设计一个对话框模板资源,然后设计一个基于该对话框模板资源的对话框类,最后声明对话框类的对象以便激活并打开对话框。
5.2模态对话框
模态对话框是最常用的对话框。本节以模态对话框编程为例来介绍如何在程序中设计对话框和运行对话框,同时分析对话框的数据交换和校验机制。
5.2.1设计对话框模板资源
可以使用对话框编辑器来创建包含不同控件的对话框模板资源。直接双击项目中的对话框资源打开对话框编辑器; 或者使用Visual Studio IDE中的“添加资源”工具,选中Dialog后单击“新建”按钮打开对话框编辑器; 或者在项目的“资源视图”窗口中找到Dialog资源,右击后选择“插入Dialog”菜单命令来打开对话框编辑器。图5.2即为打开的对话框编辑器。



图5.2对话框编辑器


对话框编辑器会对项目的资源(RC)文件进行更新,使之包含新的对话框资源,并且该项目的resource.h文件也会被更新。在对话框编辑器中可以调整对话框显示时的大小和位置,从控件工具栏拖放各种类型的控件到对话框中,用控件布局工具栏调整控件的位置,测试对话框的外观和行为。另外,也可以直接用文本方式来编辑项目的资源文件,这需要用户掌握资源脚本的编写方法。

如果没有控件,对话框完不成具体功能,对话框与控件有着密不可分的关系。所以设计对话框模板资源有两个重要的内容,第一是从“工具箱”选择控件添加到对话框中,并调整其位置和大小; 第二是在属性窗口中设置控件的“描述文字”、ID以及其他属性。
1. 增加或删除控件
控件是独立的小部件,能够放置在一个对话框中,提供应用程序与用户交互的某种功能。控件的种类较多,图5.3中
所示为Visual Studio 2019支持的部分标准控件,用户可以很方便地从“工具箱”中添加新的控件到对话框中。


在一个对话框资源中添加控件的操作十分方便,只需从图5.3所示的“工具箱”中选中要增加的控件,再将此控件拖动至对话框模板中的确定位置,松开鼠标按键即添加了一个控件。调整控件的位置和大小的操作与Word中对文本框的操作完全一样
,这里不再赘述。
如果要删除已添加的控件,先单击对话框中的控件,再按Delete键即可。

在默认情况下,“工具箱”窗口总是打开的。如果没有打开,可以通过IDE的“视图”|“工具箱”菜单命令将其打开。
用户可以通过设置“工具箱”窗口的停靠属性或者通过直接拖曳的方式来调整工具箱的位置。
2. 设置控件的属性

一个控件的相关属性决定了一个控件的可操作行为和显示效果,属性的设置是在与每个控件相对应的属性窗口中进行的。将光标指向对话框中需要设置属性的控件,右击该控件,在弹出的快捷菜单中选择“属性”菜单命令,打开控件
的属性窗口。每一种控件的属性窗口中的内容都有所不同,与其特性相关,第6章将详细介绍一些常用控件的属性
的含义。一个控件的典型的属性窗口如图5.4所示,其中的ID和“描述文字”的含义及设置方法与菜单项相同。


图5.3工具箱中的部分标准控件




图5.4控件的属性窗口



同样,对话框的属性是在对话框的属性窗口中设置的,在对话框的任意空白处右击,在弹出的快捷菜单中选择“属性”菜单命令,弹出如图5.5所示的对话框属性窗口。在“外观”|“描述文字”中填写对话框的标题,单击“字体”|“字体(大小)”会弹出
“字体”对话框,设置对话框中显示的字体格式。其他的属性设置请大家自行测试。
3. 测试对话框的运行效果
当设计好一个对话框模板资源后,还不能立即在应用程序中运行对话框,MFC提供了“测试对话框”命令,使程序员在设计阶段就能够测试对话框的运行效果。
测试对话框的方法有下面3种。
(1)  选择“格式”|“测试对话框”菜单命令。
(2) 单击布局工具栏上的“测试对话框”按钮。
(3) 按Ctrl+T键。
通过测试对话框就可以评估对话框是否符合要求。如果发现了错误或不满意的地方,
可以按Esc键退出测试对话框并重新修改对话框模板资源。



视频讲解


【例5.1】创建一个单文档的MFC应用程序MyDialog,向应用程序中添加如图5.6所示的对话框模板资源,并设置控件的“描述文字”和ID属性。


图5.5对话框的属性窗口




图5.6模态对话框


其操作步骤如下: 
(1) 使用“MFC应用”项目模板创建一个项目名为MyDialog的单文档应用程序。

(2)  插入新的对话框模板。右击“解决方案资源管理器”窗口中的MyDialog项目,选择快捷菜单中的“添加”|“资源”菜单命令,打开“添加资源”窗口,选中其中的Dialog后单击“新建”按钮,插入一个新的对话框模板。Visual Studio IDE提供的对话框模板创建了一个基本界面,包括一个“确定”按钮和一个“取消”按钮等。可以看到“资源视图”窗口的Dialog文件夹下增加了一个对话框资源IDD_DIALOG1,如图5.2所示。
(3)  在新的对话框模板上添加控件。从工具箱中选取
一个
静态控件,该控件作为输入框的提示文本。再从工具箱中选取一个

编辑框控件,该控件用来接受输入的数据。
(4) 按图5.6所示的布局调整控件的大小和位置。
(5)  设置控件的属性。选择静态控件的“描述文字”属性,将其值设置为“请输入边长”; 选择编辑框控件,将其ID属性设置为IDC_LENGTH。
(6)  设置对话框的属性。打开对话框的属性窗口,将其“描述文字”属性设置为“输入边长”。
(7) 测试对话框。单击布局工具栏上的“测试对话框”按钮,测试并重新修改对话框模板资源,直到满意为止。
5.2.2设计对话框类
一旦完成了对话框的属性和外观设计,接下来就是设计其行为。设计对话框类主要包括以下几个方面: 
第一,从MFC的CDialog中派生出一个类,用来负责对话框行为。
第二,利用ClassWizard把这个类和先前产生的对话框资源连接起来。通常这意味着必须声明某些函数,用于处理相应的对话框消息,并将对话框中的控件对应到类的成员变量上,这也就是所谓的对话框数据交换(Dialog Data eXchange,DDX)。如果用户对这些变量内容有任何“确认规则”,ClassWizard也允许用户设定它,这就是所谓的数据验证(Dialog Data Validation,DDV)。
第三,对话框的初始化。
1. 创建对话框类
使用“类向导”工具可以十分方便地创建MFC窗口类的派生类,对话框类也不例外。



视频讲解


【例5.2】完善例5.1中的应用程序MyDialog,给对话框资源添加相应的对话框类。
其操作步骤如下: 
(1) 保存已创建的对话框资源。
(2)  确保新的对话框资源在对话框编辑器中处于打开状态,右击对话框的空白区域,在弹出的快捷菜单中选择“添加类”菜单命令,为新对话框添加一个与之相关联的类,如图5.7所示。


图5.7添加对话框关联类


(3)  在“添加MFC类”窗口中输入新类的相关信息。其中,类名为CSquare,基类为CDialog,头文件为Square.h,源文件为Square.cpp,对话框ID为IDD_DIALOG1,如图5.8所示。


图5.8“添加MFC类”窗口


新类添加完成后,在IDE的“类视图”窗口中可以看到增加了一个新的类CSquare,切换到“解决方案资源管理器”窗口,在项目的“头文件”和“源文件”文件夹中可以看到该类的头文件和实现文件,如图5.9所示。


图5.9新增对话框类及文件


2. 创建对话框成员变量
在创建一个对话框类后,可以增加类的成员变量来操作对话框上的控件。可以为对话框资源上的每一个控件添加一个或多个对应的成员变量。类向导的“成员变量”选项卡主要用来为对话框类添加和删除与对话框控件
相关联的成员变量,在编写对话框程序时经常和该选项卡“打交道”。打开“类向导”工具,选择项目中的对话框类,
切换到“成员变量”选项卡,即可看到对话框类中控件对象的成员变量信息,如图5.10所示。



图5.10控件的成员变量


在类向导的“成员变量”选项卡中有一个标题为“成员变量”的列表框,第1列“控件ID”表示控件的ID,第2列“类型”表示变量的类型,第3列“成员”表示成员变量名。双击一个ID或选定ID后,单击“添加变量”按钮,将弹出“添加控制变量”对话框,如图5.11所示。“名



图5.11“添加控制变量”对话框


称”框用于输入成员变量名,类向导建议以“m_”作为成员变量名的前缀。不同控件所关联
的成员变量的类型不一定相同,“类别”下拉列表框用于选择成员变量的类别。出于不同的操作目的,MFC提供了两种类型的成员变量,如表5.2所示。用户可以为一个控件同时定义一个Value类型的变量和一个Control类型的变量。


表5.2对话框类的成员变量类型



类型描述

Value值类型的成员变量,用于控件的值的控制。它可以有多种数据类型,由所连接的控件类型决定。例如,EditBox控件可以有CString型或int型; RadioButton可以是int型
Control控件类型的成员变量,实际上是该控件类的一个对象。在创建了一个控件对象之后,就可以通过该对象使用所属控件类的成员函数对控件进行操作,例如在程序运行时为ComboBox加入选择项,设置控件是否有效或可见等

添加操作结束后,回到“类向导”工具的“成员变量”选项卡,在列表框中显示出了目前已创建的成员变量及它们的类型。如果需要规定某个成员变量的有效性校验规则,只需要在添加变量时在如图5.11所示的“其他”窗口中设置即可。如果需要修改已添加的成员变量,必须先删除该变量,然后再进行添加操作。选中需要删除的成员变量,单击“删除变量”按钮即可删除该变量。



视频讲解


【例5.3】继续完善例5.2中的应用程序MyDialog,在对话框类中添加与控件相关联的成员变量。
其操作步骤如下: 
(1) 打开“类向导”工具,选择CSquare类下的“成员变量”选项卡。
(2) 添加与编辑框控件相关联的成员变量。双击“成员变量”列表中的编辑框IDC_LENGTH,通过“添加控制变量”对话框添加成员变量。在“名称”框中填写变量名m_length,在“类别”下拉列表框中选择Value,在“变量类型”中输入数据类型int。
(3)  给变量m_length添加校验规则。在“添加控制变量”窗口中选择“其他”或单击“下一步”按钮,打开“添加控制变量”的其他设置窗口,在这里输入m_length的最小值10和最大值200。
(4) 单击“完成”按钮回到“类向导”工具窗口,依次单击“应用”和“确定”按钮关闭“类向导”工具。
3. 对话框的初始化

除了以上工作,对话框上的许多控件还需要进行初始化,从而使对话框被显示时这些控件具有相应类型的初值。对话框的初始化工作可以使用以下3种方法来进行。

1) 在构造函数中初始化
从C++的观点看,在类的构造函数中
应该初始化类的数据成员,但是在MFC应用程序中应尽量避免在构造函数中完成太多的工作,因为构造函数没有返回失败条件的方法,无法报告其中的失败信息。在构造函数中初始化主要是针对对话框的数据成员。

打开应用程序MyDialog中CSquare类的构造函数,可以看到“类向导”工具自动加入了成员变量的初始代码,代码如下: 

//CSquare类的构造函数

CSquare::CSquare(CWnd* pParent /*=nullptr*/)

: CDialog(IDD_DIALOG1, pParent)

, m_length(0)

{

}

2)  WM_CREATE初始化

由于对话框也是窗口,它在窗口创建时也会收到WM_CREATE消息,这样就能在WM_CREATE的消息处理函数OnCreate()中进行一些数据成员的初始化工作。但由于此时很多控件尚未建立,有些控件的初始化工作还无法完成。

若要响应WM_CREATE进行初始化工作,需要在创建对话框类时使用“类向导”工具对消息WM_CREATE进行映射,从而形成OnCreate()函数后再编写代码。
3)  WM_INITDIALOG初始化

在收到WM_INITDIALOG消息时,对话框处于这样一种状态: 首先,对话框框架已经建立起来; 其次,各个控件也建立起来并放在适当的地方; 最后,对话框还没有显示出来。这样就可以设置或优化对话框中各个控件的外观、大小尺寸、位置及其他内容。这是构造函数和WM_CREATE无法相比的,所以对于对话框的初始化工作通常都在响应该消息时进行。可以通过“类向导”工具映射该消息以得到其处理函数OnInitDialog(),初始化工作可在该函数中进行。
5.2.3运行对话框
在完成一个对话框资源和对话框类的设计之后,就可以在应用程序中运行该对话框。模态对话框的运行分两个步骤: 首先创建一个对话框对象,然后调用CDialog::DoModal()函数打开对话框。

DoModal()函数负责模态对话框的创建和撤销,可以根据其返回值是IDOK还是IDCANCEL来判断用户关闭对话框时按的
是哪一个键。



视频讲解


【例5.4】完善例5.3中的应用程序MyDialog,通过“对话框”|“模态对话框”菜单项打开上述标题为“输入边长”的对话框,并根据输入的边长画一个正方形。
其操作步骤如下: 
(1) 在CMyDialogView视图类中添加一个类型为int的成员变量m_vlength,用来在视图中接收并存储对话框类的成员变量m_length的值,并在构造函数CMyDialogView()中将它初始化为0。

CMyDialogView::CMyDialogView()

{

m_vlength=0;//设置编辑框显示的初始值

}


(2) 使用菜单编辑器增加一个“对话框”主菜单,并在其中添加“模态对话框”菜单项,其ID为ID_MODEDLG。
(3) 利用“类向导”工具在视图类中为ID_MODEDLG菜单项添加COMMAND消息的处理函数,在函数中添加如下代码。

void CMyDialogView::OnModedlg() 

{

CSquare dlg;//定义一个对话框对象

if(dlg.DoModal()==IDOK) //显示对话框

{

m_vlength=dlg.m_length;//接收并存储编辑框数据

Invalidate();//刷新视图

}

}

注意: 为了简单,在上述代码中直接使用dlg对象访问其成员变量m_length,所以要求m_length的访问权限必须是public。
(4) 在成员函数CMyDialogView::OnDraw()中添加绘制正方形的语句。

pDC->Rectangle(10,10,10+m_vlength,10+m_vlength); 

(5) 在视图类实现文件MyDialogView.cpp的所有include语句之后加入包含对话框类头文件的语句。

#include "Square.h"

(6) 编译、链接并运行程序,选择“对话框”|“模态对话框”菜单命令,打开“输入边长”对话框。输入边长后单击“确定”按钮,程序将在客户区画出一个正方形。
5.2.4对话框的数据交换和校验机制
在运行对话框时,对话框类的成员变量需要与控件交换数据,以完成输入和输出功能。CDialog类通过调用其成员函数DoDataExchange()实现对话框的数据交换和验证,而在DoDataExchange()中使用了MFC提供的CDataExchange类,该类实现对话框类的成员变量与控件之间的数据交换(DDX)和数据验证(DDV)。DDX将成员变量与对话框控件相连接,完成数据在成员变量和控件之间的交换。DDV用于数据的校验,它能自动校验输入的数据(如字符串的长度或数值的范围)是否符合设计要求。DDX和DDV适用于文本框、复选框、单选按钮、列表框和组合按钮。

在对话框中使用DDX/DDV非常简单,一般不需要编写CDataExchange类的代码,DoDataExchange()函数也由框架调用。
用户只需通过“类向导”工具为对话框类添加与对话框控件相关联的成员变量即可。在应用程序MyDialog中可以找到下列函数: 

void CSquare::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

DDX_Text(pDX, IDC_LENGTH, m_length);

DDV_MinMaxInt(pDX, m_length, 10, 200);

}

可见“类向导”工具替用户完成了所有的工作。DoDataExchange()函数只有一个参数CDataExchange* pDX。在该函数中调用
DDX函数来完成数据交换,调用DDV函数来完成数据的有效性检查。当利用“类向导”工具为对话框类添加与对话框控件
相关联的成员变量后,它会自动在该函数中添加代码。下列语句

DDX_Text(pDX, IDC_LENGTH, m_length);

DDV_MinMaxInt(pDX, m_length, 10, 200);



图5.12DDV提示信息对话框

中第一句是DDX函数调用语句,表明m_length是一个Value值类别的成员变量,用于交换IDC_LENGTH控件中
的内容; 第二句是DDV函数调用语句,程序运行后,如果用户的输入数据超出10~200的范围,DDV将显示如图5.12所示的提示信息对话框,提示用户有效的输入范围。


需要说明的是,虽然DoDataExchange()函数实现了DDX/DDV功能,但用户不能直接调用DoDataExchange()函数,它一般由成员函数CWnd::UpdateData()调用。用户可以随时在需要进行数据交换的地方调用UpdateData()函数。

UpdateData()函数只有一个BOOL类型的参数。当参数为TRUE时,MFC通过调用DoDataExchange()函数将数据从控件传递到相关联的成员变量; 当参数为FALSE时,数据从成员变量传递到相关联的控件。

应用程序MyDialog表面上看没有调用UpdateData()函数。但是在创建对话框时,DoModal()的任务包括装载对话框资源、调用OnInitDialog()初始化对话框并将对话框显示在屏幕上。在默认的CDialog::OnInitDialog()函数中调用了UpdateData(FALSE),这样
数据成员的初值就反映到了相应的控件上。而单击系统默认生成的“确定”按钮,将调用CDialog::OnOK()函数,CDialog::OnOK()函数中调用了UpdateData(TRUE),将控件中的数据传给成员变量。图5.13描绘了对话框的这种数据交换机制。



图5.13对话框的数据交换


由此看来,无论MFC将DDX技术如何复杂化,大家只需知道DDX如同一条双向通道,而方向控制开关就是UpdateData()函数中的参数值是TRUE还是FALSE。

5.3非模态对话框
区别于模态对话框,非模态对话框弹出后,用户不需要关闭它就可以在非模态对话框和应用程序的其他窗口之间进行切换。
5.3.1非模态对话框的特点

对于非模态对话框,使用对话框编辑器创建对话框资源以及使用“类向导”工具添加对话框类、成员变量和消息处理函数的方法与模态对话框完全一样,但创建和退出对话框的方式存在着差异。
1.  “可见”属性

在对话框的属性窗口的“行为”分组中有“可见”属性的设置,默认情况下,对话框模板不选择“可见”属性。模态对话框不需要设置该属性,而非模态对话框必须将该属性值设置为TRUE,否则对话框是不显示的。保险的办法是调用CWnd::ShowWindow(SW_ SHOW)来显示对话框,而不管对话框是否具有可见风格。
2.  对话框窗口的创建方式

非模态对话框的创建与普通窗口的创建是一样的,通过调用CWnd::Create()函数来创建对话框,这是非模态对话框的关键所在。Create()函数的原型有两种,最常用的为

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

其中,nIDTemplate是对话框资源模板的ID标识,pParentWnd为对话框父窗口的指针。

由于Create()函数不会启动新的消息循环,即对话框与应用程序共用一个消息循环,这样对话框就不会屏蔽用户对其他界面对象的访问。Create()函数与DoModal()函数的不同之处是Create()函数创建了对话框后立即返回,而DoModal()函数要在对话框关闭后才会返回。
3.  对话框对象的创建方式
众所周知,在MFC程序中窗口对象的生存期应长于对应的窗口,也就是说不能在未关闭屏幕上窗口的情况下先把对应的窗口对象删除。由于在Create()返回后不能确定对话框是否已关闭,这样也就无法确定对话框对象的生存期,所以不能以局部变量的形式创建非模态对话框的对象,只能用new操作符动态创建,并且在调用对话框类的窗口类内声明一个指向对话框类的指针变量,通过该指针访问对话框对象。
4.  窗口删除函数

非模态对话框必须调用CWnd::DestoryWindow()来关闭对话框。模态对话框是调用CDialog::EndDialog()关闭对话框。由于默认的对话框函数OnOK()和OnCancel()都是调用EndDialog()关闭对话框的,该函数使对话框不可见但不删除对话框对象,所以非模态对话框类要定义自己的OnOK()和OnCancel()函数,调用DestoryWindow()来关闭对话框。
5.  清理对话框对象的方式

与创建对象的方式——new操作相对应,使用delete操作删除一个非模态对话框对象。由于当屏幕上的一个窗口被关闭后,框架会自动调用CWnd::PostNcDestroy()函数,也可以编写程序代码,在这个函数中清理非模态对话框对象。
6.  必须有一个标志表明非模态对话框是否为打开的

因为在非模态对话框打开的情况下,用户有可能再次选择打开该对话框,这时不能再创建一个新的非模态对话框。程序根据标志来判断是打开一个新的对话框还是激活一个已打开的对话框。通常可以用拥有者窗口中的指向对话框对象的指针作为这种标志,当对话框关闭时给该指针赋NULL值,以表明对话框对象已经不存在了。



视频讲解


【例5.5】创建一个单文档的MFC应用程序Li5_5,以非模态对话框的形式实现与应用程序MyDialog同样的功能。
其操作步骤如下: 
(1) 使用“MFC应用”项目模板创建一个项目名为Li5_5的单文档应用程序。
(2) 与应用程序MyDialog一样创建对话框资源和对话框类,并在类中添加与控件相关联的成员变量。
(3)  定义对话框指针。
①  在CLi55View视图类中增加CSquare指针类型的公有成员变量m_pDlg,并初始化为NULL。
②  在Li5_5View.h文件的头部预编译命令之前增加类的前向声明语句。

class CSquare;

class CLi5_5Doc;

类的前向声明语句的作用为: 由于在CLi5_5View类中有一个CSquare类的指针和一个返回值为CLi5_5Doc指针的GetDocument()函数,所以必须保证CSquare类和CLi5_5Doc类的声明出现在CLi5_5View之前,否则会产生编译错误。
(4)  增加对话框类成员变量接收视图指针。
①  在CSquare类中增加CLi5_5View指针类型的公有成员变量m_pParent,并在CSquare类的构造函数中添加对该变量进行初始化的语句。

m_pParent=(CLi55View*)pParent;

这样就可以在CSquare类的其他函数中利用这个指针向主窗口发送消息了。
②  在Square.h文件的头部预编译命令之前增加类的前向声明语句。

class CLi5_5View; 

(5)  在Li5_5View.cpp文件头部所有的include语句之后添加如下include语句,将对话框类的头文件包含进来。

#include "Square.h"

(6)  增加菜单,显示对话框。
①  使用菜单编辑器增加一个“对话框”主菜单,并在其中添加“非模态对话框”菜单项,其ID为ID_NOMODEDLG。

②  利用“类向导”工具在视图类中为ID_NOMODEDLG菜单项添加COMMAND消息的处理函数,在函数中添加以下代码。

void CLi55View::OnNomodedlg() 

{

if(m_pDlg)

m_pDlg->SetActiveWindow();//激活非模态对话框

else

{

//创建非模态对话框

m_pDlg=new CSquare(this); //创建对话框对象

m_pDlg->m_pParent=this; //对话框对象获取主窗口视图指针

m_pDlg->Create(IDD_DIALOG1,this); //创建对话框窗口

m_pDlg->ShowWindow(SW_SHOW); //显示对话框

}

}

(7)  在Square.cpp文件头部所有的include语句之后添加include语句,将视图类的头文件包含进来。

#include "Li5_5View.h"

(8)  利用“类向导”工具为对话框中的“确定”和“取消”按钮添加自己的处理函数,并添加代码。

void CSquare::OnOK() 

{

UpdateData(true);//获取用户数据

CClientDC dc(m_pParent); //创建指向主窗口视图的CClientDC对象

dc.Rectangle(10,10,10+m_length,10+m_length);//画正方形

}

void CSquare::OnCancel() 

{

if(m_pParent!=NULL)

{

m_pParent->m_pDlg=NULL;//表明对话框对象已经不存在了

DestroyWindow();//删除对话框窗口

}

}

(9)  利用“类向导”工具在对话框类CSquare中添加PostNcDestroy消息,并添加代码。

void CSquare::PostNcDestroy() 

{

CDialog::PostNcDestroy();

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

}

(10)  编译、链接并运行程序,选择“对话框”|“非模态对话框”菜单命令,打开“输入边长”对话框,输入边长后单击“确定”按钮,运行结果如图5.14所示。


图5.14非模态对话框的运行结果


5.3.2窗口对象的自动清除

一个MFC窗口对象包括两方面的内容: 一是窗口对象封装的窗口,即地地道道的Windows窗口; 二是窗口对象本身是一个C++对象。为了区分,把前者简称为窗口,
把后者称为窗口对象。如果要删除一个MFC窗口对象,应该先删除窗口对象封装的窗口,然后删除窗口对象本身。

删除窗口最直接的方法是调用CWnd::DestroyWindow()或::DestroyWindow(),前者封装了后者的功能。前者不仅会调用后者,而且会使成员m_hWnd保存的HWND无效(NULL)。如果DestroyWindow()删除的是一个父窗口或拥有者窗口,则该函数会先自动删除所有的子窗口或被拥有者,然后再删除父窗口或拥有者。
一般情况下,在程序中不必直接调用DestroyWindow()来删除窗口,因为MFC会自动调用DestroyWindow()来删除窗口。例如,当用户退出应用程序时
会产生WM_CLOSE消息,该消息会导致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)来显示对话框。这样做的好处在于对话框中的数据可以保存下来,供以后使用。由于拥有者窗口在被关闭时会调用Window删除每一个所属窗口,所以只要非模态对话框是自动清除的,就不必担心对话框对象的删除问题。

例5.5中设计的非模态对话框就具有自动清除功能,这里对部分函数做如下修改: 

void CLi55View::OnNomodedlg() 

{

if(m_pDlg)

{

m_pDlg->SetActiveWindow();

m_pDlg->ShowWindow(SW_SHOW);//新增显示对话框语句

}

else

{

m_pDlg=new CSquare(this);

m_pDlg->m_pParent=this;

m_pDlg->Create(IDD_DIALOG1,this);

m_pDlg->ShowWindow(SW_SHOW);

}

}

void CSquare::OnCancel() 

{

if(m_pParent!=NULL)

{

CWnd::ShowWindow(SW_HIDE); //修改为隐藏对话框语句

}

}

重新运行程序,发现可以保存上次运行时对话框中的数据。
5.4属性页对话框
在设计较为复杂的对话框时经常会用到大量的控件,以至于在一个对话框中布置不了这些控件。用普通的对话框技术,这一问题很难解决。MFC提供了对属性页对话框的支持,可以很好地解决上述问题。

属性页对话框实际上是一个包含多个子对话框的对话框,这些子对话框通常被称为页(Page)。每次只有一个页是可见的,在对话框的顶端有一行标签,用户通过单击这些标签可以切换到不同的页。显然属性页对话框可以容纳大量控件。例如,Visual C++的“类向导”窗口就是属性页对话框的典型应用。

为了支持属性页对话框,MFC提供了CPropertySheet类和CPropertyPage类。前者代表属性页对话框,后者代表对话框中的某一页。CPropertySheet类对象或其派生类对象中包含了若干CPropertyPage类对象。CPropertyPage是CDialog类的派生类,而CPropertySheet是CWnd类的派生类。虽然CPropertySheet不是CDialog类的派生类,但使用CPropertySheet对象
与使用CDialog对象的方法是类似的。

属性页对话框是一种特殊的对话框,和普通对话框相比,它们的设计与实现既有许多相似之处,又有一些不同的特点。下面通过一个实例来讲解设计一个属性页对话框的过程。



视频讲解


【例5.6】创建一个单文档的MFC应用程序Li5_6,通过
选择“对话框”|“属性页对话框”菜单命令打开如图5.15所示的对话框,当单击“确定”按钮后将在消息框中输出相关信息。



图5.15“属性页对话框”


其操作步骤如下: 
(1) 使用“MFC应用”项目模板创建一个项目名为Li5_6的单文档应用程序。
(2)  设计对话框资源。分别为各页创建对话框模板,每页的模板最好具有相同尺寸,如果尺寸不统一,则框架将根据尺寸最大的页来确定属性页对话框的大小。

①  创建如图5.16所示的对话框资源作为属性页的第一页,将该对话框的ID改为IDD_PERSONAL,
将“描述文字”改为“姓名”。
②  创建如图5.17所示的对话框资源作为属性页的第二页,将该对话框的ID改为IDD_UNIT,将“描述文字”改为“工作单位”。


图5.16第一页属性页




图5.17第二页属性页


(3)  用“类向导”工具为每页创建新类,加入与控件对应的成员变量。
①  为属性页IDD_PERSONAL创建CPropertyPage类的派生类CPersonalPage,并加入与编辑框对应的成员变量m_name,其类型为CString。
②  为属性页IDD_ UNIT创建CPropertyPage类的派生类CUnitPage,并加入与编辑框对应的成员变量m_work,其类型为CString。
(4) 在CLi5_6View类的头文件开始处加入下列语句。

#include "PersonalPage.h"

#include "UnitPage.h"

(5)  增加菜单以打开属性页对话框。
①  使用菜单编辑器增加一个“对话框”主菜单,并在其中添加“属性页对话框”菜单项,其ID为ID_PROPAGE。
②  利用“类向导”工具在视图类中为ID_PROPAGE菜单项添加COMMAND消息的处理函数,在函数中添加代码。

void CLi56View::OnPropage() 

{

CPropertySheet m_mysheet(L"属性页对话框");

CPersonalPage PageFirst;

CUnitPage PageSec;

CString str=L"";

m_mysheet.AddPage(&PageFirst);

m_mysheet.AddPage(&PageSec);

if(m_mysheet.DoModal()==IDOK)

{

str=str+PageFirst.m_name+"工作单位是"+PageSec.m_work;

MessageBox(str);

}

}

(6) 编译、链接并运行程序。选择“对话框”|“属性页对话框”菜单命令,弹出如图5.15所示的属性页对话框。将姓名改为“Mary”,将工作单位改为“清华大学出版社”,当单击“确定”按钮后,将在消息框中输出“Mary工作单位是清华大学出版社”。

从上面的例子可以看出,对话框框架的创建过程与普通对话框基本相同,不同之处是还需将页对象加入CPropertySheet对象中。如果要创建的是模态对话框,应调用CPropertySheet::DoModal()函数; 如果想创建非模态对话框,则应该调用CPropertySheet::Create()函数。
若从CPropertySheet类派生了一个新类,则应该将所有的页对象以成员变量的形式嵌入派生类中,并在派生类的构造函数中调用CPropertySheet::AddPage()函数把各个页添加到对话框中。



视频讲解


【例5.7】使用CPropertySheet
类的派生类对象创建与例5.6同样的属性页对话框。
其操作步骤如下: 
(1)  与例5.6一样完成前3步,应用程序的名称为Li5_7。
(2) 创建CPropertySheet类的派生类CProframeSheet。
(3)  在CProframeSheet类的头文件开始处加入以下语句。

#include "PersonalPage.h"

#include "UnitPage.h"

(4)  在CLi5_7View.cpp文件头部所有的include语句之后添加语句。

#include "ProframeSheet.h"

(5)  在CProframeSheet.h中加入语句。

CUnitPage m_unit;

CPersonalPage m_personal;

(6)  在CProframeSheet类的第一个参数字符串的构造函数中加入语句,以便把各个页添加到对话框中。

AddPage(&m_personal);

AddPage(&m_unit);

(7)  增加菜单,显示属性页对话框。
①  使用菜单编辑器增加一个“对话框”主菜单,并在其中添加“属性页对话框”菜单项,其ID为ID_PROPAGE。
②  使用“类向导”工具在视图类中为ID_PROPAGE菜单项添加COMMAND消息的处理函数,在函数中添加代码。

void CLi57View::OnPropage() 

{

CProframeSheet m_mysheet(L"属性页对话框");

CString str=L" ";

if(m_mysheet.DoModal()==IDOK){

Str=str+m_mysheet.m_personal.m_name+"工作单位是"+m_mysheet.m_unit.m_work;

MessageBox(str);

}

(8)  编译、链接并运行程序,可以得到与例5.6相同的效果。
5.5通用对话框
Windows提供了一组标准用户界面的通用对话框,用户在程序中可以直接使用这些通用对话框,不必再设计对话框资源和对话框类,减少了大量的编程工作。为了在MFC应用程序中使用通用对话框,MFC提供了封装这些通用对话框的类。表5.3列出了MFC中一些常用的通用对话框类,这些类都是从CCommonDialog类派生而来的,而CCommonDialog类又是CDialog类的派生类。


表5.3常用的通用对话框类



通用对话框类说明

CFileDialog文件对话框,用于打开或保存文件
CColorDialog“颜色”对话框,用于选择不同的颜色
CFontDialog“字体”对话框,用于选择字体、字型、大小、效果及颜色
CPrintDialog“打印”和“打印设置”对话框,用于打印和进行打印设置
CPageSetupDialog“页面设置”对话框,用于设置和修改打印边距等
CFindReplaceDialog“查找”和“替换”对话框,用于查找和替换文本字符串

在这些通用对话框中,“查找”和“替换”对话框是非模态对话框,其他对话框都是模态对话框,它们的使用方式由其所属类型来决定。下面分别介绍这些通用对话框类的构造函数及相应类的成员函数和数据成员及其使用。
5.5.1CFileDialog类
CFileDialog类用于实现文件对话框,以支持文件的打开和保存操作。用户要打开或保存文件,就会和文件
对话框“打交道”,图5.18显示了一个标准的用于打开文件的“打开”对话框。使用“MFC应用”项目模板建立的应用程序中自动加入了文件对话框,在“文件”菜单中选择“打开”或“另存为”菜单命令就会启动它们。


图5.18“打开”对话框


利用CFileDialog类使用文件对话框与一般模态对话框类似,首先声明一个CFileDialog类对象,这时会自动调用CFileDialog类的构造函数。该构造函数的原型为

CFileDialog(BOOL bOpenFileDialog,LPCTSTR lpszDefExt=NULL,LPCTSTR lpszFileName=NULL,DWORD dwFlags=OFN_HIDEREADONLY

|OFN_OVERWRITEPROMPT,LPCTSTR lpszFilter=NULL,CWnd *pParentWnd=NULL)

参数bOpenFileDialog是一个标记位,其值如果为TRUE,那么将构造“打开”对话框; 如果为FALSE,那么将构造“另存为”对话框。
参数lpszDefExt为默认的文件扩展名。如果用户没有在“文件名”编辑框中输入扩展名,则由lpszDefExt所指定的扩展名将自动附加在文件名后。

参数lpszFileName是出现在“文件名”编辑框中的初始文件名。如果该参数的值为NULL,则不显示初始文件名。

参数dwFlags由一个或多个标志组成。其中,OFN_HIDEREADONLY将隐藏只读文件,OFN_ALLOWMULTISELECT将允许在选择时与Shift键或Ctrl键配合以选择多个文件。
参数lpszFilter指定文件过滤器,用于确定显示在文件列表中的文件类型。每个过滤器都是一个字符串对,第一个字符串描述过滤器,第二个字符串是用户过滤文件的扩展名,多个扩展名要用分号(;)作为分界符,两个字符串之间用管道符号(|)分隔。整个字符串以两个管道符号(||)结束,可以用CString对象值作为该参数的值。例如,以下字符串就是一个描述只在文件列表框中显示文本文件(*.txt)和Word文件(*.doc)的过滤器。

CFileDialog dlg(TRUE,"bmp","*.bmp", OFN_HIDEREADONLY

|OFN_ALLOWMULTISELECT,"文本文件(*.txt)|*.txt|Word文件(*.doc)|*.doc||");

然后调用CFileDialog::DoModal()来启动对话框。若调用DoModal()函数打开对话框返回的是IDOK,那么可以用表5.4列出的CFileDialog类的成员函数来获取与所选文件有关的信息。


表5.4CFileDialog类的常用成员函数



类的成员函数功 能 说 明

GetPathName()获取当前所选文件的全路径
GetFileName()获取当前所选文件的文件名
GetFileExt()获取当前所选文件的扩展名
GetFileTitle()获取当前所选文件的标题
GetNextPathName()获取所选择的下一个文件的全路径
GetStartPosition()得到文件列表的第一个元素的位置




视频讲解


【例5.8】创建一个单文档的MFC应用程序Li5_8,利用文件对话框打开一个文本文件。
其操作步骤如下: 
(1) 使用“MFC应用”项目模板创建一个项目名为Li5_8的单文档应用程序。
(2)  使用“类向导”工具在视图类CLi58View中添加WM_LBUTTONDOWN消息,并添加代码。

void CLi58View::OnLButtonDown(UINT nFlags, CPoint point) 

{

CString FilePathName;

CFileDialog dlg(TRUE,L"txt",L"*.txt", 

OFN_HIDEREADONLY|OFN_ALLOWMULTISELECT,

L"文本文件(*.txt)|*.txt|Word文件(*.doc)|*.doc||");

if(dlg.DoModal()==IDOK)

FilePathName=dlg.GetPathName(); //得到文件路径

ShellExecute(NULL,L"open",FilePathName,NULL,NULL,SW_RESTORE); //打开文件

CView::OnLButtonDown(nFlags, point);

}

(3) 编译、链接并运行程序。在视图窗口中单击,运行结果如图5.19所示。


图5.19使用文件对话框


5.5.2CColorDialog类


图5.20“颜色”对话框


CColorDialog类用于实现“颜色”对话框,如图5.20所示。在Windows的画板程序中,如果用户双击“颜色面板”中的某种颜色,就会显示一个“颜色”对话框让用户选择颜色。

利用CColorDialog类使用“颜色”对话框也与一般模态对话框类似。首先构建一个CColorDialog对象,然后调用CColorDialog::DoModal()来启动对话框。CColorDialog类的构造函数的原型为

CColorDialog(COLORREF clrInit = 0, DWORD dwFlags = 0,
CWnd* pParentWnd = NULL);

其中,参数clrInit用来指定初始的颜色选择,dwFlags用来设置对话框,pParentWnd用来指定对话框的父窗口或拥有者窗口。

根据DoModal()返回的是IDOK还是IDCANCEL可以知道用户是否确认了对颜色的选择。若调用DoModal()函数打开对话框返回的是IDOK,调用CColorDialog::GetColor()可以返回一个COLORREF类型的结果来表示在对话框中选择的颜色。COLORREF是一个32位的值,用来说明一个RGB颜色。GetColor()返回的COLORREF的格式是0x00bbggrr,即低位3个字节分别包含了蓝、绿、红3种颜色的强度。



视频讲解


【例5.9】创建一个单文档的MFC应用程序Li5_9,利用
“颜色”对话框选择颜色,并在视图区中画一个该颜色的矩形。
其操作步骤如下: 
(1) 使用“MFC应用”项目模板创建一个名为Li5_9的单文档应用程序。
(2) 给视图类CLi59View添加一个成员变量m_cc,用来保存选择的颜色,其类型为COLORREF。
(3)  使用“类向导”工具在视图类CLi59View中添加WM_LBUTTONDOWN消息,并添加代码。

void CLi59View::OnLButtonDown(UINT nFlags, CPoint point) 

{

CColorDialog dlg; //构建一个CColorDialog对象

if(dlg.DoModal()==IDOK)

{

CPen newpen,*oldpen;

CClientDC dc(this);

m_cc=dlg.GetColor();//得到在对话框中选择的颜色

//用得到的颜色画矩形

newpen.CreatePen(PS_SOLID,2,m_cc);

oldpen=dc.SelectObject(&newpen);

dc.Rectangle(100,100,200,200);

dc.SelectObject(oldpen);

}

CView::OnLButtonDown(nFlags, point);

}

(4) 编译、链接并运行程序。在视图窗口中单击,弹出“颜色”对话框,选择颜色后
单击“确定”按钮,在视图窗口中出现一个选定颜色的矩形,如图5.21所示。


图5.21使用“颜色”对话框



5.5.3CFontDialog类

CFontDialog类支持“字体”对话框,用来让用户选择字体。利用CFontDialog类使用“字体”对话框的过程
是首先构建一个CFontDialog对象,然后调用CFontDialog::DoModal()来启动对话框。
CFontDialog类的构造函数的原型为

CFontDialog(LPLOGFONT lplfInitial = NULL, 

DWORD dwFlags = CF_EFFECTS |CF_SCREENFONTS,

CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL);

其中,参数lplfInitial指向一个LOGFONG结构,用来初始化对话框中的字体设置; dwFlags用于设置对话框; pdcPrinter指向一个代表打印机的CDC对象,若设置该参数,则选择的字体就为打印机所用; pParentWnd用于指定对话框的父窗口或拥有者窗口。
CFontDialog类的数据成员m_cf用于自定义CFontDialog对象的结果。若DoModal()返回IDOK,那么可以用表5.5列出的CFontDialog类的成员函数来获得所选字体的信息。


表5.5CFontDialog类的常用成员函数



类的成员函数功能

GetCurrentFont()用来获得所选字体的属性。该函数有一个参数,该参数是指向LOGFONT结构的指针,函数将所选字体的各种属性写入这个LOGFONT结构中
GetFaceName()返回一个包含所选字体名称的CString对象
GetStyleName()返回一个包含所选字体风格的CString对象
GetSize()返回所选字体的尺寸(以10个像素为单位)
GetColor()返回一个含有所选字体颜色的COLORREF型值
GetWeight()返回所选字体的权值
IsStrikeOut()若用户选择了空心效果,返回TRUE,否则返回FALSE
IsUnderline()若用户选择了下画线效果,返回TRUE,否则返回FALSE
IsBold()若用户选择了黑体风格,返回TRUE,否则返回FALSE
IsItalic()若用户选择了斜体风格,返回TRUE,否则返回FALSE




视频讲解


【例5.10】创建一个单文档的MFC应用程序Li5_10,利用
“字体”对话框选择字体的属性,并在视图区中以该属性显示文本信息。
其操作步骤如下: 
(1) 使用“MFC应用”项目模板创建一个项目名为Li5_10的单文档应用程序。
(2) 在视图类CLi510View中添加一个成员变量m_cc,用来保存选择的颜色,其类型为COLORREF。
(3)  在视图类CLi510View中添加一个成员变量m_font,用来保存选择的字体,其类型为LOGFONT。
(4)  使用“类向导”工具在视图类CLi510View中添加WM_LBUTTONDOWN消息,并添加代码。

void CLi510View::OnLButtonDown(UINT nFlags, CPoint point) 

{

CFontDialog dlg;

if(dlg.DoModal()==IDOK)

{

//得到在对话框中选择的字体

memcpy(&m_cf,dlg.m_cf.lpLogFont,sizeof(LOGFONT));

m_cc=dlg.GetColor();//得到在对话框中选择的颜色

CFont font;

font.CreateFontIndirect(&m_cf); //创建字体

CClientDC dc(this);

CFont* def_font=dc.SelectObject(&font); //选择字体

dc.SetTextColor(m_cc); //选择字体颜色

dc.TextOut(5,5,"Hello C++ 6.0");

dc.SelectObject(def_font);

font.DeleteObject();

}

CView::OnLButtonDown(nFlags, point);

}

(5) 编译、链接并运行程序。在视图窗口中单击,弹出“字体”对话框,选择字体的相关属性后单击“确定”按钮,效果如图5.22所示。



图5.22使用“字体”对话框


5.5.4CPrintDialog类和CPageSetupDialog类

CPrintDialog类支持“打印”和“打印设置”对话框,用户通过这两个对话框可以进行与打印有关的设置。图5.23显示了一个“打印”对话框,图5.24显示了一个“打印设置”对话框。使用默认配置的“MFC应用”项目模板建立的程序支持“打印”对话框和“打印设置”对话框,用户可以在“文件”菜单中启动它们。



图5.23“打印”对话框




图5.24“打印设置”对话框


CPageSetupDialog类封装了标准的“页面设置”对话框,使得用户可以设置和修改打印边距等。

使用这两种对话框的过程与使用前3种对话框类似,只是在成员函数上有所不同,在此不再详细介绍。CPrintDialog类和CPageSetupDialog类的构造函数和成员函数请参阅MSDN文档。图5.25是使用CPageSetupDialog类打开的“页面设置”对话框。


图5.25“页面设置”对话框


5.5.5CFindReplaceDialog类

CFindReplaceDialog类用于实现“查找”对话框和“替换”对话框。这两个对话框都是非模态对话框,用于在正文中查找和替换指定的字符串。图5.26显示了一个“查找”对话框,图5.27显示了一个“替换”对话框。


图5.26“查找”对话框




图5.27“替换”对话框


在MFC类库中用于生成文本编辑器(例如Windows附带的记事本)的CEditView类自动实现了“查找”对话框和“替换”对话框的功能。虽然“MFC应用”项目模板并未提供相应的菜单命令,但可以在“编辑”菜单中加入“查找”和“替换”两项,令其ID分别为ID_EDIT_FIND和ID_EDIT_REPLACE,则“查找”对话框和“替换”对话框的功能就可以实现了。
一般很少直接用CFindReplaceDialog类来使用“查找”对话框和“替换”对话框。



视频讲解


5.6应 用 实 例
5.6.1实例简介

制作一个简单的计算器,实现加、减、乘、除、求倒数和平方根的混合运算,并能进行清屏及倒退操作。
5.6.2创建过程
1. 创建MFC应用程序框架

使用“MFC应用”项目模板生成一个基于对话框的应用程序Calculator,并将主窗口对话框的Caption改为Calculator。


图5.28对话框控件的布局


2. 编辑对话框
打开对话框编辑器,在对话框设计模板中添加如图5.28所示的控件,并对各个控件进行属性设置,如表5.6所示。

在Calculator窗口中共包含21个控件,其中一个为编辑框,20个为按钮。将编辑框设置为只读,不能接收输入; +、-、×、÷为操作按钮; +/-、Sqrt、1/x分别为取负、求平方根及求倒数按钮; Back为倒退按钮,
用于删除错误的数字输入; Clear为清除按钮,用于重新开始新的运算; 单击“=”按钮,则在编辑框中显示最后的计算结果。


表5.6控件ID和“描述文字”的设置



控件类型ID描述文字控件类型ID描述文字

编辑框IDC_EDIT_PUTOUT按钮IDC_ADD+
按钮IDC_NUMBER11按钮IDC_SUBTRACT-
按钮IDC_NUMBER22按钮IDC_MULTIPLY×
按钮IDC_NUMBER33按钮IDC_DIVIDE÷
按钮IDC_NUMBER44按钮IDC_RESULT=
按钮IDC_NUMBER55按钮IDC_MINUS+/-
按钮IDC_NUMBER66按钮IDC_BACKBack
按钮IDC_NUMBER77按钮IDC_CLEARClear
按钮IDC_NUMBER88按钮IDC_SQRTSqrt
按钮IDC_NUMBER99按钮IDC_RECIPROCAL1/x
按钮IDC_NUMBER00

3. 添加成员变量

(1)  利用“类向导”工具为编辑框在对话框类CCalculatorDlg中添加double型成员变量m_result。

(2)  为CCalculatorDlg类添加两个int型变量m_OperationCount、m_NumberCount,一个double型数组m_number[15]和一个int型数组m_Operation[15]。变量m_OperationCount存放输入的加、减、乘、除4种运算符的顺序号,m_NumberCount存放输入的操作数的顺序号; 数组m_number[15]存放输入的操作数,m_Operation[15]存放输入的操作符。

4. 添加消息映射及成员函数
(1)  手工加入ON_COMMAND_RANGE命令消息映射,处理分配给一系列相邻编号的命令ID。
①  在CalculatorDlg.h头文件中声明消息映射函数。

afx_msg void OnNumberKey(UINT nID); 

afx_msg void OnOperationKey(UINT nID); 

其中,函数OnNumberKey()用来响应数字按钮的单击操作,OnOperationKey()用来响应操作符按钮的单击操作。
②  在CalculatorDlg.cpp实现文件的消息映射表中加入ON_COMMAND_RANGE命令消息。

ON_COMMAND_RANGE(IDC_NUMBER1,IDC_NUMBER0,OnNumberKey)

ON_COMMAND_RANGE(IDC_MINUS,IDC_RESULT,OnOperationKey)

(2)  在CalculatorDlg.cpp实现文件中加入消息处理函数。

void CCalculatorDlg::OnNumberKey(UINT nID)

{//处理单击数字按钮操作,记录输入的操作数

int n=0;

switch(nID)//根据单击的数字键ID记录输入数字

{

case IDC_NUMBER1:

n=1;

break;

case IDC_NUMBER2:

n=2;

break;

case IDC_NUMBER3:

n=3;

break;

case IDC_NUMBER4:

n=4;

break;

case IDC_NUMBER5:

n=5;

break;

case IDC_NUMBER6:

n=6;

break;

case IDC_NUMBER7:

n=7;

break;

case IDC_NUMBER8:

n=8;

break;

case IDC_NUMBER9:

n=9;

break;

case IDC_NUMBER0:

n=0;

break;

}

//计算操作数

m_number[m_NumberCount]=m_number[m_NumberCount]*10+n;

m_result=m_number[m_NumberCount];

UpdateData(false); //在编辑框中显示操作数 

}

void CCalculatorDlg::OnOperationKey(UINT nID)

{//处理单击操作符按钮操作,记录输入的操作符

int i;

switch(nID) //根据单击的操作键ID记录输入的加、减、乘、除操

{ //作符,处理取负、求平方根、求倒数、倒退及清屏操作

case IDC_ADD:

m_Operation[m_OperationCount]=1;

break;

case IDC_SUBTRACT:

m_Operation[m_OperationCount]=2;

break;

case IDC_MULTIPLY:

m_Operation[m_OperationCount]=3;

break;

case IDC_DIVIDE:

m_Operation[m_OperationCount]=4;

break;

case IDC_MINUS: //取负

 m_number[m_NumberCount]=-m_number[m_NumberCount];

break;

case IDC_SQRT://求平方根

 m_number[m_NumberCount]=sqrt(m_number[m_NumberCount]);

break;

case IDC_RECIPROCAL: //求倒数

m_number[m_NumberCount]=(double)1/m_number[m_NumberCount];

break;

case IDC_BACK://倒退

 m_number[m_NumberCount]=(int)m_number[m_NumberCount]/10;

m_result=m_number[m_NumberCount];

UpdateData(false);

break;

case IDC_CLEAR: //清屏

for(i=1;i<11;i++)

{

m_number[i]=0;

m_Operation[m_OperationCount]=999;

m_NumberCount=1;

m_OperationCount=1;

m_result=0;

UpdateData(false);

}

break;

case IDC_RESULT://计算最后结果

cal();

break;

}

if(m_Operation[m_OperationCount]<5)

{

m_NumberCount++;

m_OperationCount++;

} 

}

(3)  在对话框类CalculatorDlg中添加void型成员函数cal(),并在CalculatorDlg.cpp实现文件前加上包含语句#include "math.h"。

void CCalculatorDlg::cal()

{

for(int i=1;i<15;i++)

switch(m_Operation[i]) //先处理乘、除运算

{

	 case 3:

m_number[i]=m_number[i+1]=m_number[i]*m_number[i+1];

break;

case 4:

m_number[i]=m_number[i+1]

=(double)m_number[i]/m_number[i+1];

break;

}

m_result=m_number[1];	 

for(i=1;i<15;i++)//处理加、减运算,计算最后结果

if(m_Operation[i]==1)

m_result=m_result+m_number[i+1];

else if(m_Operation[i]==2)

m_result=m_result-m_number[i+1];

UpdateData(false);//在编辑框中显示最后结果

}

5. 成员变量的初始化
代码如下: 

CCalculatorDlg::CCalculatorDlg(CWnd* pParent /*=NULL*/)

: CDialog(CCalculatorDlg::IDD, pParent)

{

…

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

m_NumberCount=1;

m_OperationCount=1;

for(int i=0;i<15;i++)

{

m_number[i]=0;

m_Operation[i]=999;

}

}

编译、链接并运行程序。该应用程序未能实现乘、除运算的连续操作,请读者自行完善。
习题
1. 填空题
(1) 对话框的主要功能是和。
(2)  从对话框的工作方式看,对话框可分为和两种类型。
(3)  对话框主要由与两部分组成。
(4)  使用函数可以创建模态对话框,使用函数可以创建非模态对话框。
(5) 为了支持属性页对话框,MFC提供了类和类。
2. 选择题
(1) 对话框的功能被封装在()类中。

A. CWndB. CDialogC. CObjectD. CCmdTarget
(2)  ()是非模态对话框。
A. “查找”对话框B. “字体”对话框
C. “打开”对话框D. “颜色”对话框
(3) 要将模态对话框在屏幕上显示需要用到()函数。
A. Create()B. DoModal()C. OnOK()D. 构造
(4) 通常将对话框的初始化工作放在()函数中进行。
A. OnOK()B. OnCancel()C. OnInitDialog()D. DoModal()
(5) 使用()通用对话框类可以打开文件。
A. CFileDialogB. CColorDialogC. CPrintDialogD. CFontDialog
3. 简答题
(1) 简述创建和使用模态对话框的主要步骤。
(2) 如何向对话框模板资源添加控件?如何添加与控件关联的成员变量?
(3)  什么是DDX和DDV?在编程时如何使用MFC提供的DDX功能?
(4) 简述创建属性页对话框的主要步骤。
4. 操作题
(1)  编写一个SDI应用程序,在执行某菜单命令时打开一个模态对话框,通过该对话框输入一对坐标值,单击OK按钮在视图区中的该坐标位置显示自己的姓名。
(2) 编写一个SDI应用程序,采用非模态对话框的方式完成与第(1)题同样的功能。

(3) 编写一个单文档的应用程序,为该应用程序添加两个按钮到工具栏中,单击第一个按钮,利用文件对话框打开一个.doc文件; 单击第二个按钮,利用“颜色”对话框选择颜色,并在视图区中画一个该颜色的矩形。