本MFC教程使用VS2022实现
MFC基本概念
微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是一个微软公司提供的类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
MFC把Windows SDK API函数包装成了几百个类,MFC给Windows操作系统提供了面向对象的接口,支持可重用性、自包含性以及其他OPP原则。MFC通过编写类来封装窗口、对话框以及其他对象,引入某些关键的虚函数(覆盖这些虚函数可以改变派生类的功能)来完成,并且MFC设计者使类库带来的总开销降到了最低。
编写MFC应用程序
我们将从一个空项目一步步的编写一个MFC应用程序
项目的创建









程序编写流程
MFC是由C++编写的用于创建和管理窗口相关功能的应用程序,因此创建MFC应用程序需要创建应用程序类和窗口类,并使应用程序对象关联窗口对象。
1.包含mfc头文件afxwin.h,仅需该头文件
2.自定义一个继承于CWinApp应用程序类的MyApp应用程序类,并实例化该应用程序类对象MyApp app(有且只有一个)
3.自定义一个继承于CFrameWnd窗口类(框架类)的MyFrame窗口类,并定义该窗口类的构造函数利用Create创建一个窗口
4.定义MyApp类虚函数:程序的入口函数InitInstance()。该函数用于创建窗口类MyFrame对象,并利用ShowWindow显示窗口,UpdateWindow更新窗口,m_pMainWnd保存窗口类对象指针,以此使得窗口可以正常运行
注意:ShowWindow和UpdateWindow属于CWnd类函数,m_pMainWnd为CWinThread类成员变量
代码的编写
框架类对象就是窗口对象
m_pMainWnd是CWinApp的父类CWinThread的一个数据成员,其保存了指向应用程序的主窗口的指针。此时m_pMainWnd = frame,使得该MFC应用程序能够管理我们创建的窗口frame
代码分析
CFrameWnd 框架窗口类
CFrameWnd是 从CWnd(窗口基类)派生出来的,用于模仿框架窗口行为。我们可以把框架窗口作为顶层窗口看待,它是应用程序与外部世界的主要接口。
如果想要创建一个窗口,可以在此类中调用CWnd::Create或CWnd::CreateEX函数:
cpp
virtual BOOL Create
(
LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID,
CCreateContext* pContext = NULL
);
1.Create接收的8个参数后6个有默认值定义,因此我们在上文使用的Create只写了两个参数
2.lpszClassName指定了窗口基于WNDCLASS类的名称,上文代码将其设定为NULL将创建一个基于已注册的WNDCLASS类的默认框架窗口。
3.lpszWindowName参数指定将在窗口的标题栏出现的文字。
CWinApp应用程序类
MFC应用程序的核心就是基于CWinApp类的应用程序对象。CWinApp提供了消息循环来检索消息并将消息调度给应用程序窗口。它还包括可被覆盖的、用来自定义应用程序行为的主要虚函数。
一个MFC程序可以有且仅有一个应用程序对象,此对象必须声明为在全局范围内有效,以便它在程序开始时即在内存中被实例化。
InitInstance函数
CWinApp::InitInstance函数是一个虚函数,其默认操作仅有一条语句:
cpp
BOOL MyApp::InitInstance()//程序入口地址
{
return TRUE;
}
InitInstance的目的是给应用程序提供一个自身初始化的机会,其返回值决定了框架接下来要执行的内容,如果返回FALSE将关闭应用程序,如果初始化正常返回TRUE以便允许程序继续进行。此函数是MFC应用程序的入口。
m_pMainWnd 成员变量
在CWinApp中有一个名为CWinThread::m_pMainWnd的成员变量。 该变量是一个CWnd类型的指针,它保存了应用程序框架窗口对象的指针。也就是指向CFramWnd对象(框架窗口类对象)的指针。
消息映射
消息映射是一个将消息和成员函数相互关联的表。比如,框架窗口接收到一个鼠标左击消息:WM_LBUTTONDOWN,MFC将搜索该窗口的消息映射,如果存在一个处理WM_LBUTTONDOWN的处理程序,然后就调用其对应的函数OnLButtonDown。
下面是是将消息映射添加到一个类中所做的全部工作:
1.在.h文件中的框架类,声明消息映射宏DECLARE_MESSAGE_MAP,作为将 Windows 消息与类的成员函数相关联的表。
2.在.cpp文件中利用BEGIN_MESSAGE_MAP和END_MESSAGE_MAP表消息映射的范围,即分界宏。在分界宏中写入要定义的消息宏,该宏用于找到消息对应的处理函数。
3.在.h文件中写入消息处理函数的原型声明
4.在.cpp文件中实现该函数
每个消息映射入口都对应一个唯一的消息处理函数,从msdn帮助手册查询
简单的理解:DECLARE_MESSAGE_MAP就是一个函数表,BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间的范围表示函数表的范围,表中的每一项都是函数的入口,每个入口都对应特定的函数

上图中ON_WM_LBUTTONDOWN表鼠标左键按下的消息
3.消息处理函数 在.h文件类中声明,.cpp文件定义:

该OnLButtonDown函数可在我们在窗 口中点击函数时,弹出显示鼠标左键
我们也可以把OnLButtonDown重新定义
void MyFrame :: OnLButtonDown(UINT, CPoint point)
{
//TCHAR buf[1024];
//wsprintf(buf, TEXT("x = %d, y = %d"), point.x, point.y);
//MessageBox(buf);
CString str;
str.Format(TEXT("x = %d, y = %d", point.x, point.y));
MessageBox(str);
}
该代码两种方式都可以在点击窗口时弹窗显示坐标
我们同样也可以添加一个键盘的消息映射,此处不再演示
此时,我们便完成了一个简单的mfc程序,该程序也具有一个功能
帮助文档的使用
MSDN的使用




VC++之MFC类库中文手册
通过此手册查找,必须加上成员所属的类作用域(类名::),否则,无法查找到匹配的关键字。


实际上,我们在使用VS进行编程遇到不熟悉的函数时,直接按F1查看帮助即可
向导生成基于单文档MFC应用程序
在我们实际编程中,我们并不需要从0创建一个mfc应用程序,VS为我们提供好了现成的模板使用,这也叫做工程文件。接下来我们将演示如何利用VS向导创建一个单文档MFC标准类型应用程序。



MFC为我们生成了如上四个类:App,MainFrame,View,Doc
APP继承于CWinApp:应用程序类
MainFrame继承于CFrameWnd:框架类,类比相框。在此类中写代码显示东西是显示不出来的,被覆盖掉了。这与我们前文学习的在框架类中弹窗显示坐标产生了区别
View继承于CView:视图类,用于显示内容,类比相片。在此类中写代码显示东西才能显示出来
Doc继承于CDocument:文档类,用于存储和管理数据
点击完成以后,我们便创建成功了,运行结果如下:

类视图

在mfc开发中,我们通常不再使用解决方案资源管理器,而是使用类视图。在类视图中可以找到MFC中的类
CAboutDlg:对话框类,由MFC向导自行生成,用于显示应用程序的 "关于" 信息,如下图所示,没啥用


双击相应的类名便可跳转到类声明所在的.h文件
下方框为类中成员函数,双击即可跳转到相应定义部分
文档/视图结构体系
MFC应用程序框架结构的基石是文档/视图体系结构,它定义了一种程序结构,这种结构依靠文档对象保存应用程序的数据,并依靠视图对象控制视图中显示的数据,把数据本身与它的显示分离开。
数据的存储和加载由文档类来完成,数据的显示和修改则由视类来完成。 MFC在类CDocument和CView中为稳定视图提供了基础结构。CWinApp、CFrameWnd和其他类与CDocument和CView合作,把所有的片段连在了一起。
CView类也派生于CWnd类,框架窗口是视图窗口的一个父窗口。主框架窗口(CFrameWnd)是整个应用程序外框所包括的部分,即图中粗框以内的内容,而视类窗口只是主框架中空白的地方。

消息处理的添加
在主框架类中添加WM_LBUTTONDOWN消息的响应函数,具体操作如下:



从类视图中找到CMainFrame(继承自CFrameWnd),选择此类然后从属性面板中找到消息按钮

在消息列表中找到WM_LBUTTONDOWN消息,添加。

我们同样也可以轻松删除,但删除的形式是注释而非代码删除
工程文件增加几处改变:

第一处:在框架类头文件中添加了鼠标左键消息函数的函数声明

第二处:在框架类cpp文件中添加了消息映射宏

第三处:在框架列cpp文件中添加了处理鼠标左键消息的函数定义
接下来我们在此OnLButtonDown函数中添加一个MessageBox消息,鼠标左键按下弹出一个提示框

执行程序,我们会惊奇的发现程序并未如我们所愿弹出消息框。
因为,框架窗口是视窗口的父窗口,那么视类窗口就应该始终覆盖在框架类窗口之上。就好比框架窗口是一面墙,视类窗口就是墙纸,它始终挡在这面墙前边。也就是说,所有操作,包括鼠标单击、鼠标移动等操作都只能有视类窗口捕获。因此当我们把上述的操作全部删除以后,在View类中重现,就会发现,此时点击鼠标左键会弹窗
MFC框架中一些重要的函数
InitInstance函数

应用程序类的一个虚函数,MFC应用程序的入口。
PreCreateWindow函数

当框架调用CreateEx函数创建窗口时,会首先调用PreCreateWindow函数。
通过修改传递给PreCreateWindow的结构体类型参数CREATESTRUCT,应用程序可以更改用于创建窗口的属性。在产生窗口之前让程序员有机会修改窗口的外观。
最后再调用CreateWindowEx函数完成窗口的创建。
OnCreate和Create

OnCreate是一个消息响应函数,是响应WM_CREATE消息的一个函数,而WM_CREATE消息是由Create函数调用的。一个窗口创建(Create)之后,会向操作系统发送WM_CREATE消息,OnCreate()函数主要是用来响应此消息的。
OnCreate与Create的区别:
1.Create()负责注册并产生窗口,像动态创建控件中的Create()一样,窗口创建成功之后会向操作系统发送WM_CREATE消息。
2.OnCreate()不产生窗口,只是在窗口显示前设置窗口的属性如风格、位置等。
3.OnCreate()是消息WM_CREATE的消息响应函数。
OnDraw和OnPaint
OnPaint()是CWnd的类成员,负责响应WM_PAINT消息。
OnDraw()是CView的成员函数,没有响应消息的功能。
OnPaint是WM_PAINT消息的消息处理函数,OnDraw是OnPaint中调用的一部分。一般来说,用户自己的绘图代码应放在OnDraw中。
当视图变得无效时(包括大小的改变,移动,被遮盖等等),Windows发送WM_PAINT消息。该视图的OnPaint 处理函数通过创建CPaintDC类的DC对象与当前窗口关联并做一些准备工作。然后OnPaint 调用视图的OnDraw成员函数,将DC对象传递给OnDraw成员函数,让该函数使用DC进行具体绘画操作。
通常我们不必编写OnPaint处理函数,这是因为在View类里添加了消息处理OnPaint()时,OnPaint()就会覆盖掉OnDraw()。

我们在这两个函数中,分别在同一个位置显示不同的文案,观察程序运行的结果

拓展知识点
MFC中后缀名为Ex的函数都是扩展函数。
在MFC中,以Afx为前缀的函数都是全局函数,可以在程序的任何地方调用它们
注意:消息处理函数中的afx_msg只是一个标识符