VC/MFC ABC
1. MFC 简介
Mircrosoft Foundation Classes集成在Visual Studio里面,可以用来快速开发图形应用程序。它以C++类的形式封装了windows API,并且包括:
-
应用程序框架(为减轻程序开发人员的工作量)
-
windows句柄封装类、内建组建
1.2. 主要背景
源自AFX(Application Framework), 生于20世纪80年代末、90年代初。
具体细节:
早在1989年,Microsoft的程序员们开始试图将C++和面向对象的编程概念应用于Windows编程中,以编写出一个可以使Windows编程更加简便的应用程序框架。他们把这个应用程序框架叫做AFX (AFX这个词来源于Application Framework,但奇怪的是这个词组中并没有包含“X”这个字母)。直到今天,AFX小组早已不存在了,AFX这个名称也于1994年初不再使用,但在Visual C++和MFC中,AFX的影子却随处可见,很多全局函数、结构和宏的标识符都被加上了AFX的前缀。
最初的AFX版本在经过一年的艰苦之后诞生,却未能被大多数Windows程序员所接受。AFX的确是经过了精心的规划和编码,并且,它也提供了对Windows API的高度抽象,建立了全新的面向对象的AFX API,但最要命的是AFX API库根本不兼容于现有的Windows API。由此导致的最严重后果是大量的SDK代码无法移植,而程序员将学习两种完全不同的编程方法。
AFX不得不重新做所有的一切,他们所创建的新的应用程序框架是一套扩展的C++类,它封装和映射了Windows API,这就是MFC的前身。过去的AFX小组也变成了MFC小组。最终,MFC的第一个公开版本于1992年3月随Microsoft C/C++ 7.0一起推出。那时距Windows 3.1发布尚有好几个月。在MFC 1.0中还没有文档/视结构,但有类CObject和CArchive。在12个月之后,MFC 2.0随Microsoft新的编程工具Visual C++ 1.0一道出炉。与MFC 1.0一样,MFC 2.0仍是16位的,因为32位的Windows NT 3.1直到1993年7月才问世。在MFC 2.0中,增加了对文档/视结构、OLE 1.0、Windows 3.1公用对话框的支持和消息映射等。在Windows NT 3.1面世一个月以后,Microsoft推出了32版本的Visual C++和MFC 2.1,它实际上是MFC 2.0的Win32接口。
经过近20年的发展,MFC已发展成一个稳定和涵盖极广的C++类库,为成千上万的Win32程序员所使用。MFC库是可扩展的,它和Windows技术的最新发展到目前为止始终是同步的。并且,MFC类库使用了标准的Windows命名约定和编码格式,所以有经验的Windows SDK程序员很容易过渡到MFC。MFC结合了Windows SDK编程概念和面向对象的程序设计技术,从而具有极大灵活性和易用性
1.3 特点
包装了许多win32例程,封装了里面很多函数;
由外部mfcxxx.dll动态库实现主要框架;
统一的函数入口,和windosSDK编程中的函数入口一致:()
程序运行都要有入口函数,在之前的C++教程中都是main函数,而Windows应用程序的入口函数是WinMain函数,MFC程序也是从WinMain函数开始的。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
2. MFC 核心
2.1.消息映射机制(onEventName() ---- EventName)
http://blog.csdn.net/ouyang_linux007/article/details/7638434
2.1.1什么是消息
消息简单的说就是指通过输入设备向程序发出指令要执行某个操作。具体的某个操作是你的一系列代码。称为消息处理函数。
2.1.2 windows SDK对消息的处理
在SDK中消息其实非常容易理解,当窗口建立后便会有一个函数(窗口处理函数)开始执行一个消息循环,我们还可以清楚的看到消息处理的脉络。一个switch case语句就可以搞定,消息循环直到遇到WM_QUIT消息才会结束,其余的消息均被拦截后调用相应的处理函数。
2.1.3 MFC框架中对消息的处理
UpdateWindow(hWindow);
-
{
-
MSG msg;
-
while(GetMessage(&msg,0,0,0))
-
{
-
TranslateMessage(&msg);
-
DispatchMessage(&msg);
-
}
-
return (int)msg.wParam;
-
}
-
}
但在封装了API的MFC中,消息似乎变的有些复杂了,我们看不到熟悉的switch case语句了,取而代之的是一个叫消息映射的东西。为什么MFC要引入消息映射机制,你可以想象一下,在现在的程序开发活动中,你的一个程序是否拥有多个窗体,主窗口就算只有一个,那菜单、工具条、控件这些都是子窗口,那我们需要写多少个switch case,并且还要为每个消息分配一个消息处理函数,这样做是多么的复杂呀。因此MFC采用了一种新的机制。利用一个数组,将窗口消息和相对应的消息处理函数进行映射,你可以理解成这是一个表。这种机制就是消息映射。这张表在窗口基类CWnd定义,派生类的消息映射表如果你没有动作它是空的,也就是说如果你不手工的增加消息处理函数,则当派生窗口接受一个消息时会执行父类的消息处理函数。这样做显然是高效的。
MFC提供的消息结构
同时MFC定义了下面的两个主要结构:
AFX_MSGMAP_ENTRY
struct AFX_MSGMAP_ENTRY{
UINT nMessage; // Windows消息的ID号
UINT nCode; // 控制消息的通知
UINT nID; // Windows控制消息的ID
UINT nLastID; //表示是一个指定范围的消息被映射的范围
UINT nSig; //表示消息的动作标识
AFX_PMSG pfn; // 指向消息处理函数的指针
};
AFX_MSGMAP
struct AFX_MSGMAP{
#ifdef _AFXDLL
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
const AFX_MSGMAP* pBaseMap;
#endif
const AFX_MSGMAP_ENTRY* lpEntries;
};
///AFX_MSGMAP可以得到基类的消息映射入口地址和得到本身的消息映射入口地址。
MFC下一个消息的处理过程是一般是这样的。
1、_AfxCbtFilterHook截获消息(这是一个钩子函数)
2、_AfxCbtFilterHook把窗口过程设定为AfxWndProc。
3、函数AfxWndProc接收Windows操作系统发送的消息。
4、函数AfxWndProc调用函数AfxCallWndProc进行消息处理。
5、函数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。
如何添加自己的消息?
我们已经了解了WINDOW的消息机制,如何加入我们自己的消息呢?好我们来看
一个标准的消息处理程序是这个样子的
在CWnd类中预定义了标准Windows消息(WM_XXXX WM是WINDOW MESSAGE的缩写)的默认处理程序。类库基于消息名命名这些处理程序。例如,WM_PAINT消息的处理程序在CWnd中被声明为:
afx_msg void OnPaint();
afx_msg 关键字通过使这些处理程序区别于其他CWnd成员函数来表明C++ virtual关键字的作用。但是请注意,这些函数实际上并不是虚拟的,而是通过消息映射实现的。我们在本文的一开始便说明了为什么要这样做。
所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。
若要重写基类中定义的处理程序,只需在派生类中定义一个具有相同原型的函数,并创建此处理程序的消息映射项。我们通过ClassWizard可以建立大多数窗口消息或自定义的消息,通过ClassWizard可以自动建立消息映射,和消息处理函数的框架,我们只需要把我们要做的事情填空,添加你要做的事情到处理函数。这个非常简单,就不细说了。但是也许我们需要添加一些ClassWizard不支持的窗口消息或自定义消息,那么就需要我们亲自动手建立消息映射和消息处理的框架,通常步骤如下:
第一步:定义消息。Microsoft推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息。
#define WM_MYMESSAGE (WM_USER + 100)
第二步:实现消息处理函数。该函数使用WPRAM和LPARAM参数并返回LPESULT。
LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
// TODO: 处理用户自定义消息,填空就是要填到这里。
return 0;
}
第三步:在类头文件的AFX_MSG块中说明消息处理函数:
// {{AFX_MSG(CMainFrame)
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
第四步:在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中。
ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )
可以看出,用户自定义的消息和我们通过ClassWizard添加的消息一样,都是利用了ON_MESSAGE宏,建立的消息映射。
其实消息类别可以分成多种,上面说的只是其中之一。有三种主要的消息类别:(以下部分摘自MSDN)
1、Windows消息
此类消息主要包括以前缀WM_开头的消息,WM_COMMAND除外。Windows消息由窗口和视图处理。此类消息往往带有用于确定如何处理消息的参数。
2、控件通知
此类消息包括从控件和其他子窗口发送到其父窗口的WM_COMMAND通知消息。例如,当用户在编辑控件(Edit Control) 中执行可能更改文本的操作后,该编辑控件(Edit Control) 将向其父级发送包含EN_CHANGE控件通知代码的WM_COMMAND消息。该消息的窗口处理程序以某种适当的方式响应此通知消息,例如在控件中检索该文本。
框架像传送其他WM_消息一样传送控件通知消息。但是有一个例外的情况,即当用户单击按钮时由按钮发送的BN_CLICKED控件通知消息。该消息被作为命令消息特别处理,并像其他命令一样传送。
3、命令消息
此类消息包括用户界面对象(菜单、工具栏按钮和快捷键)发出的WM_COMMAND通知消息。框架处理命令的方式与处理其他消息不同,可以使用更多种类的对象处理命令。
Windows消息和控件通知消息由窗口来处理(窗口是从CWnd类派生的类的对象)。包括CFrameWnd、CMDIFrameWnd、CMDIChildWnd、CView、CDialog以及从这些基类派生的您自己的类。这些对象封装了HWND——Windows窗口的句柄。
命令消息可以由范围更广的对象(文档、文档模板以及应用程序对象本身)处理,而不仅仅由窗口和视图处理。当某一命令直接影响到某个特定对象时,应当让该对象处理此命令。例如,“文件”菜单中的“打开”命令在逻辑上与应用程序相关联:该应用程序接收到此命令时会打开指定的文档。因此“打开”命令的处理程序是应用程序类的成员函数。
命令消息我们比较常见的便是菜单项和工具条了,大家可以看到他的消息映射宏和窗口消息不太一样,一般的形式是这样的
ON_COMMAND(id,memberFxn)
第一个参数是命令ID,一个ID号对应一个消息处理,当然你可以让多个ID共用一个处理函数。常见的应用例如:菜单项打开文档的ID和工具条按钮打开文档的ID同时使用一个处理函数,或者直接将它们的ID设成相同的。
还有一种消息叫通知消息。例如树型控件的等一些复杂的控件在单击后需要传递更多的信息,例如光标的位置和当前项的一个结构,所以MFC为控件的每个通知消息也定义了一个宏,它长成了这个样子:
ON_CONTROL(EN_CHANGE,id,memberFxn)
还有很多种消息存在于MFC,宏定义有区别,大家可以触类旁通。
窗口消息有上百个。你可以从MSDN上查到WM_开头的,或者查看CWnd的成员函数,会给你列出很多,别忘了还有很多非窗口消息。雷神无法一一列出,也没有必要。大家查一下就行了。不过对一些常用的、新的控件消息和特殊的通知消息我还是把他们列出几个表,大家做个参考吧。
MFC中处理消息的顺序
1. AfxWndProc()接收消息,寻找消息所属的CWnd对象,然后调用AfxCallWndProc( )。
2. AfxCallWndProc()存储消息(消息标识符和消息参数)供未来参考,然后调用WindowProc( )。
3. WindowProc()发送消息给OnWndMsg( ),如果消息未被处理,则发送给DefWindowproc( )。
4. OnWndMsg()首先按字节对消息进行排序,对于WM_COMMAND消息,调用OnCommand()消息响应函数;对于WM_NOTIFY消息调用OnNotify()消息响应函数。任何被遗漏的消息将是标准消息。OnWndMsg()函数搜索类的消息映像,以找到一个能处理任何窗口消息的处理函数。如果OnWndMsg()函数不能找到这样的处理函数的话,则把消息返回到WindowProc()函数,由它将消息发送给DefWindowProc()函数。
5. OnCommand()查看这是不是一个控件通知(lParam参数不为NULL),如果它是,OnCommand()函数会试图将消息映射到制造通知的控件;如果它不是一个控件通知,或者控件拒绝映射的消息,OnCommand()就会调用OnCmdMsg()函数。
6. OnNotify( )也试图将消息映射到制造通知的控件;如果映射不成功,OnNotify( )就调用相同的OnCmdMsg( )函数。
7. 根据接收消息的类,OnCmdMsg()函数将在一个称为命令传递(Command Routing)的过程中潜在的传递命令消息和控件通知。例如:如果拥有该窗口的类是一个框架类,则命令和控件通知消息也被传递到视图和文档类,并为该类寻找一个消息处理函数。
MFC中创建窗口的顺序
1. PreCreateWindow()是一个重载函数,在窗口被创建前,可以在该重载函数中改变创建参数(可以设置窗口风格等等)。
2. PreSubclassWindow()也是一个重载函数,允许首先子分类一个窗口OnGetMinMaxInfo()为消息响应函数,响应的是WM_GETMINMAXINFO消息,允许设置窗口的最大或者最小尺寸。
3. OnNcCreate()也是一个消息响应函数,响应WM_NCCREATE消息,发送消息以告诉窗口的客户区即将被创建。
4. OnNcCalcSize()也是消息响应函数,响应WM_NCCALCSIZE消息,作用是允许改变窗口客户区大小。
5. OnCreate()也是消息响应函数,响应WM_CREATE消息,发送消息告诉一个窗口已经被创建。
6. OnSize()也是消息响应函数,响应WM_SIZE消息,发送该消息以告诉该窗口大小已经发生变化。
7. OnMove()也是消息响应函数,响应WM_MOVE消息,发送此消息说明窗口在移动。
8. OnChildNotify()为重载函数,作为部分消息映射被调用,告诉父窗口即将被告知一个窗口刚刚被创建。
有时候还可以自己定义一些消息,主要需要使用到下面的一些函数:
SendMessage();
PostMessage();
参考链接: http://www.icodeguru.com/VC&MFC/MFCReference/
2.2.类之间层次关系图
Win32自带了很多控件、组件,提供了几乎所有常用的图形类。为了便于快速选择合适的类类型,需要了解、熟悉常用的组件及其相关父类、子类。
下面的链接给出了所有的MFC类之间的关系:
http://www.icodeguru.com/VC&MFC/MFCReference/
4.参考资料和转自链接
http://www.jizhuomi.com/software/257.html(介绍MFC相对最全面的网页)
http://www.jizhuomi.com/software/147.html
http://blog.csdn.net/huangxy10/article/details/8495930
本文转自存储之厨51CTO博客,原文链接:http://blog.51cto.com/xiamachao/1841689 ,如需转载请自行联系原作者