}
首先,GetMessage从进程的主线程的消息队列中获取一个消息并将它复制到MSG结构,如果队列中没有消息,则GetMessage函数将等待一个消息的到来以后才返回。 如果你将一个窗口句柄作为第二个参数传入GetMessage,那么只有指定窗口的的消息可以从队列中获得。GetMessage也可以从消息队列中过滤消息只接受消息队列中落在范围内的消息。这时候就要利用GetMessage/PeekMessage指定一个消息过滤器。这个过滤器是一个消息标识符的范围或者是一个窗体句柄,或者两者同时指定。当应用程序要查找一个后入消息队列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的键盘消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用于接受所有的鼠标消息。
然后TranslateAccelerator判断该消息是不是一个按键消息并且是一个加速键消息,如果是,则该函数将把几个按键消息转换成一个加速键消息传递给窗口的回调函数。处理了加速键之后,函数TranslateMessage将把两个按键消息WM_KEYDOWN和WM_KEYUP转换成一个WM_CHAR,不过需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然将传递给窗口的回调函数。
处理完之后,DispatchMessage函数将把此消息发送给该消息指定的窗口中已设定的回调函数。如果消息是WM_QUIT,则GetMessage返回0,从而退出循环体。应用程序可以使用PostQuitMessage来结
束自己的消息循环。通常在主窗口的WM_DESTROY消息中调用。
下面我们举一个常见的小例子来说明这个消息泵的运用:
if (::PeekMessage(&msg, m_hWnd,
WM_KEYFIRST,WM_KEYLAST, PM_REMOVE)) {
if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)... }
这里我们接受所有的键盘消息,所以就用WM_KEYFIRST 和 WM_KEYLAST作为参数。最后一个参数可以是PM_NOREMOVE 或者 PM_REMOVE,表示消息信息是否应该从消息队列中删除。
所以这段小代码就是判断是否按下了Esc键,如果是就进行处理。
窗口过程
窗口过程是一个用于处理所有发送到这个窗口的消息的函数。任何一个窗口类都有一个窗口过程。同一个类的窗口使用同样的窗口过程来响应消息。 系统发送消息给窗口过程将消息数据作为参数传递给他,消息到来之后,按照消息类型排序进行处理,其中的参数则用来区分不同的消息,窗口过程使用参数产生合适行为。
一个窗口过程不经常忽略消息,如果他不处理,它会将消息传回到执行默认的处理。窗口过程通过调用DefWindowProc来做这个处理。窗口过程必须return一个值作为它的消息处理结果。大多数窗口只处理
小部分消息和将其他的通过DefWindowProc传递给系统做默认的处理。窗口过程被所有属于同一个类的窗口共享,能为不同的窗口处理消息。下面我们来看一下具体的实例:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
switch (message) {
case WM_COMMAND:
wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) {
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break;
case IDM_EXIT:
DestroyWindow(hWnd); break; default:
return DefWindowProc(hWnd, message, wParam, lParam); }
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here... RECT rt;
GetClientRect(hWnd, &rt);
DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER); EndPaint(hWnd, &ps); break;
case WM_DESTROY:
PostQuitMessage(0); break; default:
return DefWindowProc(hWnd, message, wParam, lParam); }
return 0; }
通常的窗口过程是通过一个switch语句来实现的,这个事情很烦,有没有更简便的方法呢?有,那就是消息分流器,利用消息分流器,我们可以把switch语句分成更小的函数,每一个消息都对应一个小函数,这样做的好处就是对消息更容易管理。
之所以被称为消息分流器,就是因为它可以对任何消息进行分流。下面我们做一个函数就很清楚了:
void MsgCracker(HWND hWnd,int id,HWND hWndCtl,UINT codeNotify) {
switch(id) {
case ID_A:
if(codeNotify==EN_CHANGE)... break;
case ID_B:
if(codeNotify==BN_CLICKED)... break; .... } }
然后我们修改一下窗口过程:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
消息分流器
WPARAM wParam, LPARAM lParam) {
switch(message) {
HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker); HANDLE_MSG(hWnd,WM_DESTROY,MsgCracker); default:
return DefWindowProc(hWnd, message, wParam, lParam); }
return 0; }
在WindowsX.h中定义了如下的HANDLE_MSG宏:
#define HANDLE_MSG(hwnd,msg,fn) \\ switch(msg): return
HANDLE_##msg((hwnd),(wParam),(lParam),(fn));
实际上,HANDLE_WM_XXXX都是宏,例如:
HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);将被转换成如下定义:
#define
HANDLE_WM_COMMAND(hwnd,wParam,lParam,fn)\\
((fn)((hwnd),(int)(LOWORD(wParam)),(HWND)(lParam),(UINT)HIWORD(wParam)),0L);
好了,事情到了这一步,应该一切都明朗了。
不过,我们发现在windowsx.h里面还有一个宏:
FORWARD_WM_XXXX,我们还是那WM_COMMAND为例,进行分析:
#define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \\
(void)(fn)((hwnd), WM_COMMAND,