MFC消息映射浅谈
首先,如果编写一个windows应用程序,最直接的方法是使用MFC,因为MFC已经为我们处理了很多繁杂的过程,若不采用MFC,则通常的做法是调用windows API函数实现。如经过以下步骤,可以创建一个简单的窗口。
- 注册窗口
HWND h_Wnd; //保存创建窗口的句柄 WNDCLASS w_Class; MSG msg; //用以消息循环的MSG结构体 w_Class.cbClsExtra = 0; w_Class.cbWndExtra = 0; w_Class.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); w_Class.hCursor = (HCURSOR)LoadCursor(NULL, IDC_ARROW); w_Class.hIcon = (HICON)LoadIcon(NULL, IDI_APPLICATION); w_Class.hInstance = hInstance; w_Class.lpszClassName = "Application"; w_Class.lpszMenuName = NULL; w_Class.style = 0; w_Class.lpfnWndProc = WndProc; RegisterClass(&w_Class);
- 创建窗口
h_Wnd = CreateWindow("Application", "MyWindow", WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, NULL, NULL, hInstance, NULL);
- 显示窗口
ShowWindow(h_Wnd, nShowCmd);
- 更新窗口
UpdateWindow(h_Wnd);
- 消息循环
while (GetMessage(&msg, h_Wnd, NULL, NULL)) { TranslateMessage(&msg); DispatchMessage(&msg); }
- 消息处理
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_LBUTTONDOWN: { MessageBeep(0); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd,message,wParam,lParam); } return 0; }
当然,以上代码必须加入头文件#include <Windows.h>,以上代码只简单展示了窗口的创建过程以及对鼠标左键按下的消息处理的过程(发出windows嘣嘣声)。
Windows应用程序的一个特点是在程序启动并创建了程序窗口后,就进入了一个等待状态(上面代码的while循环),直到受到某种刺激(消息)之后,如鼠标单击,键盘按下等,上面代码只有鼠标单击和关闭,程序才会脱离等待状态对这个消息进行处理(上面代码的信息处理函数WndProc),处理完成后接着等待下一个信息,以此类推。这些能够触发计算机程序作出相应的刺激或者消息称为事件。
为了描述事件的各种信息,windows定义了一个结构,上面代码的MSG msg,这个结构就叫做消息。
typedef struct tagMSG { HWND hwnd; //产生消息的窗口句柄 UINT message; //消息的标识码 WPARAM wParam;// 消息的附加信息 LPARAM lParam; //消息的附加信息 DWORD time; //消息进入消息队列的时刻 POINT pt;//发送该消息时光标的位置 } MSG;
这样,当计算机发生某件事,windows会把该事件的相关信息添加到该MSG结构体的各个域中,并把这个消息送达到应用程序,而应用程序则根据消息里的结构变量的值来确定如何处理该消息。Windows已经把绝大多数事件的消息事先进行了定义,并定义了对消息的标示码。如WM_LBUTTONDOWN,WM_LBUTTONUP等待。
系统为每个应用程序都建立了一个叫做消息队列的存储空间,在程序运行过程中发生某事件,windows就会把这个事件所对应的消息送入消息队列中等待使用。而应用程序可以通过使用windows提供的API(GetMessage())从消息队列中获取消息,并利用GetMessage()的返回值(当获得一个消息时返回TRUE,否则返回FALSE)来形成一个循环,从而可以不断地从消息队列中获取消息,一旦获取一个消息,就发送给系统。这个循环就叫消息循环。如上面代码的while循环部分。
当函数DispatchMessage()把消息拍送给系统后,系统会根据消息中的hWnd找到应该接受消息的窗口,并根据窗口提供的信息,以消息为参数来调用一个用户编写的叫做“窗口函数”的函数,上面代码中的WndProc(),在该函数里用户可以按照自己的方式处理各种消息。处理完成后,只要该消息不是终止应用程序的消息,则会立即返回消息循环,等待下一个消息。Windows应用程序就这样周而复始的循环,直至用户发出结束应用程序的消息(上面代码的WM_DESTROY)。
正是windows应用程序必须接受一个消息才会启动某种操作,因此人们常说windows应用程序的运行是消息驱动或者事件驱动的。
Windows消息从输出路径来看,可以分为两类:一类是先把消息送到消息队列,然后由应用程序的消息循环通过函数DispatchMessage()发送给窗口,这类消息叫队列消息,鼠标和键盘都是队列消息;另一类是不经过消息队列而直到窗口的消息,这类消息叫非队列消息,系统事件产生的消息一般为非队列消息。
随便提一下上面消息循环的TranslateMessage(),该函数是将消息(如鼠标消息)转换为或者“翻译成”可以被系统识别的消息。
消息的传递过程如图1-1。
图1-1 windows消息传递过程
此图最右边方向箭头画错了,应该是从应用程序消息处理到消息循环(反过来)
可以看到,处理消息的窗口函数是一个switch-case结构,当然这不是处理多分支程序的唯一结构。下面将会给出一种更模块化的结构。
首先,为了使Windows SDK程序结构更清晰,利用C函数把程序模块化,对Windows程序进行封装。
程序代码可按如下设计:
#include<windows.h> //定义全局变量-------------------------------------------------------------------------------- HINSTANCE hInst; HWND hWnd; MSG msg; char lpszClassName[]="窗口"; char*ShowText; //声明函数原型-------------------------------------------------------------------------------------- ATOM MyRegisterClass(HINSTANCE hInstance);//注册窗口类函数 BOOL Create(HINSTANCE, int); //程序实例初始化函数 int Run(); //消息循环函数 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //窗口函数 void OnLButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); void OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); void OnDestroy(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); //主函数----------------------------------------------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MyRegisterClass(hInstance); //定义和注册窗口类 Create(hInstance, nCmdShow); //创建窗口 ShowWindow(hWnd, nCmdShow); //显示窗口 UpdateWindow(hWnd); //更新屏幕显示 return Run(); //消息循环 } //注册窗口类函数的实现-------------------------------------------------------------------------- ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASS wc; wc.style=0; wc.lpfnWndProc=WndProc; wc.cbClsExtra=0; wc.cbWndExtra=0; wc.hInstance=hInstance; wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW); wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName=NULL; wc.lpszClassName=lpszClassName; return RegisterClass(&wc); } //创建窗口函数的实现----------------------------------------------------------------------------- BOOL Create(HINSTANCE hInstance, int nCmdShow) { hWnd=CreateWindow( lpszClassName, "Windows", WS_OVERLAPPEDWINDOW, 400,300,180,160, NULL, NULL, hInstance, NULL); return TRUE; } //消息循环函数的实现----------------------------------------------------------------------------- int Run( ) { while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //窗口函数的实现-------------------------------------------------------------------------------------- LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_LBUTTONDOWN: OnLButtonDown(hWnd, message, wParam, lParam); break; case WM_PAINT: OnPaint(hWnd, message,wParam, lParam); break; case WM_DESTROY: OnDestroy(hWnd, message, wParam, lParam); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } void OnLButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { ShowText="Hello!"; InvalidateRect(hWnd,NULL,1); } void OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; hdc = BeginPaint(hWnd, &ps); TextOut(hdc,50,50,ShowText,6); EndPaint(hWnd, &ps); } void OnDestroy(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PostQuitMessage(0); }
从上面代码可以看出,程序由两大部分组成:主函数部分和窗口函数部分。主函数由五个函数组成,窗口函数由四个函数组成。这样使得程序的结构更见清晰。
上面随实现了部分功能,但并没有从根本上处理消息处理,下面展示一种接近MFC的处理过程。
1.先把各个消息处理程序封装成函数,制作一个如图1-1的消息与消息处理函数之间的对应关系,这个表可以叫做消息映射表。
消息标示 | 消息处理函数 |
WM_LBUTTONDOWN | On_LButtonDown |
WM_PAINT | On_Paint |
WM_RESTROY | On_Restroy |
图1-1 消息映射表
在程序中,可以用数组或者链表来实现这个表,这里简单用数组实现,然后设计数组结构如下:
struct MSGMAP_ENTRY { UINT nMessage; //消息标示 void (*pfn)(HWND, UINT, WPARAM, LPARAM); //消息处理函数指针 };
于是,作为消息映射表的数组应该为:
struct MSGMAP_ENTRY messageEntres[] = { WM_LBUTTONDOWN, On_LButtonDown, WM_PAINT, On_Paint, WM_DESTROY, On_Destroy, };
2.这样窗口函数就可以这样设计:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int i; int n = sizeof (messageEntres) / sizeof(messageEntres[0]); for (i = 0; i < n; i ++) { if (message == messageEntres[i].nMessage) (*messageEntres[i].pfn)(hWnd, message, wParam, lParam); } return DefWindowProc(hWnd, message, wParam, lParam); }
这样,无论需要处理多少消息,窗口函数的结构都被固定成这种格式。于是就可以再编程是自动产生这个函数,而用户所需要做的工作就是编写消息处理函数,并按上面消息结构将消息和消息处理函数填入MSGMAP_ENTRY中。
为了使程序结构更接近MFC,我们一步步进行处理,将这些消息处理定义为宏。这样代码将如下:
//头文件 #ifndef MYWINDOWS #define MYWINDOWS #include <Windows.h> struct MSGMAP_ENTRY { UINT nMessage; void (*pfn)(HWND, UINT, WPARAM, LPARAM); }; ATOM MyRegisterClass(HINSTANCE hInstance); HWND Create(HINSTANCE, int); int Run(); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void On_LButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); void On_Paint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); void On_Destroy(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); #define DECLARE_MESSAGE_MAP() \ struct MSGMAP_ENTRY messageEntres[]; \ #define BEGIN_MESSAGE_MAP() \ struct MSGMAP_ENTRY messageEntres[] = \ { \ #define ON_WM(messageID, msgFuc) \ messageID, msgFuc, #define END_MESSAGE_MAP() \ }; \ #endif
//实现文件 #include "feizhuang.h" MSG msg; char lpszClassName[] = "窗口"; char *ShowText; HINSTANCE hInstance; HWND hWnd; //声明消息映射表 DECLARE_MESSAGE_MAP()
//实现消息映射表 BEGIN_MESSAGE_MAP() ON_WM(WM_LBUTTONDOWN, On_LButtonDown) ON_WM(WM_PAINT, On_Paint) ON_WM(WM_DESTROY, On_Destroy) END_MESSAGE_MAP() ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASS wc; wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL,IDI_APPLICATION); wc.hCursor = LoadCursor(NULL,IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = lpszClassName; return RegisterClass(&wc); } HWND Create(HINSTANCE hInstance, int nCmdShow) { hWnd = CreateWindow(lpszClassName, "Windows", WS_OVERLAPPEDWINDOW, 400,300,500,500, NULL, NULL, hInstance, NULL); return hWnd; } int Run() { while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
int i;
int n = sizeof (messageEntres) / sizeof(messageEntres[0]);
for (i = 0; i < n; i ++)
{
if (message == messageEntres[i].nMessage)
(*messageEntres[i].pfn)(hWnd, message, wParam, lParam);
}
return DefWindowProc(hWnd, message, wParam, lParam);
} void On_LButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { ShowText = "Hello!"; InvalidateRect(hWnd,NULL,1); } void On_Paint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; hdc = BeginPaint(hWnd, &ps); TextOut(hdc, 50, 50, ShowText, 6); EndPaint(hWnd, &ps); } void On_Destroy(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PostQuitMessage(0); }
下一篇,可以使用C++特性将这些处理封装成类。