01 章 用向导创建文档窗口
MFC 教程是小雅最怕写的内容,除了水平问题之外,还有一点就是,MFC 将
许多程序封装起来,使众多程序员在编程时,即使编出了程序,也不知究竟为什么
这样就可以了。这一现象使小雅想到当今的 Java 程序员,许多人在开发外包项目时,
因为整个 FrameWork 已经建成,自己只是担当其中一部分功能,出了 BUG 之后,
找原因非常困难。
在 SDK 教程中,小雅将各种功能进行详细解说,而 MFC 教程中,只是用例子
来讲解 MFC 的原理,学会原理之后,就会感到 MFC 比 SDK 编程省了许多事。小
雅在 SDK 教程中,为了讲明 API 功能,用的方法都是 C 语言的方法,而 MFC 用的
全是 C++的内容,因此,学习 MFC 必须对 C++有较深的了解,不会 C 语言、C++
语言而能够学会 MFC 的人只能是吹牛。
小雅的“C 语言教程”和“C++语言教程”都是按照标准 C/C++来写的,在 Unix、
Linux、Windows 都适用,这是很重要的基础。SDK 教程主要是让初学者明白
WindowsAPI 的功能,以及程序的实现方法,基本上是“C 语言 + API”,而 MFC 基
本上是“C++ + API”。SDK 编程和 MFC 是可以相互转化,相互调用的,因为 MFC
封装了绝大部分 API,但不是所有的 API 函数。注意:SDK 并不是只能用 C 语言来
写,用 C++一样编程,小雅在作教程时为了把内容说清楚,同时兼顾不会 C++的人
员才使用 C 语言。
对于初学者,小雅建议在 C/C++的基础上用 MFC 来编程,一是省去了很多编
程,二是用上了 C++的面向对象编程的思想。当今要找一个 C++的项目来一边做一
边学是很困难的。小雅自己也一直想找一个 C++的工作而未能如愿。通过学习 MFC
再反过来加强一下学的不是很深的 C++,不是很好的一个方法吗?不过,MFC 和
SDK 与 C/C++不同点在消息事件处理方面,初学者要学会这一内容。
关于“”有“托管”和“非托管”二种编程方式,小雅的 SDK 教程和 MFC 教
程全部是“非托管”编程,也就是传统编程。微软自己说“托管”方式是为传统编程人
员的过渡,既然是过渡的产物,小雅也就不去浪费时间精力去写这种教程了,如果
想用“托管”编程,还不如直接用 C#或 。
一、创建一个单文档窗口
MFC 编程很大程度上要借助于集成环境中的各种工具,下面我们也通过工具来
生成一个基本的窗口程序,再通过这个程序来了解 MFC 的各主要的类、基本类,学
会图标、菜单、资源视图、类视图、解决方案资源管理器、对象浏览器的使用。创
建一个单文档窗口的步骤是:
用菜单的“文件”→“新建”→“项目”打开“新建项目”对话框。
在“新建项目”对话框中分别选择设置①类型为“Visual C++ 项目”、①
“MFC”、①模板为“MFC 应用程序”、①名称为“Test”①位置为
“C:\MFC”。其中①和①可自由设定。最后按“确定”按钮进入“MFC 应用
程序向导”对话框。
在“MFC 应用程序向导”对话框中,①选择菜单“应用程序类型”、①选择
“单文档”选项。最后按“完成”按钮结束。
二、生成后的文件的作用
生成的文件的作用可以在 便可知道。当然,后面章节中小雅将对每
一部分详细剖析。
=============================================================
===================
MICROSOFT 基础类库: Test 项目概述
=============================================================
==================
应用程序向导已为您创建了此 Test 应用程序。此应用程序不仅介绍了使用 Microsoft 基
础类的基本知识,而且是编写应用程序的起点。
此文件包含组成 Test 应用程序的每个文件的内容摘要。
这是使用“应用程序向导”生成的 VC++ 项目的主项目文件。
它包含有关生成文件的 Visual C++ 版本的信息,以及有关用“应用程序向导”所选择
的平台、配置和项目功能的信息。
这是应用程序的主头文件。 它包含其他项目特定的头文件(包括 ),并声
明 CTestApp 应用程序类。
这是包含应用程序类 CTestApp 的主应用程序源文件。
这是程序使用的所有 Microsoft Windows 资源的列表。它包含存储在 RES 子目录中的图
标、位图和光标。 可直接在 Microsoft Visual C++ 中编辑此文件。 项目资源包含在
2052 中。
res\
这是一个图标文件,用作应用程序的图标。 此图标包含在主资源文件 中。
res\
此文件包含不由 Microsoft Visual C++ 编辑的资源。 应将所有不能由资源编辑器编辑
的
资源放在此文件中。
/////////////////////////////////////////////////////////////
////////////////
对于主框架窗口:
项目将包含标准的 MFC 界面。
,
这些文件包含框架类 CMainFrame,该类派生自 CFrameWnd 并控制所有的 SDI 框架功能。
res\
此位图文件用于创建工具栏的平铺图像。初始工具栏和状态栏在 CMainFrame 类中构造。
使用资源编辑器编辑此工具栏位图,并更新 中的 IDR_MAINFRAME TOOLBAR 数组
以添加工具栏按钮。
/////////////////////////////////////////////////////////////
////////////////
应用程序向导将创建一种文档类型和一个视图:
、 - 文档
这些文件包含 CTestDoc 类。 编辑这些文件以添加特殊的文档数据并实现文件的保存和
加载(通过 CTestDoc::Serialize)。
、 - 文档视图
这些文件包含 CTestView 类。
CTestView 对象用于查看 CTestDoc 对象。
/////////////////////////////////////////////////////////////
////////////////
其他功能:
ActiveX 控件
应用程序支持使用 ActiveX 控件。
打印支持和打印预览支持
应用程序向导已生成了一些代码,通过从 MFC 库调用 CView 类中的成员函数来处理打印、
打印设置和打印预览命令。
/////////////////////////////////////////////////////////////
////////////////
其他标准文件:
、
这些文件用于生成名为 的预编译头文件(PCH)和名为 的预编译类
型
文件。
这是标准头文件,它定义新资源 ID。
Microsoft Visual C++ 将读取并更新此文件。
/////////////////////////////////////////////////////////////
////////////////
其他说明:
应用程序向导使用“TODO:” 来指示应添加或自定义的源代码部分。
如果应用程序在共享 DLL 中使用 MFC,且应用程序使用的语言不是操作系统的当前语言,则
需
要从 Microsoft Visual C++ 光盘上 Win\System 目录下将相应的本地化资源
复制到计算机的 system 或 system32 目录下,并将其重命名为 。 (“XXX”代
表
语言缩写。 例如, 包含翻译成德语的资源。) 如果不这样做,应用程序的
某
些 UI 元素将保留为操作系统的语言。
02 章 用向导创建对话框窗口
一、应用程序的窗口种类
Windows 应用程序的窗口类型主要有二种即文档类型(Document)和对话框类型
(Dialog),通常说的表单类型(Form)是介于这 2 种类型之间,主要是对话框类型。
、C#等主要就是这种表单类型的窗口,实现起来相当简单。
文档类型的应用程序的界面,大部分区域用于文字或画图等读写操作,常见的
有 Word、Excel、写字板等。文档类型的窗口又分单文档和多文档 2 种类型。所谓
多文档类型就是在一个窗口框架内有多个子窗口,常见的有 Word、Excel、当前编
写 VC++的 IDE 等。单文档类型当然就不可能有多个子窗口了,常见的有附件中的
写字板、画图等。文档类型的窗口的编程相对对话框来说要复杂得多,是我们学习
的主要对象。
对话框类型的界面中使用了很多控件,输入输出主要是针对控件,常见的有计
算器、音量控制等。编程相对比较简单,另外公共对话框也是这种类型,不过不需
要任何编程,因为这些是操作系统的东西,只调用就可以了。对话框类型是日常我
们编程用的最多的,也就是表单类型。另外,对话框类型也可以象文档类型一样进
行文字、画图等处理,而文档类型窗口中同样也可以使用控件。
对于文档窗口一般可以改变大小,并且有滚动条,背景多数为白色。而对话框
窗口的大小一般是固定的,背景一般是淡灰色,没有滚动条,通常有 OK、Cancel
等按钮。要注意,对话框窗口有模态和非模态 2 种类型。所谓模态对话框就是当前
对话框不关闭,就不能切换到调用该对话框的父窗口;而所谓非模态窗口就是不关
闭当前对话框,也同样可以切换到调用该对话框的父窗口。绝大多数对话框都是模
态方式,非模态方式比较少,例如 集成环境中按 Ctrl+F 弹出的“查找”对话框
就是非模态方式。
二、创建一个基于对话框的窗口
在上一章生成单文档应用程序的最后一步中,选择“基于对话框”后再按“完成”
按钮。
三、阅读原程序的方法
下面,我们要学会用用四种工具来查看或修改生成好的程序。①解决方案资源
管理器(以前称为项目管理器);①类视图;①资源视图;①属性。
“解决方案资源管理器”中显示项目中的所有文件,如果要
删除一个既存的程序,或添加一个新程序或头文件等,都是利
用这个工具来完成。
“资源视图”中包含菜单、鼠标光标、对话框、图标、工
具条、字符串常量表、版本信息表、快捷键、HTML 等,在
“解决方案资源管理器”中鼠标双击后缀为 rc 的文件便可打开“资源视图”。添加、修
改、删除“资源”都是在“资源视图”中操作。
这些“资源”文件其实都是文本文件,内容基本上是资源的定义(图标等除外),
完全可以直接手动编写,但手动编写既容易出错,也不方便。另外,对话框的设计
还需要和“属性”工具一起使用,这些都是微软 Visual Studio 集成工具的基本使用常
识,MFC 教程中不再详细叙述。
“类视图”是学习 MFC 最重要的工具,小雅认为初学者要学会或者说习惯这个
工具的使用。一个很简单的程序,经过 MFC 生成之后,便是一个“庞大”的程序,初
学者如果一行一行地去读,往往头晕目眩,越看越烦。而通过“类视图”从整体上来
分析,能很好地把握整个的设计思路,通过“类视图”能很准确地定位到你想编程的“位
置”。例如,添加类成员变量或成员函数,消息处理等,利用工具,多个地方的程序
将被同进更新。
通过“类视图”还可以很方便地看到父类的全部信息,从而了解当前的类还有哪
些功能可以使用。同时可以看到想相关类的全部信息。这比 Java 的集成环境(如:
Websphere 等)不知要方便多少倍。
“属性”使用该窗口查看和更改位于编辑器和设计器中的选定对象的设计时属性
及事件。也可以使用“属性”窗口编辑和查看文件、项目及解决方案属性。 “属性”窗
口显示编辑字段的不同类型,这取决于特定属性的需要。这些编辑字段包括编辑框、
下拉列表以及到自定义编辑器对话框的链接。属性以灰色显示且是只读的。
元素 图形 说明
对象名
列出当前选定的一个或多个对象。只有活动编辑器或设计器中的对象可见。
当选择多个对象时,只出现所有选定对象的通用属性。
按分类
顺序
按类别列出选定对象的所有属性及属性值。可以折叠类别以减少可见属性数。
展开或折叠类别时,可以在类别名左边看到加号 (+) 或减号 (-)。类别按字
母顺序列出。
按字母
顺序
按字母顺序对选定对象的所有设计时属性和事件排序。若要编辑可用的属性,
请在它右边的单元格中单击并输入更改内容。
属性 显示对象的属性。很多对象的事件也可以使用“属性”窗口来查看。
事件
显示对象的事件。仅当窗体或控件设计器在一个 Visual C# 项目或 C++ 项
目托管扩展的上下文中处于活动状态时,此“属性”窗口工具栏控件才可用。
VC消息
列出所有 Windows 消息。为选定类的消息添加或删除指定处理函数。此“属
性”窗口工具栏控件仅当“类视图”在 Visual C++ 项目的上下文中为活动
窗口时才可用。
VC重写
为选定类列出所有虚函数并允许添加或删除重写函数。此“属性”窗口工具
栏控件仅当“类视图”在 Visual C++ 项目的上下文中为活动窗口时才可用。
属性页
显示选定项的“属性页”对话框。“属性页”显示“属性”窗口中的可用属
性的子集、同集或超集。使用该按钮可查看和编辑与项目的活动配置相关的
属性。
“说明”
窗格
N/A
显示属性的属性类型和简短说明。可以使用快捷菜单上的“说明”命令关闭
和打开属性的说明。
03 章 初始大小、标题和图标的改变
一、直接修改现有图标
在上一章生成的基础上,打开资源视图,选择唯一的图标“IDR_MAINFRAME”
双击,然后将图标“擦除”,画一个自己喜欢的图形并保存。“Ctrl+F5”重新编译生成
运行,你将会和小雅一样惊奇地发现,运行出来的程序的图标并没有改变。
小雅是个喜欢钻牛角尖的人,将源程序前前后后地研究了很长时间,未得其解,
只好不耻下问。身边的人问了、论坛上也发贴了,仍未找到答案。不免有几句牢骚,
中国许多人在招聘时个个都说自己能力很强,问他会不会 VC++,他会举出很多做
过 VC++的项目,进了单位之后,让他编一个很简单的程序,费了九牛二虎之力生
成一堆废代码。如果给他一个 sample 的话,倒能发挥发挥。
这种现象决不是小雅有意要攻击哪个人,在国内如此,在国外更是如此。而许
多编程人员,明明技术很差,却个个自以为了不起,一点没有想学的念头。不仅如
此,许多人都象穷疯了一样,都想通过一两次生意、或一两年奋斗来“暴发”一下。
这种好大喜功的心理在中国很普遍,国家领导也将经济增长的数目作为政绩,而不
注重实实在在的技术、生产能力,靠出卖廉价劳动带来的经济增长,这有什么好吹
的。
那么原因出在哪儿呢,原因出在 MFC 生成的图标上,我们自己新建一个图标,
无论怎样画大小也只有 1K 左右,而 MFC 生成的图标竟有 22K 之多。小雅将 MFC
生成的图标一个象素一个象素地在新建的图标上画了一遍,大小也只有 1K 左右,
新画的图标在 Windows 的资源管理器里面,无论大图标还是小图标,画错了的地方
都能立即显示出来,而 MFC 生成的图标,无论怎样修改,在 Windows 的资源管理
器里面,无论大图标还是小图标都没有任何改变。
小雅知识有限,对图象方面了解不多,记得看过一篇文章,介绍如何通过调色
板控制 BMP,使其颜色不能被修改。图标也应该有类似的功能,小雅既然不会,就
介绍一下更换图标的笨方法。这个笨办法对第 1 章和第 2 章生成的程序都适用。
1. 在资源视图中“Icon”下选择“IDR_MAINFRAME”图标,然后用“Delete”键
删除。
2. 在 Windows 的资源管理器里面,打开当前项目下的“res”子目录,删除相
应的图标文件。
3. 在资源视图中“~.rc”下,用右键菜单的“插入资源(A)...”,然后在对话框
中选择“Icon”新建一个图标。
4. 在属性工具中,将新建的图标的 ID 改为“IDR_MAINFRAME”(默认为
“IDI_ICON1”)。
5. 在属性工具中,将新建的图标的 Filename 改为上面删除的文件名。注意
应保存在“res”子目录下。
如果你事先已经有一个自己的图标,那最简单了,在 VC++中什么也不需要修
改,直接在资源管理器里面将那个 22K 的图标文件删除,然后用你自己的图标换成
被删除的文件名。
二、改变窗口的标题和初始大小
在资源视图中打开“String Table”,IDR_MAINFRAME 的值为
“test2\n\ntest2\n\n\”,将之改为:“yaer\n 小雅
\ntest2\n\n\”,窗口的标题就改完了。
初始大小的更改,对于第 2 章生成的基于对话框的程序来说很简单,只要在资
源视图中选择主窗口后打开,直接用鼠标改变大小就可以了。
对于第 1 章生成的文档类程序不忙于修改,先用类视图来看一下 MFC 生成的
单文档程序和对话框程序的类有什么相同和不同。
单文档 对话框
CAboutDlg “关于”对话框类。无须修改。 CAboutDlg
“关于”对话框类。无
须修改。
CTest2App
主应用程序类,调用框架类、文档
类、视图类。无须修改。
CTest3App
主应用程序类,调用主
对话框类。无须修改。
CMainFrame
主框架类,即主窗口。很少需要修
改。
CTest2Doc
文档类,即存放数据主要在这儿编
写代码。
CTest2view
视图类,即用于屏幕显示的类,文
字显示和画图主要在这儿编写代
码。
Ctest3Dlg主对话框类。
修改窗口的初始大小在 CMainFrame 类的 PreCreateWindow()成员函数中,通过
修改 CREATESTRUCT cs 来修改窗口类或样式。
typedef struct tagCREATESTRUCTW {
LPVOID lpCreateParams; //指向创建窗口用的数据的指针
HANDLE hInstance; //窗口的实例句柄
HMENU hMenu; //窗口的菜单句柄
HWND hwndParent; //窗口句柄
int cy; //窗口的高度
int cx; //窗口的宽度
int y; //窗口的左上角的 y座标
int x; //窗口的左上角的 x座标
LONG style; //窗口的类型
LPCSTR lpszName; //指向窗口的名称的指针
LPCSTR lpszClass; //指向窗口类的名称的指针
DWORD dwExStyle; //窗口的扩展类型
} CREATESTRUCTW, *PCREATESTRUCTW, *LPCREATESTRUCTW;
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此处通过修改 CREATESTRUCT cs 来修改窗口类或样式
= 220;
= 150;
return TRUE;
}
三、框架类、文档类、视图类的关系
框架类主要处理窗口的类型、边框、位置、大小以及子窗口(对于多文档来说)
等内容,工具条、状态条也是这个类的成员变量。如果要添加成员变量时,初始化
要在 CMainFrame()构造函数中添加代码;OnCreate()成员函数是消息 WM_CREATE
的处理函数,这里面根据需要修改代码(一般不需要);PreCreateWindow()成员函数
是创建主窗口前的设置,调整窗口位置、大小 、类型等都是在这儿修改代码。
文档类主要用来处理数据。例如画一个图,图的各节点座标、颜色、线条的粗
细等都有文档类来处理,文件的保存和打开等当然也是文档类来处理。视图类主要
处理数据的显示,例如:文字在窗口中显示、键盘鼠标操作等都是由视图类来处理。
视图类不断地从文档类取数据,不断地对文档类传送数据。当文档类数据较多时,
视图类显示的数据只是文档类的一部分。
四、主应用程序类做什么
主应用程序类是程序的入口,其它各个类都是在这儿集成的。这个类除构造函
数之外,唯一要注意的就是初始化函数。这个类一般不修改。
单文档 对话框
Ctest2App() 构造函数 Ctest2App() 构造函数
InitInstance() 初始化成员函数 InitInstance()
初始化成员
函数
OnAppAbout()
由菜单发出的消息
处理函数
单文档 对话框
BOOL Ctest2App::InitInstance()
{
InitCommonControls();
CWinApp::InitInstance();
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
SetRegistryKey(_T("应用程序向导生成的本地应
用程序"));
//加载标准 INI 文件选项(包括 MRU)
LoadStdProfileSettings(4);
//上面内容不用太关注,以下是重点
//注册应用程序的文档模板。文档模
板
//将用作文档、框架窗口和视图之间
的连接
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(Ctest2Doc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(Ctest2View));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
BOOL Ctest3App::InitInstance()
{
InitCommonControls();
CWinApp::InitInstance();
AfxEnableControlContainer();
SetRegistryKey(_T("应用程序向导生成的本地
应用程序"));
//上面内容不用太关注,以下是重
点
Ctest3Dlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = ();
//下面的程序注意:对话框窗口已
经被关闭
if (nResponse == IDOK)
{
// TODO: 在此放置处理何时用“确
定”来关闭
//对话框的代码
}
else if (nResponse == IDCANCEL)
//分析标准外壳命令、DDE、打开文件
操作的命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
//调度在命令行中指定的命令。如果
//用 /RegServer、/Register、
/Unregserver
//或 /Unregister 启动应用程序,
则返回 FALSE。
if (!ProcessShellCommand(cmdInfo))
return FALSE;
//唯一的一个窗口已初始化,因此显
示它并对其进行更新
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
//仅当存在后缀时才调用
DragAcceptFiles,
//在 SDI 应用程序中,这应在
ProcessShellCommand
//之后发生
return TRUE;
}
{
// TODO: 在此放置处理何时用“取
消”来关闭
//对话框的代码
}
// 由于对话框已关闭,所以将返回
FALSE 以便退出应用程序,
// 而不是启动应用程序的消息
泵。
return FALSE;
}
04 章 程序的执行流程
一、WinMain()函数在哪儿
因为看不到 WinMain()函数,许多初学者不知道程序究竟从哪儿开始,编程从
哪儿下手,编写的代码何时运行等等,另外,还有很多看不懂的语句,最大的困难
恐怕还是 MFC 的编程思想不太理解。各位既然选择小雅的教程来学习 MFC,小雅
就有责任将每一个你不明白的地方剖析清楚,因为小雅自己也曾为此而烦恼了几年。
不过,饭还得一口一口地吃,最好的办法是先照葫芦画瓢,从容易的入手,再一步
一步地深入。
MFC 把千篇一律的 WinMain()函数写在① 中,在编译完后链接时才组
装到你的 exe 程序中。运行时,这个 WinMain()函数调用 MFC 的全局函数
AfxWinMain(),这个全局函数 AfxWinMain()是写在① 中的。在
AfxWinMain()函数中做三件事,①注册窗口类、①调用应用程序类的初始化函数
InitInstance()、①调用应用程序类的 Run()函数(实际上是父类的父类 CWinThread 的
Run()成员函数)。
上面这个 Run()函数中包含消息循环,而 InitInstance()函数又将框架类、文档类、
视图类结合在一起,于是所有的类全部集成起来了。编程的重点在文档类和视图类,
什么样的消息,执行什么样的处理函数,这样就有效地将数据和控制分离开来。
二、消息处理
消息处理的回调函数在 中的 CWnd::WindowProc(...),由上面讲的
Run()函数调用,然后在主应用程序类、框架类、文档类、视图类里面接受这些消息
并处理。问题是:某一消息究竟在哪个类里面处理?
与窗口有关的消息(即以“WM_”开头的消息),只能在框架类和视图类中处理;
来自菜单或工具条的命令事件,则框架类、视图类、主应用程序类、文档类全部可
以处理,究竟在哪个类中处理,根据具体情况再决定在哪个类中处理。这其中有优
先级别高低的问题,一个类接受到命令消息时,先传给比自己级别高的类,如果未
处理再自己处理。如果自己也不处理,则传给比自己级别低的类。
这个优先级别是:视图类>文档类>文档模板类>框架类>主应用程序类。例如,
框架类收到“编辑”菜单的“Clear All”命令消息时,首先传给视图类,如果视图类处理
便结束,否则就传给文档类。如果文档类处理便结束,否则就传给文档模板类。如
果文档模板类处理则结束,否则就回到了框架类。如果框架类处理
则结束,否则就传给主应用程序类。如果主应用程序类还不处理,
就回到 MFC 缺省的处理程序中。
三、添加、删除消息处理
添加一个消息处理,一般要改动三个地方,以
WM_LBUTTONDOWN 消息为例,①在视图类的定义()
中添加一个成员函数,①在视图类()中添加一条消息映射,①在视图类
()中实现消息处理的成员函数。
// : Ctest2View 类的接口
......(省略)
// 生成的消息映射函数
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
};
......(省略)
// : Ctest2View 类的实现
//
#include ""
#include ""
#include ""
#include ""
#include ".\"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// Ctest2View
IMPLEMENT_DYNCREATE(Ctest2View, CView)
BEGIN_MESSAGE_MAP(Ctest2View, CView)
// 标准打印命令
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
......(省略)
// Ctest2View 消息处理程序
void Ctest2View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
AfxMessageBox("劝学网 MB_OK);
CView::OnLButtonDown(nFlags, point);
}
上面虽然很简单,但不建议手动添加,应该在“类视图”中选中“Ctest2View”,再
在“属性”工具中选择“WM_LBUTTONDOWN”消息,同时选上“OnLButtonDown”后
回车就可以了。
用同样方法在“属性”工具中删除“WM_LBUTTONDOWN”消息时,前面添加的
三处代码将被用“//”注释在那儿,成了“垃圾”代码。这时最好手动删除一下这三处“垃
圾”代码。
四、程序的入口
学过 C++语言或 Java 语言的人都明白,类相当于自定义的数据类型,如果没有
实例,自身是不会运行的。那么,上面一直在解说这 4 个类,当前生成的 5 个.cpp
文件中,WinMain 启动后究竟先运行什么语句,后运行什么语句?
要明白 MFC 的细节,除了 C++的基础之外,最好学一点 SDK 编程,了解 Win32
编程中的基本内容。小雅在 SDK 教程中的第 1 章到第 3 章的内容请参考。MFC 生
成的 4 个类中,主应用程序类是从 CWinApp 类继承的,作用相当于 WinMain 程序;
CMainFrame 是从 CFrameWnd 类继承的,其作用相当于 WndProc 程序。也就是说,
消息首先由框架类接受,然后按优先顺序传给其它的类。文档类和视图类由消息来
驱动(视图类的 PreCreateWindow()除外)。
主应用程序类的程序 中,跳过消息映射以及类的构造函数、成员函数
后,实际只剩下 1 条语句,即“Ctest2App theApp;”,这便是整个应用程序的总入口。
MFC 的 的 WinMain()函数调用 中的“Ctest2App theApp;”。theApp
是类的实例,自然也是必然要调用主应用程序类的构造函数,包括父类的构造函数,
之后再调用 MFC 的 中的 AfxWinMain()函数,由 AfxWinMain()函数调
用主应用程序类的 InitInstance()成员函数。
在主应用程序类的 InitInstance()成员函数中,定义了框架类、文档类、视图类的实
例,从而间接地调用了框架类的 PreCreateWindow()和 OnCreate()成员函数,由
OnCreate()函数再调用视图类的 PreCreateWindow()成员函数。然后再回到主应用程
序类的 InitInstance()成员函数中,执行“m_pMainWnd->ShowWindow(SW_SHOW);”
和“m_pMainWnd->UpdateWindow();”。
05 章 MessageBox 和 TRACE
一、公共对话框 MessageBox
前面章节都是让人脑细胞高度紧张,这一章放松一下。对于公共对话框
MessageBox 大家并不陌生,在 MFC 的 CWnd 类中封装了一个 MessageBox()成员函
数,但 MSDN 并不推荐使用,一般用 AfxMessageBox()全局函数,任何地方都可使
用。
int AfxMessageBox( int AFXAPI AfxMessageBox(
LPCTSTR lpszText, UINT nIDPrompt, //要表示的字符
串
UINT nType = MB_OK, UINT nType = MB_OK, //画面形状(按
钮、模式、图标、缺省值 4个值组合使用)
UINT nIDHelp = 0 UINT nIDHelp = (UINT) -1 //帮助 ID,缺
省为 0
); );
返回值 说明
0 不能正常表示
IDYES 被按下
IDNO 被按下
IDOK 被按下
IDCANCEL 被按下或【ESC】被按下
IDABORT 被按下
IDIGNORE 被按下
IDRETRY 被按下
显示按钮 说明
ID_ABORTRETRYIGNORE
ID_OK
ID_OKCANCEL
ID_RETRYCANCEL
ID_YESNO
ID_YESNOCANCEL
模式 说明
ID_APPMODAL
模态(缺省值)即不关闭就不能回到父窗
口,但可以切换到其它应用程序。
ID_SYSTEMMODAL 系统模态即不关闭就不能回到任何窗口。
ID_TASKMODAL 特殊用途而备用。
显示图标 说明
ID_ICONEXCLAMATION
感叹符号
ID_ICONINFORMATION
情报符号
ID_ICONquestion
问号
ID_ICONSTOP
停止符号
缺省按钮 说明
ID_DEFBUTTON1 第一个探针为缺省按钮。
ID_DEFBUTTON2 第二个探针为缺省按钮。
ID_DEFBUTTON3 第三个探针为缺省按钮。
二、MFC 特有的规则
类型:MFC 将 C++的关键字用宏定义成以下内容。
MFC类型 意思 C++类型
BOOL,BOOLEAN 布尔型 boolean
BYTE 1字节数值型 char
WORD 2字节数值型 short
DWORD 4字节数值型 int
UINT 无符号整型 unsigned int
VOID void型 void
LPDWORD DWORD的指针型 int*
LPCSTR 常量字符串 const char*
LPSTR 字符串 char*
LPCVOID 常量 void指针 const void*
LPVOID void指针 void*
常量:MFC 将 C++的常量用宏定义成以下内容。
MFC常量 意思 C++常量
NULL 空指针 0
TRUE 布尔值:真 true
FALSE 布尔值:假 false
命名规则:MFC 有以下命名规则。
1. 类名:以大写的 C 开头,单词的首字母大写,单词之间不用下划线。
2. 成员函数和全局函数:以单词的首字母大写,单词之间不用下划线。全
局函数前再加 Afx
3. 成员变量:匈牙利命名法,以“m_”开头,单词的首字母大写。
4. 宏、类型名:全部使用大写。
三、用 TRACE 宏调试
可以在程序的任何位置插入 TRACE()宏函数来调试程序,TRACE()宏函数只在
DEBUG 状态有效,即按<F5>运行时有效,<Ctrl + F5>时无效。另外,断点也可以
设置条件。下面例子是一个没有消息循环的 MFC 程序(控制台程序)。断点的条件
是“i>5”。
//
: 定义控
制台应用
程序的入
口点。
//
#include ""
#include ""
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
_tprintf(_T("致命错误: MFC 初始化失败\n"));
nRetCode = 1;
}
else
{
// TODO: 在此处为应用程序的行为编写代码。
for (int i=0; i<10; i++) {
TRACE("i = [%d]\n", i);
//按钮:[是(Y)][否(N)][取消],图标为:[i],
//模式:不能切换到其它窗口,缺省:[否(N)]
AfxMessageBox("这是小雅的 MFC教程。" ,
MB_YESNOCANCEL|MB_SYSTEMMODAL|MB_ICONINFORMATION|MB_DEFBUTTON2);
printf("劝学网:测试 AfxMessageBox功能。\n");
}
}
return nRetCode;
}
06 章 字符串类 CString
C 语言的字符串即字符数组,在编程时时时要当心是否分配了足够的空间。用
MFC 中的 CString 来处理字符串,那些容易出错的麻烦事一概不存在,使用非常方
便。
一、WinMain()函数在哪儿
MFC 把千篇一律的 WinMain()函数写在① 中,在编译完后链接时才组
装到你的 exe 程序中。运行时,这个 WinMain()函数调用 MFC 的全局函数
AfxWinMain(),这个全局函数 AfxWinMain()是写在① 中的。在
AfxWinMain()函数中做三件事,①注册窗口类、①调用应用程序类的初始化函数
InitInstance()、①调用应用程序类的 Run()函数(实际上是父类的父类 CWinThread 的
Run()成员函数)。
上面这个 Run()函数中包含消息循环,而 InitInstance()函数又将框架类、文档类、
视图类结合在一起,于是所有的类全部集成起来了。编程的重点在文档类和视图类,
什么样的消息,执行什么样的处理函数,这样就有效地将数据和控制分离开来。
二、消息处理
消息处理的回调函数在 中的 CWnd::WindowProc(...),由上面讲的
Run()函数调用,然后在主应用程序类、框架类、文档类、视图类里面接受这些消息
并处理。问题是:某一消息究竟在哪个类里面处理?
与窗口有关的消息(即以“WM_”开头的消息),只能在框架类和视图类中处理;
来自菜单或工具条的命令事件,则框架类、视图类、主应用程序类、文档类全部可
以处理,究竟在哪个类中处理,根据具体情况再决定在哪个类中处理。这其中有优
先级别高低的问题,一个类接受到命令消息时,先传给比自己级别高的类,如果未
处理再自己处理。如果自己也不处理,则传给比自己级别低的类。
这个优先级别是:视图类>文档类>文档模板类>框架类>主应用程序类。例如,
框架类收到“编辑”菜单的“Clear All”命令消息时,首先传给视图类,如果视图类处理
便结束,否则就传给文档类。如果文档类处理便结束,否则就传给文档模板类。如
果文档模板类处理则结束,否则就回到了框架类。如果框架类处理则结束,否则就
传给主应用程序类。如果主应用程序类还不处理,就回到 MFC 缺省的处理程序中。
三、添加、删除消息处理
添加一个消息处理,一般要
改动三个地方,以
WM_LBUTTONDOWN 消息为
例,①在视图类的定义
()中添加一个成员函
数,①在视图类()中
添加一条消息映射,①在视图类()中实现消息处理的成员函数。
// : Ctest2View 类的接口
......(省略)
// 生成的消息映射函数
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
};
......(省略)
// : Ctest2View 类的实现
//
#include ""
#include ""
#include ""
#include ""
#include ".\"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// Ctest2View
IMPLEMENT_DYNCREATE(Ctest2View, CView)
BEGIN_MESSAGE_MAP(Ctest2View, CView)
// 标准打印命令
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
......(省略)
// Ctest2View 消息处理程序
void Ctest2View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
AfxMessageBox("劝学网 MB_OK);
CView::OnLButtonDown(nFlags, point);
}
上面虽然很简单,但不建议手动添加,应该在“类视图”中选中“Ctest2View”,再
在“属性”工具中选择“WM_LBUTTONDOWN”消息,同时选上“OnLButtonDown”后
回车就可以了。
用同样方法在“属性”工具中删除“WM_LBUTTONDOWN”消息时,前面添加的
三处代码将被用“//”注释在那儿,成了“垃圾”代码。这时最好手动删除一下这三处“垃
圾”代码。
四、MFC 特有的规则
类型:MFC 将 C++的关键字用宏定义成以下内容。
MFC类型 意思 C++类型
BOOL,BOOLEAN 布尔型 boolean
BYTE 1字节数值型 char
WORD 2字节数值型 short
DWORD 4字节数值型 int
UINT 无符号整型 unsigned int
VOID void型 void
LPDWORD DWORD的指针型 int*
LPCSTR 常量字符串 const char*
LPSTR 字符串 char*
LPCVOID 常量 void指针 const void*
LPVOID void指针 void*
常量:MFC 将 C++的常量用宏定义成以下内容。
MFC常量 意思 C++常量
NULL 空指针 0
TRUE 布尔值:真 true
FALSE 布尔值:假 false
命名规则:MFC 有以下命名规则。
1. 类名:以大写的 C 开头,单词的首字母大写,单词之间不用下划线。
2. 成员函数和全局函数:以单词的首字母大写,单词之间不用下划线。全
局函数前再加 Afx
3. 成员变量:匈牙利命名法,以“m_”开头,单词的首字母大写。
4. 宏、类型名:全部使用大写。