《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 ;