一、MFC消息映射机制
在前面Win32Class工程中,我们进行了Win32环境下的“消息映射”。其实,通过前面的过程,我们已经不知不觉的接触到了MFC消息映射的核心。
MFC环境下的消息映射,其原理和我们讲解过的Win32下的消息映射是类似的。简单地讲,就是让程序员指定要某个MFC类(有消息处理能力的类)处理某个消息。MFC提供了工具 ClassWizard来帮助实现消息映射,在处理消息的类中添加一些有关消息映射的内容和处理消息的成员函数。程序员负责编写消息处理函数的代码,实现所希望的功能。
可以通过如下的3个重要的宏来实现MFC消息映射,这些宏功能强大,其实现相对也比较复杂。这里只要求我们会用就可以。稍后我们会用其实际代码替换这些宏,就能理解了。
l DECLARE_MESSAGE_MAP:初始化消息映射表,实际上是给所在类添加几个用于消息处理的静态成员变量和静态或虚拟函数。
l BEGIN_MESSAE_MAP:开始消息映射。
l END_MESSAE_MAP:结束消息映射。
其他常见的、用于实现MFC消息的宏还有:
l 前缀为“ON_WM_”的宏: 用于 Windows消息的宏(不带参数)如:ON_WM_PAINT()把消息WM_PAINT映射到OnPaint函数。
l ON_COMMAND宏:通过参数指定命令 ID和消息处理函数。
如ON_COMMAND(ID_EDIT_PASTE, OnEditPaste),其中第二个参数OnEditPaste的原型说明为:void CView::OnEditPaste()。
l ON_UPDATE_COMMAND_UI宏:用于更新菜单的状态。
l 前缀为”ON_”控件通知消息宏:这类宏可能带有三个参数,如 ON_CONTROL,就需要指定控制窗口ID,通知码和消息处理函数;也可能带有两个参数,如具体处理特定通知消息的宏ON_BN_CLICKED、ON_LBN_DBLCLK、ON_CBN_EDITCHANGE等,需要指定控制窗口ID和消息处理函数。
l 实现用户自定义消息的ON_MESSAGE宏:ON_MESSAGE(message, memberFxn)。第一个参数为消息号,第二个参数为消息处理函数。
二、手工进行消息映射
上面是使用类向导工具自动进行的消息映射,下面我们通过手工的方式来实现同样的功能,以加深对消息映射的理解。
手工映射的主要步骤是:
l 在.h头文件中:声明消息响应函数、声明消息映射。如:
afx_msg void OnPaint(); //声明消息响应函数
DECLARE_MESSAGE_MAP() //声明消息映射
l 在.cpp文件中:进行消息映射。如:
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd) //消息映射开始
ON_WM_PAINT() //WM_PAINT消息映射宏
END_MESSAGE_MAP() //结束消息映射
详见下例 (工程1Win32MFC):
使用VC6.0建立一个Win32应用程序空工程Win32MFC,按Alt + F7调出工程设置界面,或者点击菜单-->Project-->Settings-->General-->Microsoft Foundation Classes-->选择Use MFC in a Shared DLL,使该工程支持MFC类库。
如果使用VS2008,则还需要另外进行如下的设置:将项目属性à链接器à高级à入口点的值设置为WinMainCRTStartup
然后添加如下2个文件:(其中用到了MFC中的CWinApp和CFrameWnd类,后面会讲,这里只是为了理解消息映射,先不用理会这2个类)
头文件:
//Win32MFC.h
#include <afxwin.h>
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
class CMyWnd : public CFrameWnd
{
public:
CMyWnd();
protected:
afx_msg void OnPaint(); //手工声明消息响应函数
DECLARE_MESSAGE_MAP() //手工声明消息映射
};
实现文件:
//Win32MFC.cpp
#include "Win32MFC.h"
CMyApp theApp;
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd) //消息映射开始
ON_WM_PAINT() //WM_PAINT消息映射宏
END_MESSAGE_MAP() //结束消息映射
BOOL CMyApp::InitInstance()
{
m_pMainWnd = new CMyWnd;
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
CMyWnd::CMyWnd()
{
Create(NULL, "Win32 MFC Application");
}
//手工定义消息响应函数OnPaint(),并添加函数处理代码
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
dc.DrawText("大地震,海啸—天灾;核危机,利比亚战火—人祸",
-1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
}
编译运行。
以上就是最简单的MFC工程,我们即将学习的MFC单文档工程和对话框工程等都是在这种简单MFC的基础上扩展而来的。
三、翻译DECLARE_MESSAGE_MAP()等宏,发现和我们前面讲过的Win32Class几乎是一样的。
见工程“2Win32MFC_翻译宏”
四、第一个MFC程序
1、建立一个SDI程序“3SDI”
2、介绍SDI程序中的App类,Frame类,View类,Doc类,以及他们之间的相互关系。
3、WinMain咋不见了?简单的跟踪执行,理解SDI执行过程。
4、在View类的OnDraw中输出”Hello world!”
5、事件、消息、虚函数
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
ON_WM_CREATE()
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
ON_WM_KEYDOWN()
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
ON_WM_LBUTTONDOWN()
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) //static
afx_msg void OnAppAbout();
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
virtual void OnDraw(CDC* pDC); // overridden to draw this view
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
ON_WM_ERASEBKGND()
BOOL CSDIView::OnEraseBkgnd(CDC* pDC)
{
static BOOL bDone = FALSE;
if (bDone == FALSE)
{
bDone = TRUE;
HBRUSH hBrush = (HBRUSH)::GetStockObject(BLACK_BRUSH);
::SetClassLong(m_hWnd, GCL_HBRBACKGROUND, (LONG)hBrush);
//联想到::SetWindowLong()函数,作用是?
}
return CView::OnEraseBkgnd(pDC);
}
在前面的课程中,我们处理消息时,除了要判断主消息值uMsg外,总是要判断wParam和lParam这2个附加消息参数,以完整的表达一个消息的全部含义。但上面的OnKeyDown和OnLButtonDown中,我们没有见到wParam和lParam的身影,这是因为MFC已经智能的帮助我们将这2个参数转换成了相应消息函数的参数了,转换后的参数直接表明了参数的具体含义,更加人性化了。
PreCreateWindow和OnCreate的不同在于:OnCreate是消息WM_CREATE的响应函数,是由::CreateWindow函数触发的消息。而PreCreateWindow不对应任何消息,它只是一个虚函数,是在其父类中的::CreateWindow函数之前调用的,是为了给程序员一个在CreateWindow执行之前改变窗口外观的机会。OnCreate和PreCreateWindow函数的参数中都涉及到了一个CREATESTRUCT结构,其作用相同。PreCreateWindow先执行,OnCreate后执行。
由此可以推断,虚函数OnDraw也是类似的作用,是在消息WM_PAINT的响应函数OnPaint中进行调用的。实际情况也是如此,可以跟踪MFC的核心代码可以证实这点。
//文件VIEWCORE.CPP代码片段:
void CView::OnPaint()
{
// standard paint routine
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}
6、预编译头文件stdafx.h,对MFC源代码的引用就在这里。
#include <afxwin.h> // MFC核心和标准组
#include <afxext.h> // MFC扩展
#include <afxdisp.h> // MFC自动化类
#include <afxdtctl.h> // MFC支持的IE4的通用控件
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC支持的Windows通用控件
#endif // _AFX_NO_AFXCMN_SUPPORT
五、Windows消息分类
Windows消息可以分为以下4类:
1、标准消息:即以WM_开头的消息,但WM_COMMAND除外。从CWnd派生的所有类均可接收该类消息。
2、命令消息:即以WM_COMMAND 形式出现的消息,如菜单、快捷键、工具条按钮消息都是命令消息。从CCmdTarget派生的类可以接收该类消息。(重温第一天第一个作业Win32自动创建Hello world的代码,其中就有WM_COMMAND的用法实例)
3、通告消息:控件产生的消息,如按钮、编辑框产生的消息等。从CCmdTarget派生的类可以接收该类消息。
4、用户自定义消息:为了跟系统消息区分,系统专门划分一段消息编号区间,让用户定义自己的消息。
其中,菜单命令的传递路径如下:
消息传递:框架类-->视类
消息处理:视类-->文档类-->视类-->框架-->应用程序类
完成一个自定义消息的完整步骤如下:
l 定义消息值:#define UM_SEND_DATA WM_USER + 1
l 定义消息响应函数:afx_msg void OnSendData();
l 消息映射:ON_MESSAGE(UM_SEND_DATA, OnSendData)
l 实现消息响应函数,编写其中的代码
l 在需要的地方调用:SendMessage(hWnd, UM_SEND_DATA);
六、MFC类向导工具class wizard用法介绍
可以使用类向导(class wizard)工具来建立消息映射、添加类成员变量和成员函数等。一般类向导生成的代码最好不要手动修改。如对一个菜单项使用类向导进行消息映射:
1、点击右键选择class wizard,如下图:
2、选择合适的类,要进行映射的资源ID,和要进行映射的消息种类,使他们高亮显示如下图。然后点击Add function按钮,系统为给对应的消息处理函数起名。如果ID名称起的合理,该函数名也会相应合理。类向导会自动生成该函数的声明和定义。当然,函数体一般情况是空的,需要自己写代码。
3、生成的消息映射、函数声明和函数实现分别如下:
消息映射:
函数声明:
函数实现:
作业:
1、 在工程“1Win32MFC”的基础上,手工添加WM_CLOSE消息,实现退出前询问是否真的要退出的功能。进一步理解MFC消息映射。
2、 预习性作业:将前面的实现时钟程序作业的功能移植到工程“1Win32MFC”中。该作业涉及到了MFC中的2个类CPaintDC, CWnd,后面会讲解,大家通过该作业进行预习。