第14单元 对话框教学目标介绍对话框的概念和用法。
学习要求理解对话框、对话框模板和控件的概念,以及数据交换和验证机制,可编写使用对话框的程序。
授课内容对话框是一种特殊的窗口,主要功能是输出信息和接收用户的输入。对话框与控件是密不可分的,在每个对话框内一般都有一些控件,对话框依靠这些控件与用户进行交流信息。本单元主要介绍对话框,控件在第15单元中介绍。
14.1 对话框(Dialog)
对话框(Dialog)实际上也是一个窗口。在MFC中,对话框的功能被封装在CDialog类中,CDialog类是CWnd类的派生类。
图14-1 文件搜索对话框对话框分为模态对话框和非模态对话框两种。模态对话框垄断了用户的输入,当一个模态对话框打开时,用户只能与该对话框进行交互,而其它用户界面对象收不到用户的输入信息(如键盘和鼠标消息)。平时我们所遇到的大部分对话框都是模态对话框,例如通过File/Open命令打开的文件搜索对话框就是模态对话框(图14-1)。非模态对话框类似普通的窗口,并不垄断用户的输入。在非模式对话框打开时,用户随时可用鼠标点击等手段激活其他窗口对象,操纵完毕后再回到本对话框。非模式对话框的典型例子是Microsoft Word中的搜索对话框,打开搜索对话框后,用户仍可与其它窗口对象进行交互,可以一边搜索,一边修改文章,非常方便。
本节介绍模态对话框,非模态对话框将在14.5节介绍。
在程序中使用模态对话框有两个步骤:
1.在视图类或框架窗口类的消息响应函数(如鼠标消息或菜单选项的命令消息响应函数)中说明一个对话框类的对象(变量);
2.调用CDialog::DoModal()成员函数。
DoModal()函数负责对模态话框的创建和撤销。在创建对话框时,DoModal()函数的任务包括载入对话框模板资源、调用OnInitDialog()函数初始化对话框和将对话框显示在屏幕上。完成对话框的创建后,DoModal()函数启动一个消息循环,以响应用户的输入。由于该消息循环截获了几乎所有的输入消息,使主消息循环收不到对对话框的输入,致使用户只能与模态对话框进行交互,而其它用户界面对象收不到输入信息。
如果用户在对话框内点击了标识符为IDOK的按钮(通常该按钮的标题是“确定”或“OK”),或者按了回车键,则CDialog::OnOK()函数会被调用。OnOK()函数首先调用UpdateData()函数将数据从控件传给对话框成员变量,然后调用CDialog::EndDialog()函数关闭对话框。关闭对话框后,DoModal()函数会返回值IDOK。
如果用户点击了标识符为IDCANCEL的按钮(通常其标题为“取消”或“Cancel”),或按了ESC键,则会导致对CDialog::OnCancel()函数的调用。该函数只调用CDialog::EndDialog()函数关闭对话框。关闭对话框后,DoModal()函数会返回IDCANCEL。
在应用程序中,可根据DoModal()函数的返回值是IDOK还是IDCANCEL来判断用户是确定还是取消了对对话框的操作。
从MFC编程的角度来看,一个对话框由两部分组成:
1.对话框模板资源:对话框模板用于指定对话框的形状、所用控件及其分布,Developer Studio根据对话框模板来创建对话框对象。
2.对话框类:对话框类用来实现对话框的功能。由于各应用程序中的对话框具体功能不同,因此一般要从CDialog类中派生一个新类,以便添加特定的数据成员和成员函数。
相应地,对话框的设计也包括对话框模板的设计和对话框类的设计两个主要方面。具体来说,应有以下步骤:
1.向项目中添加对话框模板资源;
2.编辑对话框模板资源,加入所需的控件;
3.从CDialog类派生对话框类,加入与各控件对应的数据成员;
4.在框架窗口类或视图类的菜单选项、鼠标事件或其他消息响应函数中添加对话框对象的应用代码。
向项目中添加对话框模板资源和编辑对话框模板资源的方法在14.7中介绍,下面主要说明对话框类的设计方法,这包括以下几项工作:
1.从CDialog类派生一个对话框类,并通过对话框模板资源的ID建立它们之间的对应关系;
2.为对话框类添加与各控件相对应的成员变量;
3.为对话框进行初始化工作;
4.增加对控件通知消息的处理。
[例14-1] 为例9-1的吹泡泡程序加一版权(About)对话框。
图14-2 编辑吹泡泡程序的版权对话框说 明:Windows应用程序大都有一版权对话框,提供软件的版本号、出品商和版权说明等信息。版权对话框可以通过主菜单上的“关于…,菜单选项调出,也可以通过鼠标右键调出。本例采用后一种方法。
首先建立Win32 Application空项目,为项目建立C++ Source File和Resource Script文件(建立后关闭),并将项目设置为使用MFC。
向项目中添加一个对话框模板资源(通过菜单选项Insert/Resource…),将其Caption改为“关于吹泡泡程序”。然后利用对话框编辑器添加3个静态文本控件(Static Text)说明版本信息并删除“Cancel”按钮,见图14-2。
然后修改例9-1的程序,加入以下内容:
程 序:
1.在程序首部加上文件包含命令
#include,resource.h”
2.在框架窗口类之前加入从CDialog类派生的对话框类:
// 对话框类
class CAboutDlg,public CDialog
{
public:
CAboutDlg();
enum {IDD = IDD_DIALOG1};
};
inline CAboutDlg::CAboutDlg():CDialog(CAboutDlg::IDD){}
3.在框架窗口类中添加响应鼠标右键消息的代码,包括消息响应函数说明、消息响应宏和消息响应函数定义。鼠标右键消息响应函数为:
void CMyWnd::OnRButtonDown(UINT nFlags,CPoint point)
{
CAboutDlg dlg;
dlg.DoModal();
}
输入输出:除可用鼠标左键生成泡泡外,按下鼠标右键会弹出一版权对话框。
分 析:该对话框仅提供了固定的版本信息,没有实现用户与应用程序间的数据交流,所以结构比较简单。CAboutDlg类重载了构造函数并调用了基类CDialog的构造函数来实现对话框模板资源和对话框类之间的关联。注意其中的枚举类型定义,其实质是将具体的对话框模板资源的标识符在对话框类内映射为统一的标识符IDD。
在框架窗口类的OnRButtonDown()成员函数中,声明了一个CAboutDlg类的对象,并通过其DoModal()成员函数调用该对话框。这是典型的模态对话框调用方法,除非用户按下对话框上的OK按钮、Cancel()按钮或窗口右上角的关闭按钮退出对话框,用户的所有输入信息均被该对话框截留(无论该信息对对话框是否有用)。
实际上,可以使用ClassWizard直接从对话框模板资源生成对应的对话框类代码,无需手工编程。这方面的内容请参看14.8:“使用ClassWizard建立对话框类”。
14.2 控件
要使对话框真正具有与用户对话的能力,还必须使用控件。所谓控件,就是一个现成的程序组件,可以独立运行以完成一定的功能。Windows提供了大量控件,控件的使用不仅方便了Windows编程,还使Windows程序具有相当统一的外观和风格。
控件多与对话框一起使用。所有的控件都是CWnd类派生出来的后代类对象,因此它们均有和CWnd类似的属性。每个控件均有一个标识符(ID),在程序中可以通过这个标识符对相应的控件进行操作。
使用Visual C++编程时可用的控件很多,在本单元中,仅介绍其中的两个:静态文本控件和编辑控件,目的是介绍对话框中的数据交换机制。第15单元集中介绍其他控件。
静态文本控件是最简单的控件,其用途是在对话框上显示一段文字。在例14-1中,就是使用静态文本控件来显示软件的版本信息的。由于静态文本控件的文字和属性均在编辑对话框模板资源时确定,程序运行中一般无法改变,所以在编程时无需考虑静态文本控件。
编辑控件是最有用的控件,其功能十分全面,本身就是一个小型的文本编辑器。编辑控件的作用是接受用户输入的字符串信息。
以上控件作为资源的编辑方法可参看14.7:“对话框模板资源的编辑”。
14.3 对话框的初始化对话框的初始化工作一般在构造函数和OnInitDialog()函数中完成。在构造函数中的初始化工作主要是针对对话框的数据成员进行的。
在对话框创建时,会收到WM_INITDIALOG消息,对话框对该消息的处理函数是OnInitDialog()。调用OnInitDialog()函数时,对话框已初步创建,对话框的窗口句柄也已有效,但对话框还未被显示出来。因此,可以在OnInitDialog()函数中做一些影响对话框外观的初始化工作。OnInitDialog()对对话框类的作用与OnCreate()函数对CMainFrame类的作用类似。
14.4 对话框的数据交换和数据检验机制
在程序中,通过控件与用户的数据交流是分两步完成的。首先,要在对话框类中加入与控件对应的数据成员,通过数据交换(DDX)确定其与控件的数据交换关系。例如对于编辑控件,可在对话框类中声明一个CString类的数据成员,并通过DDX将其与编辑控件联系起来。这样,打开对话框时,编辑控件窗口显示的就是该数据成员原来的值,如果用户对编辑控件的内容进行了编辑修改,则在退出对话框后,该数据成员的值也相应地变为编辑后的内容。
在视图类(或框架窗口类)中调用对话框时,还可通过对话框的数据成员来处理用户的输入数据。具体说来,在调用对话框类的DoModal()函数打开对话框之前,可以通过设置对话框数据成员的值,使其成为控件窗口的显示信息;在退出对话框后,又可将对话框数据成员所反映的用户输入数据应用到程序的其他部分。也正因为如此,对话框类的数据成员通常被说明为public的,以便在上述情况下直接处理。
MFC提供了CDataExchange类来实现对话框类与控件之间的数据交换(DDX),该类还提供了数据检验机制(DDV)。所谓数据检验,即对用户输入数据的范围进行检查,如果不符合要求则拒绝接受。这样可以将用户输入数据限制在一预先确定的范围内。数据交换和检验机制不仅适用于编辑框控件,还适用于检查框、单选按钮、列表框和组合框等控件。
数据交换和检验机制通过CDialog::DoDataExchange()函数来完成,其原型为:
virtual void DoDataExchange ( CDataExchange* pDX );
其中参数pDX为指向CDataExchange类对象的指针,框架使用该对象建立数据交换内容。该函数由程序控件直接调用,可重载以加入数据交换函数实现对话框数据成员与控件的联系。数据交换函数很多,这里先介绍与编辑控件有关的数据交换函数:
void DDX_Text ( CDataExchange* pDX,int nIDC,int& value );
void DDX_Text ( CDataExchange* pDX,int nIDC,long& value );
void DDX_Text ( CDataExchange* pDX,int nIDC,CString& value );
void DDX_Text ( CDataExchange* pDX,int nIDC,float& value );
void DDX_Text ( CDataExchange* pDX,int nIDC,double& value );
其中参数pDX是指向CDataExchange对象的指针;参数nIDC是对话框对象中的编辑控件的标识符;而参数value是对话框中的数据成员的引用。注意该参数的类型不仅可以是CString,而且可以是各种数值类型。因此,编辑控件不仅可以用来输入文字数据,也可以用来输入数值数据。
下面介绍常用的数据检验函数:
void DDV_MaxChars ( CDataExchange* pDX,CString const& value,int nChars );
void DDV_MinMaxByte ( CDataExchange* pDX,BYTE value,BYTE minVal,
BYTE maxVal );
void DDV_MinMaxDateTime ( CDataExchange* pDX,CTime& refValue,
const CTime* refMinRange,const CTime* refMaxRange );
void DDV_MinMaxDWord( CDataExchange* pDX,DWORD const& value,
DWORD minVal,DWORD maxVal );
void DDV_MinMaxDouble ( CDataExchange* pDX,double const& value,
double minVal,double maxVal );
void DDV_MinMaxFloat ( CDataExchange* pDX,float value,float minVal,
float maxVal );
void DDV_MinMaxInt ( CDataExchange* pDX,int value,int minVal,int maxVal );
void DDV_MinMaxLong ( CDataExchange* pDX,long value,long minVal,
long maxVal );
void DDV_MinMaxMonth ( CDataExchange* pDX,CTime& refValue,
const CTime* refMinRange,const CTime* refMaxRange );
void DDV_MinMaxUnsigned ( CDataExchange* pDX,unsigned value,
unsigned minVal,unsigned maxVal );
void DDV_MinMaxSlider ( CDataExchange* pDX,DWORD value,DWORD minVal,
DWORD maxVal );
其中参数pDX和value的含义与DDX函数相同,最后一个(或两个)参数是数据范围。参数nChars是字符串的最大字符数,而参数minVal和maxVal分别为数值类数据的下限和上限。
[例14-2] 签名留念簿程序。该程序模仿签名簿,用户使用鼠标左键点击窗口客户区后会弹出一个对话框,输入姓名后可在鼠标点击位置显示出该签名。签名的颜色、字体大小和方向随机确定。
说 明:项目建立及添加对话框模板资源的方法同例14-1。修改对话框模板的ID为IDD_NAMEDLG,Caption为“签名对话框”,并添加一个静态文本控件(Caption改为“签名”)和一个编辑控件(ID改为IDC_EDITNAME)。
程 序:
// Example 14-2:签名留念簿程序
#include <afxwin.h>
#include "resource.h"
// 对话框类
class CNameDlg,public CDialog
{
public:
CPoint m_pointTopLeft;
CString m_strNameEdit;
public:
CNameDlg();
enum {IDD = IDD_NAMEDLG};
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
};
// 对话框类的构造函数
CNameDlg::CNameDlg():CDialog(CNameDlg::IDD)
{
m_strNameEdit = _T("");
}
// 数据交换和数据检验
void CNameDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX,IDC_EDITNAME,m_strNameEdit);
DDV_MaxChars(pDX,m_strNameEdit,20);
}
// 初始化对话框
BOOL CNameDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rect;
GetWindowRect(&rect);
rect = CRect(m_pointTopLeft,rect.Size());
MoveWindow(rect);
return TRUE;
}
// 签名类
class CSignal,public CObject
{
CString m_sName; // 姓名
CPoint m_pointSignal; // 签名位置
int m_nHeight; // 字体高
int m_nColor; // 签名颜色
int m_nEscapement; // 签名倾角
public:
CSignal(){}
void SetValue(CString name,CPoint point,int height,int color,
int escapement);
void ShowSignal(CDC *pDC);
};
// 签名类成员函数
void CSignal::SetValue(CString name,CPoint point,int height,int color,
int escapement)
{
m_sName = name;
m_pointSignal = point;
m_nHeight = height;
m_nColor = color;
m_nEscapement = escapement;
}
// 显示签名
void CSignal::ShowSignal(CDC *pDC)
{
CFont *pOldFont,font;
font.CreateFont(m_nHeight,0,m_nEscapement,0,400,FALSE,FALSE,
0,OEM_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,
DEFAULT_PITCH,"楷体");
pOldFont = pDC->SelectObject(&font);
switch(m_nColor)
{
case 0:
pDC->SetTextColor(RGB(0,0,0));
break;
case 1:
pDC->SetTextColor(RGB(255,0,0));
break;
case 2:
pDC->SetTextColor(RGB(0,255,0));
break;
case 3:
pDC->SetTextColor(RGB(0,0,255));
break;
}
pDC->TextOut(m_pointSignal.x,m_pointSignal.y,m_sName);
pDC->SelectObject(pOldFont);
}
// 框架窗口类
#define MAX_NAME 250
class CMyWnd,public CFrameWnd
{
CSignal m_signalList[MAX_NAME];
int m_nCount;
public:
CMyWnd(),m_nCount(0){}
protected:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// 消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
// 框架窗口类的成员函数
// 鼠标右键消息响应函数
void CMyWnd::OnLButtonDown(UINT nFlags,CPoint point)
{
if(m_nCount < MAX_NAME)
{
CNameDlg dlg;
dlg.m_pointTopLeft = point;
if(dlg.DoModal() == IDOK)
{
int height = rand()%60+12;
int color = rand()%4;
int escapement = (rand()%1200)-600;
CString name = dlg.m_strNameEdit;
m_signalList[m_nCount].SetValue(name,point,height,
color,escapement);
m_nCount++;
Invalidate();
}
}
}
// 绘制框架窗口客户区函数
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
for(int i=0; i<m_nCount; i++)
m_signalList[i].ShowSignal(&dc);
}
// 应用程序类
class CMyApp,public CWinApp
{
public:
BOOL InitInstance();
};
// 应用程序类的成员函数
BOOL CMyApp::InitInstance()
{
CMyWnd *pFrame = new CMyWnd;
pFrame->Create(0,_T("签字留念簿程序"));
pFrame->ShowWindow(m_nCmdShow);
this->m_pMainWnd = pFrame;
return TRUE;
}
// 全局应用程序对象
CMyApp ThisApp;
输入输出:每当用户在窗口客户区按下鼠标左键后可弹出一对话框,要求输入一签名。输入签名后退出对话框,在窗口相应位置显示该签名,字体大小、颜色和倾斜角是随机确定的,见图14-3。
分 析:对话框类CNameDlg的声明方法与例14-1类似,但在其中加入了一个CString类的数据成员m_strNameEdit。该数据成员与编辑控件IDC_EDITNAME对应,实现这一对应关系的代码在数据交换函数DoDataExchange()中。该变量的初始化在对话框类的构造函数中完成。在框架窗口类的鼠标左键消息响应函数OnLButtonDown()中,声明了一个对话框类CNameDlg的对象dlg,通过其DoModal()函数调用对话框。在退出对话框后,将对话框类的数据成员m_strNameEdit的值(即用户输入的签名)复制到框架窗口类的签名数组中去。
图14-3 签名簿程序对话框类还有一个CPoint类的数据成员m_pointTopLeft,用来存放对话框左上角的坐标。通常情况下,对话框总是出现在屏幕的中央,而在本例中我们希望对话框出现在用户点击鼠标左键的位置,因此需要对对话框的位置加以控制。这项工作在对话框初始化函数OnInitDialog()中完成:首先用CWnd类的GetWindowRect()函数取得对话框的大小信息,然后构造出新的对话框矩形区域,再使用 MoveWindow()函数将对话框移动到指定位置。这些操作均是通过CWnd类的成员函数实现的,因为对话框也是一种窗口,对话框类是CWnd类的派生类。
为了实现上述功能,在框架窗口类的鼠标左键消息响应函数中调用对话框对象的DoModal()函数之前,先将鼠标按键的位置传送到对话框对象的数据成员m_pointTopLeft中。这样,OnInitDialog()函数就可以将对话框在显示之前就移到指定的位置上。
根据对话框模板资源向应用程序中添加与控件有关的代码(如对话框类的相应成员变量声明、初始化和数据交换(DDX)代码等)均可使用ClassWizard自动生成,参看14.9:“为对话框类加入成员变量”。
自学内容
14.5 非模态对话框与模态对话框不同,非模态对话框不垄断用户的输入,用户打开非模态对话框后,仍然可以与其它界面对象进行交互。
非模态对话框的设计与模态对话框基本类似,也包括设计对话框模板资源和设计CDialog类的派生类两部分。但是,在对话框的创建和删除过程中,非模态对话框与模态对话框相比有以下区别:
1.非模态对话框的模板资源必须具有Visible风格(在属性对话框的More Styles页中设置),否则对话框将不可见,而模态对话框则无需设置该项风格;
2.非模态对话框对象是用new操作符动态创建的,而不象模态对话框那样以对象变量的形式出现。对于非模态对话框,应在对话框的拥有者窗口类内声明一个指向对话框类的指针成员变量,通过该指针可访问非模态对话框对象;
3.通过调用CDialog::Create()函数来启动非模态对话框,而不是象模态对话框那样使用CDialog::DoModal()来启动,这是应用非模态对话框的关键之处。由于Create()函数不会启动新的消息循环,非模态对话框与应用程序共用同一个消息循环,这样非模态对话框就不会垄断用户的输入。Create()函数在显示了非模态对话框后就立即返回,而DoModal()是在模态对话框被关闭后才返回的。
4.必须重载并重新编写对话框的OnOK()和OnCancel()函数,并在OnCancel()函数中调用DestroyWindow()函数来关闭非模态对话框。DestoryWindow()是CWnd类的成员函数,用于关闭窗口;
5.因为非模态对话框对象是用new操作符构建的,因此必须在对话框关闭后,用delete操作符删除之;
6.必须有一个标志表明非模态对话框是否打开。这样做的原因是用户有可能在打开一个模态对话框的情况下,又一次选择打开命令。应用程序根据该标志决定是打开一个新的对话框,还是仅仅把原来打开的对话框激活。通常可以用拥有者窗口中指向非模态对话框对象的指针作为这种标志,当对话框关闭时,给该指针赋NULL值,以表明该对话框对象已不存在了。
[例14-3] 将例14-2的签名留念簿中的对话框改为无模式对话框。用户可用鼠标右键调出签名对话框,并在不退出该对话框的情况下用鼠标左键将输入的签名显示在窗口客户区。
说 明:在向项目中添加对话框模板资源时,要在其属性对话框的More Styles页中选择Visible项。其他同例14-2。
程 序:该程序中的签名类CSignal和应用程序类与上例相同,因此下面仅列出了框架窗口类和对话框类。由于这两个类的成员函数中存在相互引用的情况,所以我们将框架窗口类的声明放在前面,接下来是对话框类的定义,并在框架窗口类之前加入了一条对对话框类的声明。最后是这两个类的成员函数定义。
// 框架窗口类
#define MAX_NAME 250
class CNameDlg;
class CMyWnd,public CFrameWnd
{
CSignal m_signalList[MAX_NAME];
int m_nCount;
CNameDlg *m_pNameDlg;
public:
CMyWnd();
~CMyWnd();
protected:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnRButtonDown(UINT nFlags,CPoint point);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// 对话框类
class CNameDlg,public CDialog
{
public:
BOOL m_bActive;
CString m_strNameEdit;
enum {IDD = IDD_NAMEDLG};
CNameDlg();
BOOL Create();
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
virtual void OnOK();
virtual void OnCancel();
};
// 框架窗口类的消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
// 框架窗口类的成员函数
// 框架窗口类的构造函数
CMyWnd::CMyWnd()
{
m_nCount = 0;
m_pNameDlg = new CNameDlg;
}
// 框架窗口类的析构函数
CMyWnd::~CMyWnd()
{
delete m_pNameDlg;
}
// 鼠标右键消息响应函数
void CMyWnd::OnRButtonDown(UINT nFlags,CPoint point)
{
if(m_pNameDlg->m_bActive)
m_pNameDlg->SetActiveWindow(); // 激活对话框
else
m_pNameDlg->Create(); // 显示对话框
}
// 鼠标左键消息响应函数
void CMyWnd::OnLButtonDown(UINT nFlags,CPoint point)
{
if(m_nCount < MAX_NAME)
{
int height = rand()%60+12;
int color = rand()%4;
int escapement = (rand()%1200)-600;
CString name = m_pNameDlg->m_strNameEdit;
m_signalList[m_nCount].SetValue(name,point,height,color,
escapement);
m_nCount++;
Invalidate();
}
}
// 绘制框架窗口客户区函数
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
for(int i=0; i<m_nCount; i++)
m_signalList[i].ShowSignal(&dc);
}
// 对话框类的成员函数
// 对话框类的构造函数
CNameDlg::CNameDlg():CDialog(CNameDlg::IDD)
{
m_bActive = FALSE;
m_strNameEdit = _T("");
}
// 数据交换
void CNameDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX,IDC_EDIT1,m_strNameEdit);
}
// 初始化对话框
BOOL CNameDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rect;
GetWindowRect(&rect);
MoveWindow(0,0,rect.Width(),rect.Height());
return TRUE;
}
// 显示无模态对话框
BOOL CNameDlg::Create()
{
m_bActive = TRUE;
return CDialog::Create(CNameDlg::IDD);
}
// 退出对话框
void CNameDlg::OnCancel()
{
m_bActive = FALSE;
DestroyWindow();
}
// 更新数据
void CNameDlg::OnOK()
{
UpdateData(TRUE);
}
输入输出:与例14-2类似。
分 析:由于本程序仅使用了一个非模态对话框,所以将其对象的建立和删除分别放在框架窗口类的构造函数和析构函数中,即该对话框对象的生存期与框架窗口相同。在对话框类中声明了一个数据成员m_bActive表示非模态对话框是否显示。在框架窗口类的鼠标右键消息响应函数OnRButtonDown()函数中根据该变量的值决定是调用Create()显示对话框还是仅仅激活它。激活对话框使用CWnd::SetActiveWindow()函数,其原型为
CWnd* SetActiveWindow();
该函数使本窗口成为活动窗口,并返回原来活动的窗口。
在对话框类中,重载了OnOK()函数和OnCancel()函数,前者用控件的内容更新对话框类的数据成员,后者通过调用DestoryWindow()销毁窗口对象。
在非模态对话框的OnCancel()函数中也可以不调用CWnd::DestroyWindow()函数,而代之以调用CWnd::ShowWindow(SW_HIDE)函数来隐藏对话框。下次打开对话框时就不必调用Create()函数了,只需调用CWnd::ShowWindow(SW_SHOW)即可显示对话框。这样做的好处在于对话框中的数据可以保存下来,供以后使用。
14.6 公用对话框在Windows程序中,经常会遇到一些有特定用途的对话框。例如,当选择Developer Studio的菜单选项File/Open时,就会弹出一个文件选择的对话框,用户可在其中选择想要打开的文件。与此类似的的还有颜色选择、字体选择、打印和打印设置以及正文搜索和替换对话框。这五种对话框均由Windows支持,被称为公用对话框。
MFC为这些公用对话框类提供了相应的对话框类(均为CDialog派生类),如CColorDialog(颜色选择对话框类)、CFileDialog(文件选择对话框类)、CFindReplaceDialog(文本查找和替换对话框类)、CFontDialog(字体选择对话框类)以及CPrintDialog(打印和打印设置对话框类)。这样,程序员在编写自己的应用程序时也可以直接选用这些对话框了。要注意的是,这些类的说明均在头文件afxdlgs.h中,在编程时要在程序首部加上文件包含命令:
#include <afxdlgs.h>
在编写文档/视图结构的应用程序时,文件选择对话框和打印和打印设置对话框由框架直接调用,配合文档的序列化应用。
图14-4 颜色选择公用对话框下面以颜色选择对话框和字体选择对话框为例说明公用对话框的使用方法。
14.6.1 颜色选择对话框
CColorDialog类用于实现颜色选择公用对话框。颜色选择对话框如图14-4所示,在Windows的PaintBrush(画板)程序中,如果用户在颜色面板的某种颜色上双击鼠标,就会显示一个颜色选择对话框来让用户选择颜色。
颜色选择对话框的创建与一般的模态对话框没什么两样:首先说明一个CColorDialog类的对象,然后调用CColorDialog::DoModal()函数来启动颜色选择对话框。CColorDialog类的构造函数的原型为:
CColorDialog ( COLORREF clrInit = 0,DWORD dwFlags = 0,
CWnd* pParentWnd = NULL );
其中参数clrInit用来指定初始的颜色选择,dwFlags用来设置对话框,pParentWnd用于指定对话框的父窗口或拥有者窗口。
图14-5 字体选择公用对话框在程序中,根据DoModal()函数的返回值是IDOK还是IDCANCEL可知用户是否确认了对颜色的选择。在由DoModal()函数返回后,调用CColorDialog::GetColor()可得到一个COLORREF类型的值来表示在对话框中选择的颜色。COLORREF是一个32位的值,用来说明一个RGB颜色。GetColor返回的COLORREF的格式是0x00bbggrr,即低位三个字节分别包含了蓝、绿、红三种颜色的强度。颜色选择对话框的应用可参看例14-5。
14.6.2 字体选择对话框
CFontDialog类支持字体选择对话框,可让用户选择字体。图14-5显示了一个字体选择对话框。字体选择对话框的创建过程与颜色选择对话框的类似,首先说明一个CFontDialog类的对象(变量),然后调用CFontDialog::DoModal()函数来启动对话框。
CFontDialog类的构造函数如下所示
CFontDialog ( LPLOGFONT lplfInitial = NULL,
DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS,
CDC* pdcPrinter = NULL,CWnd* pParentWnd = NULL );
参数lplfInitial指向一个LOGFONT结构体对象,用来初始化对话框中的字体设置;dwFlags用于设置对话框;pdcPrinter指向一个代表打印机的CDC对象,如果设置该参数,则选择的字体就为打印机所用。参数pParentWnd用于指定对话框的父窗口或拥有者窗口。
LOGFONT结构体类型用于说明字体,可参看10.4:“字体”。
如果字体选择对话框的DoModal()函数返回IDOK,则可调用CFontDialog的成员函数GetCurrentFont()来获得所选字体的信息。其原型为:
void GetCurrentFont( LPLOGFONT lplf );
其中参数lplf是一个指向LOGFONT结构的指针,函数将所选字体的各种属性写入这个LOGFONT结构中。
CFontDialog类还提供了一些成员函数用来查询使用字体对话框设置的字体的各项参数:
CString GetFaceName ( ) const; // 查询字体名称
CString GetStyleName ( ) const; // 查询字体风格
int GetSize ( ) const; // 查询字体大小
COLORREF GetColor ( ) const; // 查询字体颜色
int GetWeight ( ) const; // 查询字体磅数
BOOL IsStrikeOut ( ) const; // 查询字体是否有删除线
BOOL IsUnderline ( ) const; // 查询字体是否有下划线
BOOL IsBold ( ) const; // 查询是否黑体
BOOL IsItalic ( ) const; // 查询是否斜体使用字体对话框的例子见14-4。
编程与调试
14.7 对话框模板资源的编辑
图14-6 编辑吹泡泡程序的版权对话框利用Developer Studio提供的可视化设计工具,可以方便地设计对话框模板。为了向项目中插入对话框模板资源,可选择Developer Studio菜单中的Insert/Resource选项,并在Insert Resource对话框中双击Dialog项。完成后在Workspace窗口的Resource View中会出现一个名为IDD_DIALOG1的新对话框模板资源。双击IDD_DIALOG1,则会打开该对话框模板的编辑窗口,如图14-6所示。缺省的对话框模板有OK和Cancel两个按钮。
在资源编辑窗口的旁边有一个控件面板,在控件面板上用鼠标选择一个控件,然后在对话框中点击,则相应的控件就被放置到对话框模板中的鼠标点击位置。如果不能确定控件的类型,只要将鼠标在某个控件按钮上停留片刻,就会显示一个工具提示,指出该按钮所代表控件的名称。
如果看不到控件面板,可在Developer Studio的工具条的空白处单击鼠标右键,并在随之弹出的菜单中选择Controls。
当用鼠标选择对话框或控件时,会出现一个围绕该对话框或控件的虚框,拖动虚框的边界可以改变对话框或控件的大小,在Developer Studio的状态条中会显示出所选对象的坐标和尺寸。控件可以被拖动,也可以按箭头键来移动选中的控件。在拖动控件时如果按住Ctrl键,则可复制控件。选择控件后按Del键可删除该控件。
选中控件或对话框后按回车键,则会弹出一个属性对话框,属性对话框用来设置控件或对话框的各种属性。属性对话框是标签式对话框,第一页是常规属性(General)。一个典型的控件属性对话框见图14-6的左下方。如果不理解属性对话框中的选项的意思,可按F1键获得帮助。
在控件属性对话框的常规属性中,有一些控件共同的属性:
1.ID属性:用于指定控件的标识符,Windows依靠ID来区分不同的控件;
2.Caption(标题)属性:静态文本、组框、按钮、检查框、单选按钮等控件可以显示标题,用来对控件进行文字说明。控件标题中的字符&在紧跟其后的字符下加上下划线,用户按Alt+下划线字符将启动该控件。如果控件是一个单选按钮,则按下“Alt+下划线字符”将选择该按钮;如果是检查框,则相当于对该检查框按空格键;如果是按钮,则将激活按钮命令。
3.Visible属性:用来指定控件是否可见;
4.Disable属性:允许或禁止该控件。一个禁止的控件呈灰色显示,不能接收任何输入。
5.Tabstop属性:用户可以按Tab键移动到具有Tabstop属性的控件上。Tab移动的顺序可以由用户指定。按Ctrl+D则Tab顺序会显示出来,用户可用鼠标重新指定Tab顺序。缺省的Tab顺序是控件的创建次序。
6.Group属性:用来指定一组控件,用户可以用箭头键在该组控件内移动。在同一组内的单选按钮具有互斥的特性,即在这些单选按钮中只能有一个是选中的。如果一个控件具有Group属性,则这个控件以及按Tab顺序紧随其后的所有控件都属于一组的,直到遇到另一个有Group属性的控件为止。
注意对话框的尺寸单位不是象素,而是与字体的大小有关。X方向上一个单位等于字符平均宽度的1/4,Y方向上一个单位等于字符平均高度的1/8。这样,随着字体的改变,对话框单位也会改变,对话框本身的总体比例保持不变。
在编辑好对话框模板后可直接对其进行测试。按Ctrl+T键,则会弹出一个当前模板的测试对话框,这个对话框的外观和基本行为与程序中将要弹出的对话框一样。这样,不用编译运行程序,通过测试对话框就可以评估对话框是否合乎要求。如果发现了错误或不满意的地方,可按ESC键退出测试对话框并重新修改对话框模板。
14.8 使用ClassWizard建立对话框类使用ClassWizard可以十分方便地创建MFC窗口类的派生类,对话框类也不例外。其方法是打开相应的对话框模板资源,然后按Ctrl+W进入ClassWizard(也可直接选择Developer Studio菜单的View/ClassWizard选项)。
进入ClassWizard后,如果尚未为新对话框模板建立对应的对话框类,则ClassWizard会发现这是一个新的对话框模板。于是ClassWizard会询问是否要为这个新的对话框模板创建一个对话框类。如果选择“OK”按钮,则会弹出一个Create New Class对话框。
在该对话框中,Name栏输入对话框类的名称,Base class栏选择CDialog,Dialog ID栏选择新对话框模板的标识符。按Create按钮后即可创建对应的对话框类。
与对话框有关的消息主要是WM_INITDIALOG消息和控件通知消息。在ClassWizard对话框的Message Maps标签中的Message框中选择WM_INITDIALOG消息,可以为已建立的对话框类自动添加OnInitDialog()函数的框架,程序员可在其中加入自己的对话框初始化代码。
14.9 为对话框类加入成员变量对话框的主要功能是输出和输入数据,这个任务是通过控件完成的。为了和控件传递数据,需要在对话框类中说明一组与各控件对应的成员变量。
图14-7 为对话框类添加数据成员与控件对应的成员变量即可以是一个数据,也可以是一个控件对象,这可由具体编程需要来确定。例如,可为编辑控件指定一个数据变量,这样就可以很方便地取得或设置编辑控件所代表的数据。如果想对编辑控件进行控制,则应为其指定一个Cedit类的对象,通过该对象可以控制控件的行为。
利用ClassWizard可以很方便地为对话框类添加数据成员。其方法是在ClassWizard对话框中选择Member Variables标签,然后在Class name栏中选择相应的对话框类。此时这时在下面的变量列表中会出现该对话框中各控件的标识符。
双击列表中的某个控件的标识符,则会弹出Add Member Variable对话框。在Member variable name栏中输入变量名,在Category栏中选择Value(值变量)或Control(相应的控件类对象)。如果选择了Value,在应在Variable type栏中选择相应的变量类型。如果是数值变量,还可设置其检验范围。方法是在ClassWizard对话框的左下角输入其检验范围(见图14-7)。
按OK按钮后,相应的数据成员就会被加入到对话框类的声明中,同时也会在对话框类的构造函数中添加相应的初始化代码,以及在成员函数DoDataExchange()中自动设置数据交换(DDX)和数据检验(DDV)函数。
应用程序举例
[例14-4] 为例14-2的签名程序加上字体选择对话框。
说 明:本程序使用字体选择公用对话框(通过鼠标右键调出)选择签名的字体、字号和颜色等参数,在签名对话框中要输入姓名和签名与X轴的倾斜角。建立项目的方法与例14-2相似,只是要在签名对话框模板中再添加一个编辑控件用于输入签名的倾斜角,其标识符为IDD_EDIT2。
程 序:
// Example 14-4:签名留念簿程序
#include <afxwin.h>
#include <afxdlgs.h>
#include <string.h>
#include "resource.h"
// 对话框类
class CNameDlg,public CDialog
{
public:
CPoint m_pointTopLeft; // 对话框位置
CString m_strNameEdit; // 签名
LONG m_lEscapement; // 签名倾角
public:
CNameDlg();
enum {IDD = IDD_NAMEDLG};
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
};
// 对话框类的构造函数
CNameDlg::CNameDlg():CDialog(CNameDlg::IDD),m_pointTopLeft(0,0)
{
m_strNameEdit = _T("");
m_lEscapement = 0;
}
// 数据交换和数据检验
void CNameDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX,IDC_EDIT1,m_strNameEdit);
DDX_Text(pDX,IDC_EDIT2,m_lEscapement);
DDV_MaxChars(pDX,m_strNameEdit,20);
DDV_MinMaxLong(pDX,m_lEscapement,-600,600);
}
// 初始化对话框
BOOL CNameDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rect;
GetWindowRect(&rect);
rect = CRect(m_pointTopLeft,rect.Size());
MoveWindow(rect);
return TRUE;
}
// 签名类
class CSignal,public CObject
{
CString m_strSignal; // 姓名
COLORREF m_colorSignal; // 签名颜色
CPoint m_pointSignal; // 签名位置
LOGFONT m_fontSignal; // 签名字体
public:
CSignal(){}
void SetValue(CString signal,CPoint point,COLORREF color,
LONG escapement,LOGFONT *pfont);
void ShowSignal(CDC *pDC);
};
// 签名类成员函数
void CSignal::SetValue(CString signal,CPoint point,COLORREF color,
int escapement,LOGFONT *pfont)
{
m_strSignal = signal;
m_pointSignal = point;
m_colorSignal = color;
memcpy(&m_fontSignal,pfont,sizeof(LOGFONT));
m_fontSignal.lfEscapement = escapement;
}
// 显示签名
void CSignal::ShowSignal(CDC *pDC)
{
CFont font,*pOldFont;
font.CreateFontIndirect(&m_fontSignal);
pOldFont = pDC->SelectObject(&font);
pDC->SetTextColor(m_colorSignal);
pDC->TextOut(m_pointSignal.x,m_pointSignal.y,m_strSignal);
pDC->SelectObject(pOldFont);
}
// 框架窗口类
#define MAX_NAME 250
class CMyWnd,public CFrameWnd
{
CSignal m_signalList[MAX_NAME]; // 签名数组
int m_nCount; // 签名数量
LOGFONT m_fontSignal; // 签名字体
COLORREF m_colorSignal; // 签名颜色
public:
CMyWnd();
protected:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnRButtonDown(UINT nFlags,CPoint point);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// 消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
// 框架窗口类的成员函数
CMyWnd::CMyWnd()
{
m_nCount = 0;
m_colorSignal = RGB(0,0,0);
m_fontSignal.lfHeight = 40;
m_fontSignal.lfWidth = 0;
m_fontSignal.lfEscapement = 0;
m_fontSignal.lfOrientation = 0;
m_fontSignal.lfWeight = 400;
m_fontSignal.lfItalic = FALSE;
m_fontSignal.lfUnderline = FALSE;
m_fontSignal.lfStrikeOut = 0;
m_fontSignal.lfCharSet = OEM_CHARSET;
m_fontSignal.lfOutPrecision = OUT_DEFAULT_PRECIS;
m_fontSignal.lfClipPrecision = CLIP_DEFAULT_PRECIS;
m_fontSignal.lfQuality = DEFAULT_QUALITY;
m_fontSignal.lfPitchAndFamily = DEFAULT_PITCH;
strcpy(m_fontSignal.lfFaceName,"Arial");
}
// 鼠标右键消息响应函数
void CMyWnd::OnLButtonDown(UINT nFlags,CPoint point)
{
if(m_nCount < MAX_NAME)
{
CNameDlg dlg;
dlg.m_pointTopLeft = point;
if(dlg.DoModal() == IDOK)
{
LONG escapement = dlg.m_lEscapement;
CString name = dlg.m_strNameEdit;
m_signalList[m_nCount].SetValue(name,point,m_colorSignal,
escapement,&m_fontSignal);
m_nCount++;
Invalidate();
}
}
}
// 鼠标右键消息响应函数
void CMyWnd::OnRButtonDown(UINT nFlags,CPoint point)
{
CFontDialog dlg(&m_fontSignal);
if(dlg.DoModal() == IDOK)
{
dlg.GetCurrentFont(&m_fontSignal);
m_colorSignal = dlg.GetColor();
}
}
// 绘制框架窗口客户区函数
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
for(int i=0; i<m_nCount; i++)
m_signalList[i].ShowSignal(&dc);
}
// 应用程序类
class CMyApp,public CWinApp
{
public:
BOOL InitInstance();
};
// 应用程序类的成员函数
BOOL CMyApp::InitInstance()
{
CMyWnd *pFrame = new CMyWnd;
pFrame->Create(0,_T("签字留念簿程序"));
pFrame->ShowWindow(SW_SHOWMAXIMIZED);
this->m_pMainWnd = pFrame;
return TRUE;
}
// 全局应用程序对象
CMyApp ThisApp;
输入输出:使用鼠标右键可调出字体选择对话框选择签名的字体,使用鼠标左键调出签名对话框输入签名及其倾斜角。所有的签名均以各自的字体、字号、颜色和倾斜角在窗口客户区显示,如图14-8。
分 析:在签名对话框类中添加了一个输入签名倾斜角的编辑控件,对应的数据成员为m_lEscapement。由于该数据成员是数值型(LONG)的,所以可从编辑控件中接收一个数值。在DoDataExchange()函数中,分别对签名控件和倾斜角控件建立了数据交换和数据检验。
在签名类中,说明了一个LOGFONT类型的数据成员,用来说明签名的字体。注意,在字体选择公用对话框中没有输入倾斜角的位置,因此要用输入签名时输入的倾斜角替换该结构体对象中的倾斜角成员。在复制字体对象时,使用了一个全局函数memcpy(),其功能为复制内存区域。这是因为LOGFONT类型没有重载赋值运算符“=”,无法直接赋值。该函数的原型为:
void *memcpy ( void *dest,const void *src,size_t count );
其中参数dest和src分别为目标区域和源区域的地址,count为复制的字节数。该函数的原型在头文件string.h中。
在框架窗口类中也说明了一个LOGFONT类型的数据成员,用于接收用户输入的字体信息。该成员在框架窗口类的构造函数中初始化。在鼠标右键消息响应函数中,说明了一个CFontDialog的对象dlg,通过DoModal()调用字体选择公用对话框。由于LOGFONT中不保存字体的颜色信息,所以在退出字体选择对话框后要将用户选用的字体颜色单独保存。
在鼠标左键消息响应函数中,用签名对话框接收用户输入的签名及其倾斜角。然后,与保存在框架窗口类中的当前字体信息一起存入签名数组中。
[例14-5] 为例9-3的吹泡泡程序添加颜色选择对话框,使其可以绘出五颜六色的泡泡。
程 序:在例9-3的程序基础上作如下修改:
1.在程序首部添加文件包含命令:
#include <afxdlgs.h>
2.在框架窗口类声明中添加一个COLORREF类型的数组,存放各泡泡的颜色:
COLORREF m_colorBubble [MAX_BUBBLE];
3.修改鼠标左键消息映射函数,添加使用颜色选择公用对话框的代码:
void CMyWnd::OnLButtonDown ( UINT nFlags,CPoint point )
{
if(m_nBubbleCount < MAX_BUBBLE)
{
m_colorBubble[m_nBubbleCount] = RGB(200,200,200);
CColorDialog dlg(m_colorBubble[m_nBubbleCount]);
if(dlg.DoModal() == IDOK)
m_colorBubble[m_nBubbleCount] = dlg.GetColor();
int r = rand()%50+10;
CRect rect(point.x-r,point.y-r,point.x+r,point.y+r);
m_rectBubble[m_nBubbleCount] = rect;
m_nBubbleCount++;
InvalidateRect(rect,FALSE);
}
}
4.修改OnPaint()成员函数,添加根据泡泡颜色使用画刷的代码:
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
CBrush brushNew,*pbrushOld;
for(int i=0; i<m_nBubbleCount; i++)
{
brushNew.CreateSolidBrush(m_colorBubble[i]);
pbrushOld = dc.SelectObject(&brushNew);
dc.Ellipse(m_rectBubble[i]);
dc.SelectObject(pbrushOld);
brushNew.DeleteObject();
}
}
输入输出:在窗口客户区点击鼠标左键会弹出一颜色选择公用对话框,用户选择泡泡颜色后可在窗口相应位置显示一彩色泡泡。
分 析:在鼠标左键消息响应函数中,在添加泡泡前先将泡泡的颜色初始化为浅灰色。这样,即使用户按Cancel按钮退出颜色选择对话框,泡泡的颜色也已设置好。
在显示泡泡时,由于各个泡泡的颜色均可能不同,因此将建立画刷的工作放在循环中进行。这时要注意在显示完泡泡后要及时销毁原来的画刷(使用CBrush::DeleteObject()画刷)。否则,再次创建画刷时会出现错误。
单元上机练习题目修改例14-4的签名留念程序。用AppWizard生成一个SDI程序框架,并添加对话框资源。然后使用ClassWizard为项目添加对话框类的代码。找到AppWizard自动生成的版权对话框并对其进行编辑,添加自己的版权信息。
为例13-7的七巧板程序添加修改拼板块颜色的功能。
为例11-4的拼图程序添加可用对话框修改拼图参数(拼图块大小)的功能。
学习要求理解对话框、对话框模板和控件的概念,以及数据交换和验证机制,可编写使用对话框的程序。
授课内容对话框是一种特殊的窗口,主要功能是输出信息和接收用户的输入。对话框与控件是密不可分的,在每个对话框内一般都有一些控件,对话框依靠这些控件与用户进行交流信息。本单元主要介绍对话框,控件在第15单元中介绍。
14.1 对话框(Dialog)
对话框(Dialog)实际上也是一个窗口。在MFC中,对话框的功能被封装在CDialog类中,CDialog类是CWnd类的派生类。
图14-1 文件搜索对话框对话框分为模态对话框和非模态对话框两种。模态对话框垄断了用户的输入,当一个模态对话框打开时,用户只能与该对话框进行交互,而其它用户界面对象收不到用户的输入信息(如键盘和鼠标消息)。平时我们所遇到的大部分对话框都是模态对话框,例如通过File/Open命令打开的文件搜索对话框就是模态对话框(图14-1)。非模态对话框类似普通的窗口,并不垄断用户的输入。在非模式对话框打开时,用户随时可用鼠标点击等手段激活其他窗口对象,操纵完毕后再回到本对话框。非模式对话框的典型例子是Microsoft Word中的搜索对话框,打开搜索对话框后,用户仍可与其它窗口对象进行交互,可以一边搜索,一边修改文章,非常方便。
本节介绍模态对话框,非模态对话框将在14.5节介绍。
在程序中使用模态对话框有两个步骤:
1.在视图类或框架窗口类的消息响应函数(如鼠标消息或菜单选项的命令消息响应函数)中说明一个对话框类的对象(变量);
2.调用CDialog::DoModal()成员函数。
DoModal()函数负责对模态话框的创建和撤销。在创建对话框时,DoModal()函数的任务包括载入对话框模板资源、调用OnInitDialog()函数初始化对话框和将对话框显示在屏幕上。完成对话框的创建后,DoModal()函数启动一个消息循环,以响应用户的输入。由于该消息循环截获了几乎所有的输入消息,使主消息循环收不到对对话框的输入,致使用户只能与模态对话框进行交互,而其它用户界面对象收不到输入信息。
如果用户在对话框内点击了标识符为IDOK的按钮(通常该按钮的标题是“确定”或“OK”),或者按了回车键,则CDialog::OnOK()函数会被调用。OnOK()函数首先调用UpdateData()函数将数据从控件传给对话框成员变量,然后调用CDialog::EndDialog()函数关闭对话框。关闭对话框后,DoModal()函数会返回值IDOK。
如果用户点击了标识符为IDCANCEL的按钮(通常其标题为“取消”或“Cancel”),或按了ESC键,则会导致对CDialog::OnCancel()函数的调用。该函数只调用CDialog::EndDialog()函数关闭对话框。关闭对话框后,DoModal()函数会返回IDCANCEL。
在应用程序中,可根据DoModal()函数的返回值是IDOK还是IDCANCEL来判断用户是确定还是取消了对对话框的操作。
从MFC编程的角度来看,一个对话框由两部分组成:
1.对话框模板资源:对话框模板用于指定对话框的形状、所用控件及其分布,Developer Studio根据对话框模板来创建对话框对象。
2.对话框类:对话框类用来实现对话框的功能。由于各应用程序中的对话框具体功能不同,因此一般要从CDialog类中派生一个新类,以便添加特定的数据成员和成员函数。
相应地,对话框的设计也包括对话框模板的设计和对话框类的设计两个主要方面。具体来说,应有以下步骤:
1.向项目中添加对话框模板资源;
2.编辑对话框模板资源,加入所需的控件;
3.从CDialog类派生对话框类,加入与各控件对应的数据成员;
4.在框架窗口类或视图类的菜单选项、鼠标事件或其他消息响应函数中添加对话框对象的应用代码。
向项目中添加对话框模板资源和编辑对话框模板资源的方法在14.7中介绍,下面主要说明对话框类的设计方法,这包括以下几项工作:
1.从CDialog类派生一个对话框类,并通过对话框模板资源的ID建立它们之间的对应关系;
2.为对话框类添加与各控件相对应的成员变量;
3.为对话框进行初始化工作;
4.增加对控件通知消息的处理。
[例14-1] 为例9-1的吹泡泡程序加一版权(About)对话框。
图14-2 编辑吹泡泡程序的版权对话框说 明:Windows应用程序大都有一版权对话框,提供软件的版本号、出品商和版权说明等信息。版权对话框可以通过主菜单上的“关于…,菜单选项调出,也可以通过鼠标右键调出。本例采用后一种方法。
首先建立Win32 Application空项目,为项目建立C++ Source File和Resource Script文件(建立后关闭),并将项目设置为使用MFC。
向项目中添加一个对话框模板资源(通过菜单选项Insert/Resource…),将其Caption改为“关于吹泡泡程序”。然后利用对话框编辑器添加3个静态文本控件(Static Text)说明版本信息并删除“Cancel”按钮,见图14-2。
然后修改例9-1的程序,加入以下内容:
程 序:
1.在程序首部加上文件包含命令
#include,resource.h”
2.在框架窗口类之前加入从CDialog类派生的对话框类:
// 对话框类
class CAboutDlg,public CDialog
{
public:
CAboutDlg();
enum {IDD = IDD_DIALOG1};
};
inline CAboutDlg::CAboutDlg():CDialog(CAboutDlg::IDD){}
3.在框架窗口类中添加响应鼠标右键消息的代码,包括消息响应函数说明、消息响应宏和消息响应函数定义。鼠标右键消息响应函数为:
void CMyWnd::OnRButtonDown(UINT nFlags,CPoint point)
{
CAboutDlg dlg;
dlg.DoModal();
}
输入输出:除可用鼠标左键生成泡泡外,按下鼠标右键会弹出一版权对话框。
分 析:该对话框仅提供了固定的版本信息,没有实现用户与应用程序间的数据交流,所以结构比较简单。CAboutDlg类重载了构造函数并调用了基类CDialog的构造函数来实现对话框模板资源和对话框类之间的关联。注意其中的枚举类型定义,其实质是将具体的对话框模板资源的标识符在对话框类内映射为统一的标识符IDD。
在框架窗口类的OnRButtonDown()成员函数中,声明了一个CAboutDlg类的对象,并通过其DoModal()成员函数调用该对话框。这是典型的模态对话框调用方法,除非用户按下对话框上的OK按钮、Cancel()按钮或窗口右上角的关闭按钮退出对话框,用户的所有输入信息均被该对话框截留(无论该信息对对话框是否有用)。
实际上,可以使用ClassWizard直接从对话框模板资源生成对应的对话框类代码,无需手工编程。这方面的内容请参看14.8:“使用ClassWizard建立对话框类”。
14.2 控件
要使对话框真正具有与用户对话的能力,还必须使用控件。所谓控件,就是一个现成的程序组件,可以独立运行以完成一定的功能。Windows提供了大量控件,控件的使用不仅方便了Windows编程,还使Windows程序具有相当统一的外观和风格。
控件多与对话框一起使用。所有的控件都是CWnd类派生出来的后代类对象,因此它们均有和CWnd类似的属性。每个控件均有一个标识符(ID),在程序中可以通过这个标识符对相应的控件进行操作。
使用Visual C++编程时可用的控件很多,在本单元中,仅介绍其中的两个:静态文本控件和编辑控件,目的是介绍对话框中的数据交换机制。第15单元集中介绍其他控件。
静态文本控件是最简单的控件,其用途是在对话框上显示一段文字。在例14-1中,就是使用静态文本控件来显示软件的版本信息的。由于静态文本控件的文字和属性均在编辑对话框模板资源时确定,程序运行中一般无法改变,所以在编程时无需考虑静态文本控件。
编辑控件是最有用的控件,其功能十分全面,本身就是一个小型的文本编辑器。编辑控件的作用是接受用户输入的字符串信息。
以上控件作为资源的编辑方法可参看14.7:“对话框模板资源的编辑”。
14.3 对话框的初始化对话框的初始化工作一般在构造函数和OnInitDialog()函数中完成。在构造函数中的初始化工作主要是针对对话框的数据成员进行的。
在对话框创建时,会收到WM_INITDIALOG消息,对话框对该消息的处理函数是OnInitDialog()。调用OnInitDialog()函数时,对话框已初步创建,对话框的窗口句柄也已有效,但对话框还未被显示出来。因此,可以在OnInitDialog()函数中做一些影响对话框外观的初始化工作。OnInitDialog()对对话框类的作用与OnCreate()函数对CMainFrame类的作用类似。
14.4 对话框的数据交换和数据检验机制
在程序中,通过控件与用户的数据交流是分两步完成的。首先,要在对话框类中加入与控件对应的数据成员,通过数据交换(DDX)确定其与控件的数据交换关系。例如对于编辑控件,可在对话框类中声明一个CString类的数据成员,并通过DDX将其与编辑控件联系起来。这样,打开对话框时,编辑控件窗口显示的就是该数据成员原来的值,如果用户对编辑控件的内容进行了编辑修改,则在退出对话框后,该数据成员的值也相应地变为编辑后的内容。
在视图类(或框架窗口类)中调用对话框时,还可通过对话框的数据成员来处理用户的输入数据。具体说来,在调用对话框类的DoModal()函数打开对话框之前,可以通过设置对话框数据成员的值,使其成为控件窗口的显示信息;在退出对话框后,又可将对话框数据成员所反映的用户输入数据应用到程序的其他部分。也正因为如此,对话框类的数据成员通常被说明为public的,以便在上述情况下直接处理。
MFC提供了CDataExchange类来实现对话框类与控件之间的数据交换(DDX),该类还提供了数据检验机制(DDV)。所谓数据检验,即对用户输入数据的范围进行检查,如果不符合要求则拒绝接受。这样可以将用户输入数据限制在一预先确定的范围内。数据交换和检验机制不仅适用于编辑框控件,还适用于检查框、单选按钮、列表框和组合框等控件。
数据交换和检验机制通过CDialog::DoDataExchange()函数来完成,其原型为:
virtual void DoDataExchange ( CDataExchange* pDX );
其中参数pDX为指向CDataExchange类对象的指针,框架使用该对象建立数据交换内容。该函数由程序控件直接调用,可重载以加入数据交换函数实现对话框数据成员与控件的联系。数据交换函数很多,这里先介绍与编辑控件有关的数据交换函数:
void DDX_Text ( CDataExchange* pDX,int nIDC,int& value );
void DDX_Text ( CDataExchange* pDX,int nIDC,long& value );
void DDX_Text ( CDataExchange* pDX,int nIDC,CString& value );
void DDX_Text ( CDataExchange* pDX,int nIDC,float& value );
void DDX_Text ( CDataExchange* pDX,int nIDC,double& value );
其中参数pDX是指向CDataExchange对象的指针;参数nIDC是对话框对象中的编辑控件的标识符;而参数value是对话框中的数据成员的引用。注意该参数的类型不仅可以是CString,而且可以是各种数值类型。因此,编辑控件不仅可以用来输入文字数据,也可以用来输入数值数据。
下面介绍常用的数据检验函数:
void DDV_MaxChars ( CDataExchange* pDX,CString const& value,int nChars );
void DDV_MinMaxByte ( CDataExchange* pDX,BYTE value,BYTE minVal,
BYTE maxVal );
void DDV_MinMaxDateTime ( CDataExchange* pDX,CTime& refValue,
const CTime* refMinRange,const CTime* refMaxRange );
void DDV_MinMaxDWord( CDataExchange* pDX,DWORD const& value,
DWORD minVal,DWORD maxVal );
void DDV_MinMaxDouble ( CDataExchange* pDX,double const& value,
double minVal,double maxVal );
void DDV_MinMaxFloat ( CDataExchange* pDX,float value,float minVal,
float maxVal );
void DDV_MinMaxInt ( CDataExchange* pDX,int value,int minVal,int maxVal );
void DDV_MinMaxLong ( CDataExchange* pDX,long value,long minVal,
long maxVal );
void DDV_MinMaxMonth ( CDataExchange* pDX,CTime& refValue,
const CTime* refMinRange,const CTime* refMaxRange );
void DDV_MinMaxUnsigned ( CDataExchange* pDX,unsigned value,
unsigned minVal,unsigned maxVal );
void DDV_MinMaxSlider ( CDataExchange* pDX,DWORD value,DWORD minVal,
DWORD maxVal );
其中参数pDX和value的含义与DDX函数相同,最后一个(或两个)参数是数据范围。参数nChars是字符串的最大字符数,而参数minVal和maxVal分别为数值类数据的下限和上限。
[例14-2] 签名留念簿程序。该程序模仿签名簿,用户使用鼠标左键点击窗口客户区后会弹出一个对话框,输入姓名后可在鼠标点击位置显示出该签名。签名的颜色、字体大小和方向随机确定。
说 明:项目建立及添加对话框模板资源的方法同例14-1。修改对话框模板的ID为IDD_NAMEDLG,Caption为“签名对话框”,并添加一个静态文本控件(Caption改为“签名”)和一个编辑控件(ID改为IDC_EDITNAME)。
程 序:
// Example 14-2:签名留念簿程序
#include <afxwin.h>
#include "resource.h"
// 对话框类
class CNameDlg,public CDialog
{
public:
CPoint m_pointTopLeft;
CString m_strNameEdit;
public:
CNameDlg();
enum {IDD = IDD_NAMEDLG};
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
};
// 对话框类的构造函数
CNameDlg::CNameDlg():CDialog(CNameDlg::IDD)
{
m_strNameEdit = _T("");
}
// 数据交换和数据检验
void CNameDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX,IDC_EDITNAME,m_strNameEdit);
DDV_MaxChars(pDX,m_strNameEdit,20);
}
// 初始化对话框
BOOL CNameDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rect;
GetWindowRect(&rect);
rect = CRect(m_pointTopLeft,rect.Size());
MoveWindow(rect);
return TRUE;
}
// 签名类
class CSignal,public CObject
{
CString m_sName; // 姓名
CPoint m_pointSignal; // 签名位置
int m_nHeight; // 字体高
int m_nColor; // 签名颜色
int m_nEscapement; // 签名倾角
public:
CSignal(){}
void SetValue(CString name,CPoint point,int height,int color,
int escapement);
void ShowSignal(CDC *pDC);
};
// 签名类成员函数
void CSignal::SetValue(CString name,CPoint point,int height,int color,
int escapement)
{
m_sName = name;
m_pointSignal = point;
m_nHeight = height;
m_nColor = color;
m_nEscapement = escapement;
}
// 显示签名
void CSignal::ShowSignal(CDC *pDC)
{
CFont *pOldFont,font;
font.CreateFont(m_nHeight,0,m_nEscapement,0,400,FALSE,FALSE,
0,OEM_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,
DEFAULT_PITCH,"楷体");
pOldFont = pDC->SelectObject(&font);
switch(m_nColor)
{
case 0:
pDC->SetTextColor(RGB(0,0,0));
break;
case 1:
pDC->SetTextColor(RGB(255,0,0));
break;
case 2:
pDC->SetTextColor(RGB(0,255,0));
break;
case 3:
pDC->SetTextColor(RGB(0,0,255));
break;
}
pDC->TextOut(m_pointSignal.x,m_pointSignal.y,m_sName);
pDC->SelectObject(pOldFont);
}
// 框架窗口类
#define MAX_NAME 250
class CMyWnd,public CFrameWnd
{
CSignal m_signalList[MAX_NAME];
int m_nCount;
public:
CMyWnd(),m_nCount(0){}
protected:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// 消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
// 框架窗口类的成员函数
// 鼠标右键消息响应函数
void CMyWnd::OnLButtonDown(UINT nFlags,CPoint point)
{
if(m_nCount < MAX_NAME)
{
CNameDlg dlg;
dlg.m_pointTopLeft = point;
if(dlg.DoModal() == IDOK)
{
int height = rand()%60+12;
int color = rand()%4;
int escapement = (rand()%1200)-600;
CString name = dlg.m_strNameEdit;
m_signalList[m_nCount].SetValue(name,point,height,
color,escapement);
m_nCount++;
Invalidate();
}
}
}
// 绘制框架窗口客户区函数
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
for(int i=0; i<m_nCount; i++)
m_signalList[i].ShowSignal(&dc);
}
// 应用程序类
class CMyApp,public CWinApp
{
public:
BOOL InitInstance();
};
// 应用程序类的成员函数
BOOL CMyApp::InitInstance()
{
CMyWnd *pFrame = new CMyWnd;
pFrame->Create(0,_T("签字留念簿程序"));
pFrame->ShowWindow(m_nCmdShow);
this->m_pMainWnd = pFrame;
return TRUE;
}
// 全局应用程序对象
CMyApp ThisApp;
输入输出:每当用户在窗口客户区按下鼠标左键后可弹出一对话框,要求输入一签名。输入签名后退出对话框,在窗口相应位置显示该签名,字体大小、颜色和倾斜角是随机确定的,见图14-3。
分 析:对话框类CNameDlg的声明方法与例14-1类似,但在其中加入了一个CString类的数据成员m_strNameEdit。该数据成员与编辑控件IDC_EDITNAME对应,实现这一对应关系的代码在数据交换函数DoDataExchange()中。该变量的初始化在对话框类的构造函数中完成。在框架窗口类的鼠标左键消息响应函数OnLButtonDown()中,声明了一个对话框类CNameDlg的对象dlg,通过其DoModal()函数调用对话框。在退出对话框后,将对话框类的数据成员m_strNameEdit的值(即用户输入的签名)复制到框架窗口类的签名数组中去。
图14-3 签名簿程序对话框类还有一个CPoint类的数据成员m_pointTopLeft,用来存放对话框左上角的坐标。通常情况下,对话框总是出现在屏幕的中央,而在本例中我们希望对话框出现在用户点击鼠标左键的位置,因此需要对对话框的位置加以控制。这项工作在对话框初始化函数OnInitDialog()中完成:首先用CWnd类的GetWindowRect()函数取得对话框的大小信息,然后构造出新的对话框矩形区域,再使用 MoveWindow()函数将对话框移动到指定位置。这些操作均是通过CWnd类的成员函数实现的,因为对话框也是一种窗口,对话框类是CWnd类的派生类。
为了实现上述功能,在框架窗口类的鼠标左键消息响应函数中调用对话框对象的DoModal()函数之前,先将鼠标按键的位置传送到对话框对象的数据成员m_pointTopLeft中。这样,OnInitDialog()函数就可以将对话框在显示之前就移到指定的位置上。
根据对话框模板资源向应用程序中添加与控件有关的代码(如对话框类的相应成员变量声明、初始化和数据交换(DDX)代码等)均可使用ClassWizard自动生成,参看14.9:“为对话框类加入成员变量”。
自学内容
14.5 非模态对话框与模态对话框不同,非模态对话框不垄断用户的输入,用户打开非模态对话框后,仍然可以与其它界面对象进行交互。
非模态对话框的设计与模态对话框基本类似,也包括设计对话框模板资源和设计CDialog类的派生类两部分。但是,在对话框的创建和删除过程中,非模态对话框与模态对话框相比有以下区别:
1.非模态对话框的模板资源必须具有Visible风格(在属性对话框的More Styles页中设置),否则对话框将不可见,而模态对话框则无需设置该项风格;
2.非模态对话框对象是用new操作符动态创建的,而不象模态对话框那样以对象变量的形式出现。对于非模态对话框,应在对话框的拥有者窗口类内声明一个指向对话框类的指针成员变量,通过该指针可访问非模态对话框对象;
3.通过调用CDialog::Create()函数来启动非模态对话框,而不是象模态对话框那样使用CDialog::DoModal()来启动,这是应用非模态对话框的关键之处。由于Create()函数不会启动新的消息循环,非模态对话框与应用程序共用同一个消息循环,这样非模态对话框就不会垄断用户的输入。Create()函数在显示了非模态对话框后就立即返回,而DoModal()是在模态对话框被关闭后才返回的。
4.必须重载并重新编写对话框的OnOK()和OnCancel()函数,并在OnCancel()函数中调用DestroyWindow()函数来关闭非模态对话框。DestoryWindow()是CWnd类的成员函数,用于关闭窗口;
5.因为非模态对话框对象是用new操作符构建的,因此必须在对话框关闭后,用delete操作符删除之;
6.必须有一个标志表明非模态对话框是否打开。这样做的原因是用户有可能在打开一个模态对话框的情况下,又一次选择打开命令。应用程序根据该标志决定是打开一个新的对话框,还是仅仅把原来打开的对话框激活。通常可以用拥有者窗口中指向非模态对话框对象的指针作为这种标志,当对话框关闭时,给该指针赋NULL值,以表明该对话框对象已不存在了。
[例14-3] 将例14-2的签名留念簿中的对话框改为无模式对话框。用户可用鼠标右键调出签名对话框,并在不退出该对话框的情况下用鼠标左键将输入的签名显示在窗口客户区。
说 明:在向项目中添加对话框模板资源时,要在其属性对话框的More Styles页中选择Visible项。其他同例14-2。
程 序:该程序中的签名类CSignal和应用程序类与上例相同,因此下面仅列出了框架窗口类和对话框类。由于这两个类的成员函数中存在相互引用的情况,所以我们将框架窗口类的声明放在前面,接下来是对话框类的定义,并在框架窗口类之前加入了一条对对话框类的声明。最后是这两个类的成员函数定义。
// 框架窗口类
#define MAX_NAME 250
class CNameDlg;
class CMyWnd,public CFrameWnd
{
CSignal m_signalList[MAX_NAME];
int m_nCount;
CNameDlg *m_pNameDlg;
public:
CMyWnd();
~CMyWnd();
protected:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnRButtonDown(UINT nFlags,CPoint point);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// 对话框类
class CNameDlg,public CDialog
{
public:
BOOL m_bActive;
CString m_strNameEdit;
enum {IDD = IDD_NAMEDLG};
CNameDlg();
BOOL Create();
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
virtual void OnOK();
virtual void OnCancel();
};
// 框架窗口类的消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
// 框架窗口类的成员函数
// 框架窗口类的构造函数
CMyWnd::CMyWnd()
{
m_nCount = 0;
m_pNameDlg = new CNameDlg;
}
// 框架窗口类的析构函数
CMyWnd::~CMyWnd()
{
delete m_pNameDlg;
}
// 鼠标右键消息响应函数
void CMyWnd::OnRButtonDown(UINT nFlags,CPoint point)
{
if(m_pNameDlg->m_bActive)
m_pNameDlg->SetActiveWindow(); // 激活对话框
else
m_pNameDlg->Create(); // 显示对话框
}
// 鼠标左键消息响应函数
void CMyWnd::OnLButtonDown(UINT nFlags,CPoint point)
{
if(m_nCount < MAX_NAME)
{
int height = rand()%60+12;
int color = rand()%4;
int escapement = (rand()%1200)-600;
CString name = m_pNameDlg->m_strNameEdit;
m_signalList[m_nCount].SetValue(name,point,height,color,
escapement);
m_nCount++;
Invalidate();
}
}
// 绘制框架窗口客户区函数
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
for(int i=0; i<m_nCount; i++)
m_signalList[i].ShowSignal(&dc);
}
// 对话框类的成员函数
// 对话框类的构造函数
CNameDlg::CNameDlg():CDialog(CNameDlg::IDD)
{
m_bActive = FALSE;
m_strNameEdit = _T("");
}
// 数据交换
void CNameDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX,IDC_EDIT1,m_strNameEdit);
}
// 初始化对话框
BOOL CNameDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rect;
GetWindowRect(&rect);
MoveWindow(0,0,rect.Width(),rect.Height());
return TRUE;
}
// 显示无模态对话框
BOOL CNameDlg::Create()
{
m_bActive = TRUE;
return CDialog::Create(CNameDlg::IDD);
}
// 退出对话框
void CNameDlg::OnCancel()
{
m_bActive = FALSE;
DestroyWindow();
}
// 更新数据
void CNameDlg::OnOK()
{
UpdateData(TRUE);
}
输入输出:与例14-2类似。
分 析:由于本程序仅使用了一个非模态对话框,所以将其对象的建立和删除分别放在框架窗口类的构造函数和析构函数中,即该对话框对象的生存期与框架窗口相同。在对话框类中声明了一个数据成员m_bActive表示非模态对话框是否显示。在框架窗口类的鼠标右键消息响应函数OnRButtonDown()函数中根据该变量的值决定是调用Create()显示对话框还是仅仅激活它。激活对话框使用CWnd::SetActiveWindow()函数,其原型为
CWnd* SetActiveWindow();
该函数使本窗口成为活动窗口,并返回原来活动的窗口。
在对话框类中,重载了OnOK()函数和OnCancel()函数,前者用控件的内容更新对话框类的数据成员,后者通过调用DestoryWindow()销毁窗口对象。
在非模态对话框的OnCancel()函数中也可以不调用CWnd::DestroyWindow()函数,而代之以调用CWnd::ShowWindow(SW_HIDE)函数来隐藏对话框。下次打开对话框时就不必调用Create()函数了,只需调用CWnd::ShowWindow(SW_SHOW)即可显示对话框。这样做的好处在于对话框中的数据可以保存下来,供以后使用。
14.6 公用对话框在Windows程序中,经常会遇到一些有特定用途的对话框。例如,当选择Developer Studio的菜单选项File/Open时,就会弹出一个文件选择的对话框,用户可在其中选择想要打开的文件。与此类似的的还有颜色选择、字体选择、打印和打印设置以及正文搜索和替换对话框。这五种对话框均由Windows支持,被称为公用对话框。
MFC为这些公用对话框类提供了相应的对话框类(均为CDialog派生类),如CColorDialog(颜色选择对话框类)、CFileDialog(文件选择对话框类)、CFindReplaceDialog(文本查找和替换对话框类)、CFontDialog(字体选择对话框类)以及CPrintDialog(打印和打印设置对话框类)。这样,程序员在编写自己的应用程序时也可以直接选用这些对话框了。要注意的是,这些类的说明均在头文件afxdlgs.h中,在编程时要在程序首部加上文件包含命令:
#include <afxdlgs.h>
在编写文档/视图结构的应用程序时,文件选择对话框和打印和打印设置对话框由框架直接调用,配合文档的序列化应用。
图14-4 颜色选择公用对话框下面以颜色选择对话框和字体选择对话框为例说明公用对话框的使用方法。
14.6.1 颜色选择对话框
CColorDialog类用于实现颜色选择公用对话框。颜色选择对话框如图14-4所示,在Windows的PaintBrush(画板)程序中,如果用户在颜色面板的某种颜色上双击鼠标,就会显示一个颜色选择对话框来让用户选择颜色。
颜色选择对话框的创建与一般的模态对话框没什么两样:首先说明一个CColorDialog类的对象,然后调用CColorDialog::DoModal()函数来启动颜色选择对话框。CColorDialog类的构造函数的原型为:
CColorDialog ( COLORREF clrInit = 0,DWORD dwFlags = 0,
CWnd* pParentWnd = NULL );
其中参数clrInit用来指定初始的颜色选择,dwFlags用来设置对话框,pParentWnd用于指定对话框的父窗口或拥有者窗口。
图14-5 字体选择公用对话框在程序中,根据DoModal()函数的返回值是IDOK还是IDCANCEL可知用户是否确认了对颜色的选择。在由DoModal()函数返回后,调用CColorDialog::GetColor()可得到一个COLORREF类型的值来表示在对话框中选择的颜色。COLORREF是一个32位的值,用来说明一个RGB颜色。GetColor返回的COLORREF的格式是0x00bbggrr,即低位三个字节分别包含了蓝、绿、红三种颜色的强度。颜色选择对话框的应用可参看例14-5。
14.6.2 字体选择对话框
CFontDialog类支持字体选择对话框,可让用户选择字体。图14-5显示了一个字体选择对话框。字体选择对话框的创建过程与颜色选择对话框的类似,首先说明一个CFontDialog类的对象(变量),然后调用CFontDialog::DoModal()函数来启动对话框。
CFontDialog类的构造函数如下所示
CFontDialog ( LPLOGFONT lplfInitial = NULL,
DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS,
CDC* pdcPrinter = NULL,CWnd* pParentWnd = NULL );
参数lplfInitial指向一个LOGFONT结构体对象,用来初始化对话框中的字体设置;dwFlags用于设置对话框;pdcPrinter指向一个代表打印机的CDC对象,如果设置该参数,则选择的字体就为打印机所用。参数pParentWnd用于指定对话框的父窗口或拥有者窗口。
LOGFONT结构体类型用于说明字体,可参看10.4:“字体”。
如果字体选择对话框的DoModal()函数返回IDOK,则可调用CFontDialog的成员函数GetCurrentFont()来获得所选字体的信息。其原型为:
void GetCurrentFont( LPLOGFONT lplf );
其中参数lplf是一个指向LOGFONT结构的指针,函数将所选字体的各种属性写入这个LOGFONT结构中。
CFontDialog类还提供了一些成员函数用来查询使用字体对话框设置的字体的各项参数:
CString GetFaceName ( ) const; // 查询字体名称
CString GetStyleName ( ) const; // 查询字体风格
int GetSize ( ) const; // 查询字体大小
COLORREF GetColor ( ) const; // 查询字体颜色
int GetWeight ( ) const; // 查询字体磅数
BOOL IsStrikeOut ( ) const; // 查询字体是否有删除线
BOOL IsUnderline ( ) const; // 查询字体是否有下划线
BOOL IsBold ( ) const; // 查询是否黑体
BOOL IsItalic ( ) const; // 查询是否斜体使用字体对话框的例子见14-4。
编程与调试
14.7 对话框模板资源的编辑
图14-6 编辑吹泡泡程序的版权对话框利用Developer Studio提供的可视化设计工具,可以方便地设计对话框模板。为了向项目中插入对话框模板资源,可选择Developer Studio菜单中的Insert/Resource选项,并在Insert Resource对话框中双击Dialog项。完成后在Workspace窗口的Resource View中会出现一个名为IDD_DIALOG1的新对话框模板资源。双击IDD_DIALOG1,则会打开该对话框模板的编辑窗口,如图14-6所示。缺省的对话框模板有OK和Cancel两个按钮。
在资源编辑窗口的旁边有一个控件面板,在控件面板上用鼠标选择一个控件,然后在对话框中点击,则相应的控件就被放置到对话框模板中的鼠标点击位置。如果不能确定控件的类型,只要将鼠标在某个控件按钮上停留片刻,就会显示一个工具提示,指出该按钮所代表控件的名称。
如果看不到控件面板,可在Developer Studio的工具条的空白处单击鼠标右键,并在随之弹出的菜单中选择Controls。
当用鼠标选择对话框或控件时,会出现一个围绕该对话框或控件的虚框,拖动虚框的边界可以改变对话框或控件的大小,在Developer Studio的状态条中会显示出所选对象的坐标和尺寸。控件可以被拖动,也可以按箭头键来移动选中的控件。在拖动控件时如果按住Ctrl键,则可复制控件。选择控件后按Del键可删除该控件。
选中控件或对话框后按回车键,则会弹出一个属性对话框,属性对话框用来设置控件或对话框的各种属性。属性对话框是标签式对话框,第一页是常规属性(General)。一个典型的控件属性对话框见图14-6的左下方。如果不理解属性对话框中的选项的意思,可按F1键获得帮助。
在控件属性对话框的常规属性中,有一些控件共同的属性:
1.ID属性:用于指定控件的标识符,Windows依靠ID来区分不同的控件;
2.Caption(标题)属性:静态文本、组框、按钮、检查框、单选按钮等控件可以显示标题,用来对控件进行文字说明。控件标题中的字符&在紧跟其后的字符下加上下划线,用户按Alt+下划线字符将启动该控件。如果控件是一个单选按钮,则按下“Alt+下划线字符”将选择该按钮;如果是检查框,则相当于对该检查框按空格键;如果是按钮,则将激活按钮命令。
3.Visible属性:用来指定控件是否可见;
4.Disable属性:允许或禁止该控件。一个禁止的控件呈灰色显示,不能接收任何输入。
5.Tabstop属性:用户可以按Tab键移动到具有Tabstop属性的控件上。Tab移动的顺序可以由用户指定。按Ctrl+D则Tab顺序会显示出来,用户可用鼠标重新指定Tab顺序。缺省的Tab顺序是控件的创建次序。
6.Group属性:用来指定一组控件,用户可以用箭头键在该组控件内移动。在同一组内的单选按钮具有互斥的特性,即在这些单选按钮中只能有一个是选中的。如果一个控件具有Group属性,则这个控件以及按Tab顺序紧随其后的所有控件都属于一组的,直到遇到另一个有Group属性的控件为止。
注意对话框的尺寸单位不是象素,而是与字体的大小有关。X方向上一个单位等于字符平均宽度的1/4,Y方向上一个单位等于字符平均高度的1/8。这样,随着字体的改变,对话框单位也会改变,对话框本身的总体比例保持不变。
在编辑好对话框模板后可直接对其进行测试。按Ctrl+T键,则会弹出一个当前模板的测试对话框,这个对话框的外观和基本行为与程序中将要弹出的对话框一样。这样,不用编译运行程序,通过测试对话框就可以评估对话框是否合乎要求。如果发现了错误或不满意的地方,可按ESC键退出测试对话框并重新修改对话框模板。
14.8 使用ClassWizard建立对话框类使用ClassWizard可以十分方便地创建MFC窗口类的派生类,对话框类也不例外。其方法是打开相应的对话框模板资源,然后按Ctrl+W进入ClassWizard(也可直接选择Developer Studio菜单的View/ClassWizard选项)。
进入ClassWizard后,如果尚未为新对话框模板建立对应的对话框类,则ClassWizard会发现这是一个新的对话框模板。于是ClassWizard会询问是否要为这个新的对话框模板创建一个对话框类。如果选择“OK”按钮,则会弹出一个Create New Class对话框。
在该对话框中,Name栏输入对话框类的名称,Base class栏选择CDialog,Dialog ID栏选择新对话框模板的标识符。按Create按钮后即可创建对应的对话框类。
与对话框有关的消息主要是WM_INITDIALOG消息和控件通知消息。在ClassWizard对话框的Message Maps标签中的Message框中选择WM_INITDIALOG消息,可以为已建立的对话框类自动添加OnInitDialog()函数的框架,程序员可在其中加入自己的对话框初始化代码。
14.9 为对话框类加入成员变量对话框的主要功能是输出和输入数据,这个任务是通过控件完成的。为了和控件传递数据,需要在对话框类中说明一组与各控件对应的成员变量。
图14-7 为对话框类添加数据成员与控件对应的成员变量即可以是一个数据,也可以是一个控件对象,这可由具体编程需要来确定。例如,可为编辑控件指定一个数据变量,这样就可以很方便地取得或设置编辑控件所代表的数据。如果想对编辑控件进行控制,则应为其指定一个Cedit类的对象,通过该对象可以控制控件的行为。
利用ClassWizard可以很方便地为对话框类添加数据成员。其方法是在ClassWizard对话框中选择Member Variables标签,然后在Class name栏中选择相应的对话框类。此时这时在下面的变量列表中会出现该对话框中各控件的标识符。
双击列表中的某个控件的标识符,则会弹出Add Member Variable对话框。在Member variable name栏中输入变量名,在Category栏中选择Value(值变量)或Control(相应的控件类对象)。如果选择了Value,在应在Variable type栏中选择相应的变量类型。如果是数值变量,还可设置其检验范围。方法是在ClassWizard对话框的左下角输入其检验范围(见图14-7)。
按OK按钮后,相应的数据成员就会被加入到对话框类的声明中,同时也会在对话框类的构造函数中添加相应的初始化代码,以及在成员函数DoDataExchange()中自动设置数据交换(DDX)和数据检验(DDV)函数。
应用程序举例
[例14-4] 为例14-2的签名程序加上字体选择对话框。
说 明:本程序使用字体选择公用对话框(通过鼠标右键调出)选择签名的字体、字号和颜色等参数,在签名对话框中要输入姓名和签名与X轴的倾斜角。建立项目的方法与例14-2相似,只是要在签名对话框模板中再添加一个编辑控件用于输入签名的倾斜角,其标识符为IDD_EDIT2。
程 序:
// Example 14-4:签名留念簿程序
#include <afxwin.h>
#include <afxdlgs.h>
#include <string.h>
#include "resource.h"
// 对话框类
class CNameDlg,public CDialog
{
public:
CPoint m_pointTopLeft; // 对话框位置
CString m_strNameEdit; // 签名
LONG m_lEscapement; // 签名倾角
public:
CNameDlg();
enum {IDD = IDD_NAMEDLG};
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
};
// 对话框类的构造函数
CNameDlg::CNameDlg():CDialog(CNameDlg::IDD),m_pointTopLeft(0,0)
{
m_strNameEdit = _T("");
m_lEscapement = 0;
}
// 数据交换和数据检验
void CNameDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX,IDC_EDIT1,m_strNameEdit);
DDX_Text(pDX,IDC_EDIT2,m_lEscapement);
DDV_MaxChars(pDX,m_strNameEdit,20);
DDV_MinMaxLong(pDX,m_lEscapement,-600,600);
}
// 初始化对话框
BOOL CNameDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rect;
GetWindowRect(&rect);
rect = CRect(m_pointTopLeft,rect.Size());
MoveWindow(rect);
return TRUE;
}
// 签名类
class CSignal,public CObject
{
CString m_strSignal; // 姓名
COLORREF m_colorSignal; // 签名颜色
CPoint m_pointSignal; // 签名位置
LOGFONT m_fontSignal; // 签名字体
public:
CSignal(){}
void SetValue(CString signal,CPoint point,COLORREF color,
LONG escapement,LOGFONT *pfont);
void ShowSignal(CDC *pDC);
};
// 签名类成员函数
void CSignal::SetValue(CString signal,CPoint point,COLORREF color,
int escapement,LOGFONT *pfont)
{
m_strSignal = signal;
m_pointSignal = point;
m_colorSignal = color;
memcpy(&m_fontSignal,pfont,sizeof(LOGFONT));
m_fontSignal.lfEscapement = escapement;
}
// 显示签名
void CSignal::ShowSignal(CDC *pDC)
{
CFont font,*pOldFont;
font.CreateFontIndirect(&m_fontSignal);
pOldFont = pDC->SelectObject(&font);
pDC->SetTextColor(m_colorSignal);
pDC->TextOut(m_pointSignal.x,m_pointSignal.y,m_strSignal);
pDC->SelectObject(pOldFont);
}
// 框架窗口类
#define MAX_NAME 250
class CMyWnd,public CFrameWnd
{
CSignal m_signalList[MAX_NAME]; // 签名数组
int m_nCount; // 签名数量
LOGFONT m_fontSignal; // 签名字体
COLORREF m_colorSignal; // 签名颜色
public:
CMyWnd();
protected:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnRButtonDown(UINT nFlags,CPoint point);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// 消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
// 框架窗口类的成员函数
CMyWnd::CMyWnd()
{
m_nCount = 0;
m_colorSignal = RGB(0,0,0);
m_fontSignal.lfHeight = 40;
m_fontSignal.lfWidth = 0;
m_fontSignal.lfEscapement = 0;
m_fontSignal.lfOrientation = 0;
m_fontSignal.lfWeight = 400;
m_fontSignal.lfItalic = FALSE;
m_fontSignal.lfUnderline = FALSE;
m_fontSignal.lfStrikeOut = 0;
m_fontSignal.lfCharSet = OEM_CHARSET;
m_fontSignal.lfOutPrecision = OUT_DEFAULT_PRECIS;
m_fontSignal.lfClipPrecision = CLIP_DEFAULT_PRECIS;
m_fontSignal.lfQuality = DEFAULT_QUALITY;
m_fontSignal.lfPitchAndFamily = DEFAULT_PITCH;
strcpy(m_fontSignal.lfFaceName,"Arial");
}
// 鼠标右键消息响应函数
void CMyWnd::OnLButtonDown(UINT nFlags,CPoint point)
{
if(m_nCount < MAX_NAME)
{
CNameDlg dlg;
dlg.m_pointTopLeft = point;
if(dlg.DoModal() == IDOK)
{
LONG escapement = dlg.m_lEscapement;
CString name = dlg.m_strNameEdit;
m_signalList[m_nCount].SetValue(name,point,m_colorSignal,
escapement,&m_fontSignal);
m_nCount++;
Invalidate();
}
}
}
// 鼠标右键消息响应函数
void CMyWnd::OnRButtonDown(UINT nFlags,CPoint point)
{
CFontDialog dlg(&m_fontSignal);
if(dlg.DoModal() == IDOK)
{
dlg.GetCurrentFont(&m_fontSignal);
m_colorSignal = dlg.GetColor();
}
}
// 绘制框架窗口客户区函数
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
for(int i=0; i<m_nCount; i++)
m_signalList[i].ShowSignal(&dc);
}
// 应用程序类
class CMyApp,public CWinApp
{
public:
BOOL InitInstance();
};
// 应用程序类的成员函数
BOOL CMyApp::InitInstance()
{
CMyWnd *pFrame = new CMyWnd;
pFrame->Create(0,_T("签字留念簿程序"));
pFrame->ShowWindow(SW_SHOWMAXIMIZED);
this->m_pMainWnd = pFrame;
return TRUE;
}
// 全局应用程序对象
CMyApp ThisApp;
输入输出:使用鼠标右键可调出字体选择对话框选择签名的字体,使用鼠标左键调出签名对话框输入签名及其倾斜角。所有的签名均以各自的字体、字号、颜色和倾斜角在窗口客户区显示,如图14-8。
分 析:在签名对话框类中添加了一个输入签名倾斜角的编辑控件,对应的数据成员为m_lEscapement。由于该数据成员是数值型(LONG)的,所以可从编辑控件中接收一个数值。在DoDataExchange()函数中,分别对签名控件和倾斜角控件建立了数据交换和数据检验。
在签名类中,说明了一个LOGFONT类型的数据成员,用来说明签名的字体。注意,在字体选择公用对话框中没有输入倾斜角的位置,因此要用输入签名时输入的倾斜角替换该结构体对象中的倾斜角成员。在复制字体对象时,使用了一个全局函数memcpy(),其功能为复制内存区域。这是因为LOGFONT类型没有重载赋值运算符“=”,无法直接赋值。该函数的原型为:
void *memcpy ( void *dest,const void *src,size_t count );
其中参数dest和src分别为目标区域和源区域的地址,count为复制的字节数。该函数的原型在头文件string.h中。
在框架窗口类中也说明了一个LOGFONT类型的数据成员,用于接收用户输入的字体信息。该成员在框架窗口类的构造函数中初始化。在鼠标右键消息响应函数中,说明了一个CFontDialog的对象dlg,通过DoModal()调用字体选择公用对话框。由于LOGFONT中不保存字体的颜色信息,所以在退出字体选择对话框后要将用户选用的字体颜色单独保存。
在鼠标左键消息响应函数中,用签名对话框接收用户输入的签名及其倾斜角。然后,与保存在框架窗口类中的当前字体信息一起存入签名数组中。
[例14-5] 为例9-3的吹泡泡程序添加颜色选择对话框,使其可以绘出五颜六色的泡泡。
程 序:在例9-3的程序基础上作如下修改:
1.在程序首部添加文件包含命令:
#include <afxdlgs.h>
2.在框架窗口类声明中添加一个COLORREF类型的数组,存放各泡泡的颜色:
COLORREF m_colorBubble [MAX_BUBBLE];
3.修改鼠标左键消息映射函数,添加使用颜色选择公用对话框的代码:
void CMyWnd::OnLButtonDown ( UINT nFlags,CPoint point )
{
if(m_nBubbleCount < MAX_BUBBLE)
{
m_colorBubble[m_nBubbleCount] = RGB(200,200,200);
CColorDialog dlg(m_colorBubble[m_nBubbleCount]);
if(dlg.DoModal() == IDOK)
m_colorBubble[m_nBubbleCount] = dlg.GetColor();
int r = rand()%50+10;
CRect rect(point.x-r,point.y-r,point.x+r,point.y+r);
m_rectBubble[m_nBubbleCount] = rect;
m_nBubbleCount++;
InvalidateRect(rect,FALSE);
}
}
4.修改OnPaint()成员函数,添加根据泡泡颜色使用画刷的代码:
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
CBrush brushNew,*pbrushOld;
for(int i=0; i<m_nBubbleCount; i++)
{
brushNew.CreateSolidBrush(m_colorBubble[i]);
pbrushOld = dc.SelectObject(&brushNew);
dc.Ellipse(m_rectBubble[i]);
dc.SelectObject(pbrushOld);
brushNew.DeleteObject();
}
}
输入输出:在窗口客户区点击鼠标左键会弹出一颜色选择公用对话框,用户选择泡泡颜色后可在窗口相应位置显示一彩色泡泡。
分 析:在鼠标左键消息响应函数中,在添加泡泡前先将泡泡的颜色初始化为浅灰色。这样,即使用户按Cancel按钮退出颜色选择对话框,泡泡的颜色也已设置好。
在显示泡泡时,由于各个泡泡的颜色均可能不同,因此将建立画刷的工作放在循环中进行。这时要注意在显示完泡泡后要及时销毁原来的画刷(使用CBrush::DeleteObject()画刷)。否则,再次创建画刷时会出现错误。
单元上机练习题目修改例14-4的签名留念程序。用AppWizard生成一个SDI程序框架,并添加对话框资源。然后使用ClassWizard为项目添加对话框类的代码。找到AppWizard自动生成的版权对话框并对其进行编辑,添加自己的版权信息。
为例13-7的七巧板程序添加修改拼板块颜色的功能。
为例11-4的拼图程序添加可用对话框修改拼图参数(拼图块大小)的功能。