上一篇文章学习了Windows应用程序的简单实现,如果你还不了解其流程,请点击下面的链接:
Windows应用程序简介
接下来我们来研究一下MFC的消息映射表是如何实现的。
在MFC中,我们只需要定义我们的消息处理函数,以及在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP中添加对应关系,它是如何实现的呢?下面我们就来看一下吧。
- 定义消息结构体,声明映射函数
//构造消息映射表格
struct MSGMAP_ENTRY {
UINT nMessage; //消息类型
LONG (*pfn)(HWND, UINT, WPARAM, LPARAM); //消息处理函数
};
#define dim(x) (sizeof(x) / sizeof(x[0]))
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
//消息映射函数声明
LONG OnCommand(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LONG OnClose(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LONG OnDestroy(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParamv);
LONG OnLButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LONG OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
在该结构体中,消息类型和消息处理函数一一对应。
- 初始化消息和映射函数对照表
//消息与处理函数对照表
struct MSGMAP_ENTRY _messageEntries[] =
{
WM_PAINT, OnPaint,
WM_COMMAND, OnCommand,
WM_CLOSE, OnClose,
WM_DESTROY, OnDestroy,
WM_LBUTTONDOWN, OnLButtonDown,
} ;
//Command消息与处理函数对照表
struct MSGMAP_ENTRY _commandEntries[] =
{
0,0, //数组长度不能为0,补空值
//IDM_FILEOPEN, OnFileOpen,
//IDM_SAVEAS, OnSaveAs,
} ;
- 重新实现窗口过程
//窗口过程
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
for(int i = 0; i < dim(_messageEntries); i++) {
// 如果不在数组中,则丢给默认处理函数
if(message == _messageEntries[i].nMessage) {
return _messageEntries[i].pfn(hWnd, message, wParam, lParam);
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
- 实现消息映射函数
//同窗口过程,只处理WM_Command消息
LONG OnCommand( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
for(int i = 0; i < dim(_commandEntries); i++) {
if(LOWORD(wParam) == _commandEntries[i].nMessage) {
return _commandEntries[i].pfn(hWnd, message, wParam, lParam);
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
LONG OnClose( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
DestroyWindow(hWnd); //仅关闭窗口,没有关闭进程,并发送另一个消息WM_DESTROY
return TRUE;
}
LONG OnDestroy( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParamv )
{
PostQuitMessage(0); //真正的关闭进程
return TRUE;
}
//省略部分消息映射函数实现
这样设计之后,我们的WndProc和OnCommand函数便一般化了,每当有新的消息需要处理,我们只需要在_messageEntries和_commandEntries中添加消息类型和映射函数即可,且不需要修改WndProc和OnCommand函数。
结合上一篇的注册,创建窗口,就可运行起来了,大家可以一起尝试一下。
以上实现就是MFC的MessageMap雏形,不过MFC包装的更好更复杂。下面我们可以简单看一下MFC是如何实现的。
- 在MFC中实现消息映射
//.h文件
afx_msg void OnPaint();
//.cpp文件
BEGIN_MESSAGE_MAP(ThisClass, BaseClass)
ON_WM_PAINT()
END_MESSAGE_MAP()
- 跟踪BEGIN_MESSAGE_MAP和END_MESSAGE_MAP宏
PTM_WARNING_DISABLE // 告警抑制
// 定义获取消息映射表的函数
const AFX_MSGMAP* theClass::GetMessageMap() const
{
return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap()
{
typedef theClass ThisClass;
typedef baseClass TheBaseClass;
// 添加消息映射的数组
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
ON_WM_PAINT(); // 我们添加的内容,ON_WM_PAINT宏将后续参数转换为AFX_MSGMAP_ENTRY格式,常用消息已经统一,不需要参数
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } // 数组初始化长度不能为0,为防止我们没有添加内容而导致出错而添加0值
};
// 构建子类的消息映射表结构
static const AFX_MSGMAP messageMap =
{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] };
return &messageMap; //返回此类的消息映射表
}
PTM_WARNING_RESTORE
这里我删掉了续行符,并添加了一下换行和缩减,方便大家阅读和理解。
- 接下来我们看一下AFX_MSGMAP_ENTRY结构体及ON_WM_PAINT宏:
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
#define ON_WM_PAINT()
{ WM_PAINT, 0, 0, 0, AfxSig_vv, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnPaint)) },
可以看到,对于系统提供的消息,我们只用添加固定的宏即可,如果是自己添加的控件,则需要添加资源ID和映射函数。
参考文献:
[1]《深入浅出MFC》第二版 华中科技大学出版社 侯俊杰