MFC消息机制
作者:Mrzhu007
日期:2018-06-02
博客地址:金色世界
说明
为了以后方便了解。将目前了解的MFC消息映射记录下来方便以后查看
传统的SDK编程
Windows操作系统是基于消息的事件驱动模式。消息是win32应用程序运行的源泉。下面为传统的SDK编程中,消息处理。
include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); //窗口消息处理使用的回调函数
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;
MSG msg ;
WNDCLAS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ; // 视窗类别样式
wndclass.lpfnWndProc = WndProc ; // 消息处理的回调函数
wndclass.cbClsExtra = 0 ; // 预留空间
wndclass.cbWndExtra = 0 ; // 预留空间
wndclass.hInstance = hInstance ; // 主函数参数,有操作系统分配的句柄。指当前进程
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; // 应用程序图标
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; // 鼠标
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; // 指定窗口背景画刷。这里使用白色画刷
wndclass.lpszMenuNam = NULL ; // 这里指定窗体的功能菜单。
wndclass.lpszClassName = szAppName ; // 窗体类名 这是即指“HelloWin”
if (!RegisterClass (&wndclass)) // 注册窗体类别
{
MessageBox ( NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
// 窗体建立函数
hwnd = CreateWindow( szAppName, // window class name
TEXT ("The Hello Program"), // window caption
WS_OVERLAPPEDWINDOW, // window style 窗体风格
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL) ; // creation parameters
// 窗体显示,根据iCmdShow来确定如何显示窗口,最大化,最小化还是一般化
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
// 进入消息循环 GetMessage收到WM_QUIT消息时返回0,其余非零
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ; // 消息翻译(主要处理一些键盘消息)
DispatchMessage (&msg) ; // 消息分发进入WndProc函数进行消息处理
}
return msg.wParam ;
}
// 消息处理函数
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
{
case WM_CREATE:
PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ; // 传给默认的消息处理函数
}
// 重点消息结构体的定义
typedef struct tagMSG
{
HWND hwnd; // 接收消息的窗体
UINT message; // 消息类型 在下面讲 WM_ 等
WPARAM wParam; // 32位信息参数,根据消息类型进行填充
LPARAM lParam; // 32位信息参数,同上
DWORD time; // 信息放入消息队列的时间
POINT pt; // 信息放入消息队列的鼠标坐标
}
消息可分为序列化消息和非序列化消息
序列化消息:可以理解为外部呼叫程序。如:键盘消息(WM_CHAR)、鼠标消息(WM_KEYDOWN、WM_KEYUP、WM_MOUSEMOVE)等
非序列化消息:可以理解为程序自己呼叫自己。如:CreateWindow时发送WM_CREATE,UpdateWindow时发送WM_PAINT
运行后如下:
消息分类
种类大体上分为三种消息:窗口消息、命令消息、控件通知消息
窗口消息:指操作系统和控制其他窗口的窗口所使用的消息。
命令消息:用来处理从一个窗口发生到另一个窗口的用户请求。
控件通知消息:一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的控件
如按钮、列表框、组合框、编辑框等。当用户与控件窗口交互的时候控件通知消息就会从控件窗口发送到它的主窗口。
按钮控件
BN_CLICKED 用户单击了按钮
BN_DISABLE 按钮被禁止
BN_DOUBLECLICKED 双击
BN_HILITE 加亮了按钮
BN_PAINT 按钮需要重绘
BN_UNHILITE 取消加亮
组合框控件
CBN_CLOSEUP 列表框被关闭
CBN_DBLCLK 用户双击了一个字符串
CBN_DROPDOWN 列表框被拉出
CBN_EDITCHANGE 修改编辑框文本
CBN_EDITUPDATE 文本更新
CBN_ERRSPACE 内存不足
CBN_KILLFOCUS 组合框失去输入焦点
CBN_SELCHANGE 选择了一项
CBN_SETFOCUS 获得输入焦点
编辑框控件
EN_CHANGE 文件已更新
EN_VSCROLL 用户点击了垂直滚动条消息
...
列表框控件
LBN_DBLCLK 用户双击了一项
LBN_KILLFOCUS 列表框失去焦点
...
0x0000 空消息
指WM_NULL
0x0001–0x0087 窗口消息
WM_CREATE、WM_DESTROY、WM_MOVE、WM_SIZEWAIT...
0x00A0–0x00A9 非客户区消息
WM_NCMOUSEMOVE、WM_NCLBUTTONDOWN、WM_NCLBUTTONUP...
0x0100–0x0108 键盘消息
WM_KEYDOWN、WM_KEYUP、WM_CHAR...
0x0111–0x0126 菜单消息
WM_COMMAND、WMSYSCOMMAND、WM_TIMER、WM_HSCROLL...
0x0132–0x0138 颜色控制消息
WM_CTLCOLORMSGBOX、WM_CTLCOLOREDIT、WM_CTLCOLORBTN...
0x0200–0x020A 鼠标消息
WM_MOUSEMOVE、WM_LBUTTONDOWN、WM_LBUTTONUP...
0x0211–0x0213 菜单循环消息
WM_ENTERMENULOOP、WM_EXITMENULOOP
0x0220–0x0230 多文档消息
WM_MDICREATE、WM_MDIACTIVATE、WM_MDIDESTROY
- 0x0400 WM_USER
指应用程序自定义私有消息 - 0x8000 WM_APP
MFC消息机制
对于MFC框架来建议看侯捷先生的一本书《深入浅出MFC》。可先看基础部分
- MFC程序的初始化过程
- RTTI执行时期类型识别
- Dynamic Creation动态生成
- Perisistece 永续留存
- Message Mapping 消息映射
- Message Routing 消息绕行
由前两节的基础。再根据深入浅出MFC中的消息映射。一起来看看MFC是怎么处理消息映射
在MFC程序中我们经常看到构建MFC消息映射的宏如下:
DECLARE_MESSAGE_MAP
struct AFX_MSGMAP
{
AFX_MSGMAP* pBaseMessageMap;
AFX_MSGMAP_ENTRY* lpEntries;
};
struct AFX_MSGMAP_ENTRY // MFC 4.0 format
{
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 nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
typedef void (CCmdTarget::*AFX_PMSG)(void);
#define DECLARE_MESSAGE_MAP() \
static AFX_MSGMAP_ENTRY _messageEntries[]; \
static AFX_MSGMAP messageMap; \
virtual AFX_MSGMAP* GetMessageMap() const;
以上宏构建消息映射网络
下面宏填充消息映射网络的内容
消息映射宏
BEGIN_MESSAGE_MAP
ON_COMMAND
ON_CONTROL
ON_MESSAGE
END_MESSAGE_MAP
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_MSGMAP theClass::messageMap = \
{ &(baseClass::messageMap), \
(AFX_MSGMAP_ENTRY*) &(theClass::_messageEntries) }; \
AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, 0, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },
#define END_MESSAGE_MAP() \
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
};
// 以下结构代表消息处理函数的类型。
enum AfxSig
{
AfxSig_end = 0, // [marks end of message map]
AfxSig_vv,
};
通过以上宏及构建出了如下的网络
关于ON_COMMAND及AfxSig定义如下图
将MFC中的框架类库串起来就会构成这样的一大张网络如下
目前我们网路搭建起来了,即相应的消息可在网路中查找想应的处理函数。万事俱备只欠东风。我们来了解一下消息泵的推动点
及如SDK程序中的消息分流器。WinProc。
首先在进入MFC入口点时。MFC调用了一个全局初始化函数,这部分做了一件事,及注册了一个消息回调函数AfxWndProc。同SDK程序中的WndProc。如下
BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { ... // register basic WndClasses ( WNDCLASS wndcls; wndcls.lpfnWndProc = AfxWndProc; // Child windows - no brush, no icon, safest default class styles ... wndcls.lpszClassName = _afxWnd; if (!::RegisterClass(&wndcls)) return FALSE; // Control bar windows ... wndcls.lpszClassName = _afxWndControlBar; if (!::RegisterClass(&wndcls)) return FALSE; // MDI Frame window (also used for splitter window) ... if (!RegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME)) return FALSE; // SDI Frame or MDI Child windows or views - normal colors ... if (!RegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME)) return FALSE; ... }
AfxWndProc如下:
LRESULT CALLBACK AFX_EXPORT AfxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { CWnd* pWnd; pWnd = CWnd::FromHandlePermanent(hWnd); ASSERT(pWnd != NULL); ASSERT(pWnd->m_hWnd == hWnd); LRESULT lResult = _AfxCallWndProc(pWnd, hWnd, message, wParam, lParam); return lResult; } LRESULT PASCAL _AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam) { LRESULT lResult; ... TRY { ... lResult = pWnd->WindowProc(message, wParam, lParam); } ... return lResult; }
消息分发获取函数
在应用程序调用CWinAPP::Run()时,里面调用::DispatchMessage()即将消息推送至CWnd::WindowProc()函数。 WindowProc(),即根据所注册的消息大型网络进行找对应的消息响应函数执行。