先看第一种的程序:
/*------------------------------------------------- CHECKER1.C -- Mouse Hit-Test Demo Program No. 1 (c) Charles Petzold, 1998 -------------------------------------------------*/ #include <windows.h> #define DIVISIONS 5 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Checker1") ; 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 ; } hwnd = CreateWindow (szAppName, TEXT ("Checker1 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 BOOL fState[DIVISIONS][DIVISIONS] ; //每个格子的大小 static int cxBlock, cyBlock ; HDC hdc ; int x, y ; PAINTSTRUCT ps ; //每个格子耳朵位置 RECT rect ; switch (message) { case WM_SIZE : //获取格子的大小 cxBlock = LOWORD (lParam) / DIVISIONS ; cyBlock = HIWORD (lParam) / DIVISIONS ; return 0 ; case WM_LBUTTONDOWN : //点在哪个格子里 x = LOWORD (lParam) / cxBlock ; y = HIWORD (lParam) / cyBlock ; //防止意外发生 if (x < DIVISIONS && y < DIVISIONS) { fState [x][y] ^= 1 ; //获取点击的矩形位置 rect.left = x * cxBlock ; rect.top = y * cyBlock ; rect.right = (x + 1) * cxBlock ; rect.bottom = (y + 1) * cyBlock ; //矩形变为无效,重绘矩形 InvalidateRect (hwnd, &rect, FALSE) ; } else MessageBeep (0) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; for (x = 0 ; x < DIVISIONS ; x++) for (y = 0 ; y < DIVISIONS ; y++) { //画矩形 Rectangle (hdc, x * cxBlock, y * cyBlock,(x + 1) * cxBlock, (y + 1) * cyBlock) ; //通过标志位判断是否需要画对角线 if (fState [x][y]) { //画对角线 MoveToEx (hdc, x * cxBlock, y * cyBlock, NULL) ; LineTo (hdc, (x+1) * cxBlock, (y+1) * cyBlock) ; MoveToEx (hdc, x * cxBlock, (y+1) * cyBlock, NULL) ; LineTo (hdc, (x+1) * cxBlock, y * cyBlock) ; } } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
原理是这样的,每次WM_PAINT消息下,都对把客户区分成5*5个矩形方框,为每个方框建立一个bool类数组,当点击一下方框时该方框的数组改为true,再点击一下,变为false。并保存点击的矩形框的位置。每次点击都会是得方框变为无效,引起WM_PAINT消息,而在该消息下,不管三七二十一,先画出25个矩形框,然后通过数组来判断每个矩形框是否需要打叉。
再看另一种程序:
/*------------------------------------------------- CHECKER3.C -- Mouse Hit-Test Demo Program No. 3 (c) Charles Petzold, 1998 -------------------------------------------------*/ #include <windows.h> #define DIVISIONS 5 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szChildClass[] = TEXT ("Checker3_Child") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Checker3") ; 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 ("Checker3 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 (0) ; 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 : //用SetWindowWord在窗口结构保留的额外区域中储存一个0值 SetWindowLong (hwnd, 0, 0) ; // on/off flag return 0 ; case WM_LBUTTONDOWN : //1 与GetWindowLong (hwnd, 0)的结果求异或 //如果原来是1,异或结果为0 SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (hwnd, 0)) ; InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; //或得子窗口的客户区大小 GetClientRect (hwnd, &rect) ; Rectangle (hdc, 0, 0, rect.right, rect.bottom) ; //如果为1,画图 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) ; } EndPaint (hwnd, &ps) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
这个程序就完全是另外一种思路了:在WinMain函数中,注册了两个窗口,第一个窗口就是我们正常使用的。第二个窗口有附加的bite,这个比特是用来保留点击信息的,这样与第一个程序相比,就少使用了标记。
在主窗口的WM_CREATE消息下,创建了25个子窗口;在WM_SIZE消息下,将这些窗口摆放到了特定的位置。
程序的主要工作是在子窗口的响应函数下完成的(注意,这时当你点击客户区时,由于客户区被子窗口覆盖满了,所以消息响应全是子窗口的):
WM_CREATE消息下:SetWindowLong (hwnd, 0, 0) ;函数的作用实际上是给标志位设为0,表示没有打叉;
WM_LBUTTONDOWN消息下:SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (hwnd, 0))是将/1 与GetWindowLong (hwnd, 0)的结果求异或,而GetWindowLong返回的,就是你设置的那个值(因为设置和返回的偏移量都为0),。所以如果之前里面设为1(有叉),那么与1异或结果为0(无叉)。
WM_PAINT下就用过GetWindowLong返回结果来判断是否需要画插了。
总体上讲,其实我们并没有判断鼠标到底停留在哪个窗口的上空,而是操作系统替我们完成这个工作。