《Windows 程序设计》第七章中的 Checker3 和 Checker4 程序
为父窗口和子窗口分别定义了窗口过程 WndProc 和 ChildWndProc
并且父窗口和子窗口使用同一个消息循环
Checker4 的代码如下
#include <windows.h> #define DIVISIONS 5 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ; int idFocus = 0 ; TCHAR szChildClass[] = TEXT ("Checker4_Child") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Checker4") ; HWND hwnd ; MSG msg ; WNDCLASS 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.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("Program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } wndclass.lpfnWndProc = ChildWndProc ; wndclass.cbWndExtra = sizeof (long) ; wndclass.hIcon = NULL ; wndclass.lpszClassName = szChildClass ; RegisterClass (&wndclass) ; hwnd = CreateWindow (szAppName, TEXT ("Checker4 Mouse Hit-Test Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hwndChild[DIVISIONS][DIVISIONS] ; int cxBlock, cyBlock, x, y ; switch (message) { case WM_CREATE: for (x = 0 ; x < DIVISIONS ; x++) for (y = 0 ; y < DIVISIONS ; y++) hwndChild[x][y] = CreateWindow (szChildClass, NULL, WS_CHILDWINDOW | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU) (y <<8 | x), (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE), NULL) ; return 0 ; case WM_SIZE: cxBlock = LOWORD (lParam) / DIVISIONS ; cyBlock = HIWORD (lParam) / DIVISIONS ; for (x = 0 ; x < DIVISIONS ; x++) for (y = 0 ; y < DIVISIONS ; y++) MoveWindow (hwndChild[x][y], x * cxBlock, y * cyBlock, cxBlock, cyBlock, TRUE) ; return 0 ; case WM_LBUTTONDOWN: MessageBeep (1) ; return 0 ; case WM_KEYDOWN: x = idFocus & 0xFF ; y = idFocus >> 8 ; switch (wParam) { case VK_UP: y-- ; break ; case VK_DOWN: y++ ; break ; case VK_LEFT: x-- ; break ; case VK_RIGHT: x++ ; break ; case VK_HOME: x = y = 0 ; break ; case VK_END: x = y = DIVISIONS - 1 ; break ; default: return 0 ; } x = (x + DIVISIONS) % DIVISIONS ; y = (y + DIVISIONS) % DIVISIONS ; idFocus = (y << 8) | x ; SetFocus (GetDlgItem (hwnd, idFocus)) ; //??? return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_CREATE: SetWindowLong (hwnd, 0, 0) ; return 0 ; case WM_KEYDOWN: if (wParam != VK_RETURN && wParam != VK_SPACE) { SendMessage (GetParent (hwnd), message, wParam, lParam) ; return 0 ; } case WM_LBUTTONDOWN: SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (hwnd, 0)) ; SetFocus (hwnd) ; InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; case WM_SETFOCUS: idFocus = GetWindowLong (hwnd, GWL_ID) ; case WM_KILLFOCUS: InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; Rectangle (hdc, 0, 0, rect.right, rect.bottom) ; if (GetWindowLong (hwnd, 0)) { MoveToEx (hdc, 0, 0, NULL) ; LineTo (hdc, rect.right, rect.bottom) ; MoveToEx (hdc, 0, rect.bottom, NULL) ; LineTo (hdc, rect.right, 0) ; } if (hwnd == GetFocus ()) { rect.left += rect.right / 10 ; rect.right -= rect.left ; rect.top += rect.bottom / 10 ; rect.bottom -= rect.top ; SelectObject (hdc, GetStockObject (NULL_BRUSH)) ; SelectObject (hdc, CreatePen (PS_DASH, 0, 0)) ; Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom) ; DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ; } EndPaint (hwnd, &ps) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
于是对其父窗口和子窗口消息的分发产生了困惑
如当用户在客户区移动或点击鼠标时,相关消息发送给 WndProc 还是 ChildWndProc
当用户按下键盘按键时,相关消息发送给 WndProc 还是 ChildWndProc
针对这两种情况我分别进行了下跟踪调试,过程如下
首先,在两个窗口过程的消息处理中添加对 WM_MOUSEMOVE 消息的支持
case WM_MOUSEMOVE: return 0 ;
然后对其设置断点,编译-链接-调试,在客户区移动鼠标进行测试
因为该程序在客户区定义了25个子窗口,以 5 * 5 的格子形分布
所以在客户区宽高不被5整除时,客户区的右侧或下侧会留下部分空白
如下图
首先在网格内移动鼠标,在 ChildWndProc 中的断点拦截到消息
然后在空白处移动鼠标,在 WndProc 中的断点拦截到消息
再进行单击的测试,取消 WM_MOUSEMOVE 的断点
对 WM_LBUTTONDOWN 消息设置断点
在网格内单击鼠标,ChildWndProc 中的断点拦截到消息
在空白处单击鼠标,WndProc 中的断点拦截到消息
由此可以推测,
Windows 会自动查找当前鼠标位置所归属的窗口句柄,并对其分发消息
假设其25个子窗口是平铺在客户区上,在鼠标经过时
主窗口的客户区已被子窗口客户区遮盖
所以此时窗口消息属于子窗口,由子窗口接收窗口消息
因为对消息分发不甚理解,所以该程序对 WM_KEYDOWN 的处理也是云里雾里
首先分别对两个窗口过程的消息处理中对 WM_KEYDOWN 消息设置断点
调试程序,任意按下一个按键
当第一次按键时,消息被分发到了 WndProc 窗口过程
之后的按键均先经过 ChildWndProc 窗口过程,如非空格和回车再回传给 WndProc
因为程序中有对输入焦点的设置,于是判断键盘消息的分发与输入焦点有关
在 WndProc 中对 WM_SIZE 消息处理中添加了一条语句
SetFocus (GetDlgItem (hwnd, idFocus)) ;
重新编译调试,这次所有的键盘消息均先经过 ChildWndProc
最小化程序,然后再恢复窗口,键盘消息又被分发到了 WndProc
由此可以推测
键盘消息会被分发到当前具有输入焦点的窗口句柄中
或者说键盘消息会发生在当前具有输入焦点的窗口中
然后改进下程序因为失去焦点再恢复后导致的子窗口虚线丢失的问题
在 WndProc 的消息处理中添加 WM_SETFOCUS 消息的处理
case WM_SETFOCUS: SetFocus (GetDlgItem (hwnd, idFocus)) ; return 0 ;