MFC简介
- MFC
- MFC样式
- MFC应用程序框架
- 单文档应用程序框架
- 工程文件的组成结构
- MFC应用程序框架分析
- SDK应用程序和MFC应用程序运行过程对比
- MFC应用程序框架主要类之间的关系
- MFC消息映射机制概述
- 消息
- 消息映射机制
- Windows消息分类
- 消息映射表
- 添加消息处理函数
- 各种Windows消息的消息处理函数
MFC
MFC全称Microsoft Foundation Classes,也就是微软基础类库。它是VC++的核心,是C++与Windows API的结合,很彻底的用C++封装了Windows SDK(Software Development Kit,软件开发工具包)中的结构和功能,还提供了一个应用程序框架,此应用程序框架为软件开发者完成了一些例行化的工作,比如各种窗口、工具栏、菜单的生成和管理等,不需要开发者再去解决那些很复杂很乏味的难题,比如每个窗口都要使用Windows API注册、生成与管理。这样就大大减少了软件开发者的工作量,提高了开发效率。
MFC样式
利用VS创建MFC Application,在创建的窗口界面设置参数:
设置的类型有:单文档、多文档、对话框和多个顶层文档。对话框类型中没有以下四种风格,其余类型有以下四种界面风格。
-
Visual Studio风格
-
MFC Standard 风格
-
Windows Explorer 风格
-
Office 风格
MFC应用程序框架
单文档应用程序框架
个应用程序都作为一个工程来处理,它包含了头文件、源文件和资源文件等,这些文件通过工程集中管理。工程都是在解决方案管理之下的。一个解决方案可以管理多个工程,可以把解决方案理解为多个有关系或者没有关系的工程的集合。,当新建一个工程时可以选择新建一个解决方案还是加入当前解决方案。
创建MFC程序中注意设置的参数类型:
- 选择应用程序类型,我们看到有四种类型:Single document(单文档)、Multiple documents(多文档)、Dialog based(基于对话框)和Multiple top-level documents。我们选择Single document类型,以生成一个单文档应用程序框架。单文档应用程序运行时是一个单窗口界面。
- 选择程序风格:Visual Studio、MFC Standard、Windows Explorer、Office
- “Use of MFC”有两个选项:Use MFC in a shared DLL(动态链接库方式使用MFC)和Use MFC in a static library(静态库方式使用MFC)。
- 选择Use MFC in a shared DLL时MFC的类会以动态链接库的方式访问,所以我们的应用程序本身就会小些,但是发布应用程序时必须同时添加必要的动态链接库,以便在没有安装VS2010的机子上能够正常运行程序。
- 选择Use MFC in a static library时MFC的类会编译到可执行文件中,所以应用程序的可执行文件要比上种方式大,但可以单独发布,不需另加包含MFC类的库。这里我们使用默认的Use MFC in a shared DLL。点“Next”按钮。
- 其他界面设置等
- 生成的4 个类:
- 一个视图类(CHelloWorldView)
- 一个应用类(CHelloWorldApp)
- 一个文档类(CHelloWorldDoc)
- 一个主框架窗口类(CMainFrame)
工程文件的组成结构
在创建工程的文件夹下看到以解决方案名命名的文件夹,此文件夹中包含了几个文件和一个以工程名命名的子文件夹,这个子文件夹中又包含了若干个文件和一个res文件夹,创建工程时的选项不同,工程文件夹下的文件可能也会有所不同。
如果已经以Debug方式编译链接过程序,则会在解决方案文件夹下和工程子文件夹下各有一个名为“Debug”的文件夹,而如果是Release方式编译则会有名为“Release”的文件夹。这两种编译方式将产生两种不同版本的可执行程序:Debug版本和Release版本。
- Debug版本的可执行文件中包含了用于调试的信息和代码,
- Release版本则没有调试信息,不能进行调试,但可执行文件比较小。
所有文件分为6个部分:解决方案相关文件、工程相关文件、应用程序头文件和源文件、资源文件、预编译头文件和编译链接生成文件。
- 解决方案相关文件
解决方案相关文件包括解决方案文件夹下的.sdf文件、.sln文件、.suo文件和ipch文件夹。
.sdf文件和ipch目录一般占用空间比较大,几十兆甚至上百兆,与智能提示、错误提示、代码恢复和团队本地仓库等相关。如果你觉得不需要则可以设置不生成它们,方法是点击菜单栏Tools->Options,弹出Options对话框,选择左侧面板中Text Editor->C/C+±>Advanced,右侧列表中第一项Disable Database由False改为True就可以了,最后关闭VS2010再删除.sdf文件和ipch目录以后就不会再产生了。但关闭此选项以后也会有很多不便,例如写程序时的智能提示没有了。
.sln文件和.suo文件为MFC自动生成的解决方案文件,它包含当前解决方案中的工程信息,存储解决方案的设置。 - 工程相关文件
工程相关文件包括工程文件夹下的.vcxproj文件和.vcxproj.filters文件。
.vcxproj文件是MFC生成的工程文件,它包含当前工程的设置和工程所包含的文件等信息。.vcxproj.filters文件存放工程的虚拟目录信息,也就是在解决方案浏览器中的目录结构信息。 - 应用程序头文件和源文件
应用程序向导会根据应用程序的类型(单文档、多文档或基于对话框的程序)自动生成一些头文件和源文件,这些文件是工程的主体部分,用于实现主框架、文档、视图等。简单介绍下各个文件:
- HelloWorld.h:应用程序的主头文件。主要包含由CWinAppEx类派生的CHelloWorldApp类的声明,以及CHelloWorldApp类的全局对象theApp的声明。
- HelloWorld.cpp:应用程序的主源文件。主要包含CHelloWorldApp类的实现,CHelloWorldApp类的全局对象theApp的定义等。
- MainFrm.h和MainFrm.cpp:通过这两个文件从CFrameWndEx类派生出CMainFrame类,用于创建主框架、菜单栏、工具栏和状态栏等。
- HelloWorldDoc.h和HelloWorldDoc.cpp:这两个文件从CDocument类派生出文档类CHelloWorldDoc,包含一些用来初始化文档、串行化(保存和装入)文档和调试的成员函数。
- HelloWorldView.h和HelloWorldView.cpp:它们从CView类派生出名为CHelloWorldView的视图类,用来显示和打印文档数据,包含了一些绘图和用于调试的成员函数。
- ClassView.h和ClassView.cpp:由CDockablePane类派生出CClassView类,用于实现应用程序界面左侧面板上的Class View。
- FileView.h和FileView.cpp:由CDockablePane类派生出CFileView类,用于实现应用程序界面左侧面板上的File View。
- OutputWnd.h和OutputWnd.cpp:由CDockablePane类派生出COutputWnd类,用于实现应用程序界面下侧面板Output。
- PropertiesWnd.h和PropertiesWnd.cpp:由CDockablePane类派生出CPropertiesWnd类,用于实现应用程序界面右侧面板Properties。
- ViewTree.h和ViewTree.cpp:由CTreeCtrl类派生出CViewTree类,用于实现出现在ClassView和FileView等中的树视图。
- 资源文件
一般我们使用MFC生成窗口程序都会有对话框、图标、菜单等资源,应用程序向导会生成资源相关文件:res目录、HelloWorld.rc文件和Resource.h文件。
- res目录:工程文件夹下的res目录中含有应用程序默认图标、工具栏使用图标等图标文件。
- HelloWorld.rc:包含默认菜单定义、字符串表和加速键表,指定了默认的About对话框和应用程序默认图标文件等。
- Resource.h:含有各种资源的ID定义。
- 预编译头文件
几乎所有的MFC程序的文件都要包含afxwin.h等文件,如果每次都编译一次则会大大减慢编译速度。所以把常用的MFC头文件都放到了stdafx.h文件中,然后由stdafx.cpp包含stdafx.h文件,编译器对stdafx.cpp只编译一次,并生成编译之后的预编译头HelloWorld.pch,大大提高了编译效率。 - 编译链接生成文件
如果是Debug方式编译,则会在解决方案文件夹和工程文件夹下都生成Debug子文件夹,而如果是Release方式编译则生成Release子文件夹。
工程文件夹下的Debug或Release子文件夹中包含了编译链接时产生的中间文件,解决方案文件夹下的Debug或Release子文件夹中主要包含有应用程序的可执行文件。
MFC应用程序框架分析
SDK应用程序和MFC应用程序运行过程对比
程序运行都要有入口函数,在之前的C++教程中都是main函数,而Windows应用程序的入口函数是WinMain函数,MFC程序也是从WinMain函数开始的。Windows SDK开发程序就是不使用MFC类库,直接用Windows API函数进行软件开发。
- SDK应用程序(windows的桌面应用程序)
#include <windows.h> LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{const static TCHAR appName[] = TEXT("Hello world");WNDCLASSEX myWin;myWin.cbSize = sizeof(myWin);myWin.style = CS_HREDRAW | CS_VREDRAW;myWin.lpfnWndProc = myWndProc;myWin.cbClsExtra = 0;myWin.cbWndExtra = 0;myWin.hInstance = hInstance;myWin.hIcon = 0;myWin.hIconSm = 0;myWin.hCursor = 0;myWin.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);myWin.lpszMenuName = 0;myWin.lpszClassName = appName;//Register if (!RegisterClassEx(&myWin)) return 0;const HWND hWindow = CreateWindow(appName,appName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0,hInstance,0);ShowWindow(hWindow, iCmdShow);UpdateWindow(hWindow);{MSG msg;while (GetMessage(&msg, 0, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}return (int)msg.wParam;}
}LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{if (msg == WM_PAINT){PAINTSTRUCT ps;const HDC hDC = BeginPaint(hWindow, &ps);RECT rect;GetClientRect(hWindow, &rect);DrawText(hDC, TEXT("HELLO WORLD"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);EndPaint(hWindow, &ps);return 0;}else if (msg == WM_DESTROY){PostQuitMessage(0);return 0;}return DefWindowProc(hWindow, msg, wParam, lParam);
}< / windows.h>
上面的程序运行的流程是:进入WinMain函数->初始化WNDCLASSEX,调用RegisterClassEx函数注册窗口类->调用ShowWindow和UpdateWindow函数显示并更新窗口->进入消息循环。关于消息循环再简单说下,Windows应用程序是消息驱动的,系统或用户让应用程序进行某项操作或完成某个任务时会发送消息,进入程序的消息队列,然后消息循环会将消息队列中的消息取出,交予相应的窗口过程处理,此程序的窗口过程函数就是myWndProc函数,窗口过程函数处理完消息就完成了某项操作或任务。本例是要显示“HELLO WORLD”字符串,UpdateWindow函数会发送WM_PAINT消息,但是此消息不经过消息队列而是直接送到窗口过程处理,在窗口过程函数中最终绘制了“HELLO WORLD”字符串。
- MFC应用程序
- 定义全局对象theApp:。调用CWinApp和CHelloWorldApp的构造函数后,进入WinMain函数(位于appmodul.cpp中)。
- 在TCHAR.h中,有此定义:#define _tWinMain WinMain,所以这里的_tWinMain就是WinMain函数。它调用了AfxWinMain函数(位于WinMain.cpp中)。
- InitInstance中的ProcessShellCommand函数又调用了CMainFrame的LoadFrame函数注册并创建了窗口,执行完ProcessShellCommand函数以后,调用了m_pMainWnd的ShowWindow和UpdateWindow函数显示并更新框架窗口。与SDK程序类似。
- 接下来该是消息循环了,上面的AfxWinMain函数中调用了pThread的Run函数(位于THRDCORE.cpp中),在Run中包含了消息循环。PumpMessage中通过调用GetMessage、TranslateMessage、DispatchMessage等建立了消息循环并投递消息。
到此,通过对比可以发现,MFC应用程序的运行流程与SDK程序是类似的,都是先进行一些初始化过程,再注册并创建窗口,然后显示、更新窗口,最后进入消息循环,消息都由窗口过程函数处理。
MFC应用程序框架主要类之间的关系
- CHelloWorldApp类处理消息,将收到的消息分发给相应的对象。
- CMainFrame是视图CHelloWorldView的父窗口。
- 视图CHelloWorldView就显示在CMainFrame的客户区中。视图类CHelloWorldView用来显示文档类。
- CHelloWorldDoc中的数据,并根据对视图类的操作修改文档类的数据。一个视图类只能跟一个文档类相联系,而一个文档类可以跟多个视图类相联系。
MFC消息映射机制概述
Windows应用程序是消息驱动的。在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息,通过对消息的处理实现相应的操作。比较典型的过程是,用户操作窗口,然后有消息产生,送给窗口的消息处理函数处理,对用户的操作做出响应。
消息
窗口消息一般由三个部分组成:
- 一个无符号整数,是消息值;
- 消息附带的WPARAM类型的参数;
- 消息附带的LPARAM类型的参数。其实我们一般所说的消息是狭义上的消息值,也就是一个无符号整数,经常被定义为宏。
消息映射机制
MFC使用一种消息映射机制来处理消息,在应用程序框架中的表现就是一个消息与消息处理函数一一对应的消息映射表,以及消息处理函数的声明和实现等代码。当窗口接收到消息时,会到消息映射表中查找该消息对应的消息处理函数,然后由消息处理函数进行相应的处理。SDK编程时需要在窗口过程中一一判断消息值进行相应的处理,相比之下MFC的消息映射机制要方便好用的多。
Windows消息分类
Windows消息分为系统消息和用户自定义消息。Windows系统消息有三种:
- 标准Windows消息。除WM_COMMAND外以WM_开头的消息是标准消息。例如,WM_CREATE、WM_CLOSE。
- 命令消息。消息名为WM_COMMAND,消息中附带了标识符ID来区分是来自哪个菜单、工具栏按钮或加速键的消息。
- 通知消息。通知消息一般由列表框等子窗口发送给父窗口,消息名也是WM_COMMAND,其中附带了控件通知码来区分控件。
- CWnd的派生类都可以接收到标准Windows消息、通知消息和命令消息。命令消息还可以由文档类等接收。
- 用户自定义消息是实际上就是用户定义一个宏作为消息,此宏的值应该大于等于WM_USER,然后此宏就可以跟系统消息一样使用,窗口类中可以定义它的处理函数。
消息映射表
除了一些没有基类的类或CObject的直接派生类外,其他的类都可以自动生成消息映射表。CMainFrame为例。消息映射表如下:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx)ON_WM_CREATE()ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize)ON_REGISTERED_MESSAGE(AFX_WM_CREATETOOLBAR, &CMainFrame::OnToolbarCreateNew)ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook)ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnUpdateApplicationLook)ON_WM_SETTINGCHANGE()
END_MESSAGE_MAP()
- 在BEGIN_MESSAG_MAP和END_MESSAGE_MAP之间的内容成为消息映射入口项。消息映射除了在CMainFrame的实现文件中添加消息映射表外,在类的定义文件MainFrm.h中还会添加一个宏调用:DECLARE_MESSAGE_MAP()
- 一般这个宏调用写在类定义的结尾处。
添加消息处理函数
不管是自动还是手动添加都有三个步骤:
- 在类定义中加入消息处理函数的函数声明,注意要以afx_msg打头。例如MainFrm.h中WM_CREATE的消息处理函数的函数声明:afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);。
- 在类的消息映射表中添加该消息的消息映射入口项。例如WM_CREATE的消息映射入口项:ON_WM_CREATE()。
- 在类实现中添加消息处理函数的函数实现。例如,MainFrm.cpp中WM_CREATE的消息处理函数的实现:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{......
}
通过以上三个步骤以后,WM_CREATE等消息就可以在窗口类中被消息处理函数处理了。
各种Windows消息的消息处理函数
- 标准Windows消息的消息处理函数都与WM_CREATE消息类似。
- 命令消息的消息映射入口项形式如:ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize),消息为ID_VIEW_CUSTOMIZE,消息处理函数为OnViewCustomize。
- 如果想要使用某个处理函数批量处理某些命令消息,则可以像CMainFrame消息映射表中的ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook)一样添加消息映射入口项,这样值在ID_VIEW_APPLOOK_WIN_2000到ID_VIEW_APPLOOK_WINDOWS_7之间的菜单项等的命令消息都由CMainFrame的OnApplicationLook函数处理。函数原型为afx_msg void OnApplicationLook(UINT id);,参数id为用户操作的菜单项等的ID。
- 在操作列表框等控件时往往会给父窗口发送WM_NOTIFY通知消息。WM_NOTIFY消息的wParam参数为发送通知消息的控件的ID,lParam参数指向一个结构体,可能是NMHDR结构体,也可能是第一个元素为NMHDR结构体变量的其他结构体。NMHDR结构体的定义如下(仅作了解):
Typedef sturct tagNMHDR{HWND hwndFrom;UINT idFrom;UINT code;
} NMHDR;
hwndFrom为发送通知消息控件的句柄,idFrom为控件ID,code为要处理的通知消息的通知码,例如NM_CLICK。
通知消息的消息映射入口项形式如:
ON_NOTIFY(wNotifyCode,id,memberFxn)
wNotifyCode为要处理的通知消息通知码,例如:NM_CLICK。id为控件标识ID。MemberFxn为此消息的处理函数。
通知消息的处理函数的原型为:
afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result);
- 如果需要使用用户自定义消息,首先要定义消息宏,如:#define WM_UPDATE_WND (WM_USER+1),再到消息映射表中添加消息映射入口项:ON_MESSAGE(WM_UPDATE_WND, &CMainFrame::OnUpdateWnd),然后在MainFrm.h中添加消息处理函数的函数声明:afx_msg LRESULT OnUpdateWnd(WPARAM wParam, LPARAM lParam);,最后在MainFrm.cpp中实现此函数。