第七章 鼠标(CHECKER2)
CHECKER2程序包含一个键盘接口,内容与CHECKER1完全相同。利用←、→、↑、↓四个方向键可以在25个矩形之间移动鼠标指针。Home键把鼠标指针移动到左上角的矩形;End键使鼠标指针落到右下角的矩形。空格键和回车键都可以切换X形标记。
1 /*--------------------------------------------- 2 CHECKER2.C -- Mouse Hit-Test Demo Program No.2 3 (c) Charles Petzold, 1998 4 ---------------------------------------------*/ 5 6 #include <Windows.h> 7 8 #define DIVISIONS 5 9 10 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 11 12 int WINAPI WinMain( __in HINSTANCE hInstance 13 , __in_opt HINSTANCE hPrevInstance 14 , __in LPSTR lpCmdLine 15 , __in int nShowCmd ) 16 { 17 static TCHAR szAppName[] = TEXT("Checker2"); 18 HWND hwnd; 19 MSG msg; 20 WNDCLASS wndclass; 21 22 wndclass.style = CS_HREDRAW | CS_VREDRAW; 23 wndclass.lpfnWndProc = WndProc; 24 wndclass.cbClsExtra = 0; 25 wndclass.cbWndExtra = 0; 26 wndclass.hInstance = hInstance; 27 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); 28 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); 29 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 30 wndclass.lpszMenuName = NULL; 31 wndclass.lpszClassName = szAppName; 32 33 if (!RegisterClass(&wndclass)) 34 { 35 MessageBox(NULL, TEXT("Program requires Windows NT!") 36 , szAppName, MB_ICONERROR); 37 return 0; 38 } 39 40 hwnd = CreateWindow(szAppName, TEXT("Checker2 Mouse Hit-Test Demo") 41 , WS_OVERLAPPEDWINDOW 42 , CW_USEDEFAULT, CW_USEDEFAULT 43 , CW_USEDEFAULT, CW_USEDEFAULT 44 , NULL, NULL, hInstance, NULL); 45 46 ShowWindow(hwnd, nShowCmd); 47 UpdateWindow(hwnd); 48 49 while (GetMessage(&msg, NULL, 0, 0)) 50 { 51 TranslateMessage(&msg); 52 DispatchMessage(&msg); 53 } 54 55 return msg.wParam; 56 } 57 58 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 59 { 60 static BOOL fState[DIVISIONS][DIVISIONS]; 61 static int cxBlock, cyBlock; 62 HDC hdc; 63 int x, y; 64 PAINTSTRUCT ps; 65 POINT point; 66 RECT rect; 67 68 switch (message) 69 { 70 case WM_SIZE: 71 cxBlock = LOWORD(lParam) / DIVISIONS; 72 cyBlock = HIWORD(lParam) / DIVISIONS; 73 return 0; 74 75 case WM_SETFOCUS: 76 ShowCursor(TRUE); 77 return 0; 78 79 case WM_KILLFOCUS: 80 ShowCursor(FALSE); 81 return 0; 82 83 case WM_KEYDOWN: 84 GetCursorPos(&point); 85 ScreenToClient(hwnd, &point); 86 87 x = max(0, min(DIVISIONS - 1, point.x / cxBlock)); 88 y = max(0, min(DIVISIONS - 1, point.y / cyBlock)); 89 90 switch (wParam) 91 { 92 case VK_UP: 93 --y; 94 break; 95 96 case VK_DOWN: 97 ++y; 98 break; 99 100 case VK_LEFT: 101 --x; 102 break; 103 104 case VK_RIGHT: 105 ++x; 106 break; 107 108 case VK_HOME: 109 x = y = 0; 110 break; 111 112 case VK_END: 113 x = y = DIVISIONS - 1; 114 break; 115 116 case VK_RETURN: 117 case VK_SPACE: 118 SendMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x * cxBlock, y * cyBlock)); 119 break; 120 } 121 122 x = (x + DIVISIONS) % DIVISIONS; 123 y = (y + DIVISIONS) % DIVISIONS; 124 125 point.x = x * cxBlock + cxBlock / 2; 126 point.y = y * cyBlock + cyBlock / 2; 127 128 ClientToScreen(hwnd, &point); 129 SetCursorPos(point.x, point.y); 130 return 0; 131 132 case WM_LBUTTONDOWN: 133 x = LOWORD(lParam) / cxBlock; 134 y = HIWORD(lParam) / cyBlock; 135 136 if (x < DIVISIONS && y < DIVISIONS) 137 { 138 fState[x][y] ^= 1; 139 140 rect.left = x * cxBlock; 141 rect.top = y * cyBlock; 142 rect.right = (x + 1) * cxBlock; 143 rect.bottom = (y + 1) * cyBlock; 144 145 InvalidateRect(hwnd, &rect, FALSE); 146 } 147 else 148 MessageBeep(0); 149 return 0; 150 151 case WM_PAINT: 152 hdc = BeginPaint(hwnd, &ps); 153 154 for (x = 0; x < DIVISIONS; ++x) 155 for (y = 0; y < DIVISIONS; ++y) 156 { 157 Rectangle(hdc, x * cxBlock, y * cyBlock 158 , (x + 1) * cxBlock, (y + 1) * cyBlock); 159 160 if (fState[x][y]) 161 { 162 MoveToEx(hdc, x * cxBlock, y * cyBlock, NULL); 163 LineTo(hdc, (x + 1) * cxBlock, (y + 1) * cyBlock); 164 MoveToEx(hdc, x * cxBlock, (y + 1) * cyBlock, NULL); 165 LineTo(hdc, (x + 1) * cxBlock, y * cyBlock); 166 } 167 } 168 169 EndPaint(hwnd, &ps); 170 return 0; 171 172 case WM_DESTROY: 173 PostQuitMessage(0); 174 return 0; 175 } 176 177 return DefWindowProc(hwnd, message, wParam, lParam); 178 }
在CHECKER2程序中,处理WM_KEYDOWN时利用GetCursorPos判断指针的位置,并利用ScreenToClient将屏幕坐标转换成客户区坐标,然后将坐标值除以矩形块的宽和高,得到x和y。这些x和y的值表示了矩形在5*5数组中的位置。当按下某个键时,鼠标指针可能在客户区也可能不在客户区内,因此x和y必须包含在min和max的宏处理中,保证它们的范围处于0和4之间。
对于方向键,CHECKER2程序相应的增加或减少x和y的值。若按下回车键或空格键,CHECKER2程序调用SendMessage给自己发送一个WM_LBUTTONDOWN消息。最后,WM_KEYDOWN处理逻辑计算得到指向矩形中心的客户区坐标,并调用ClientToScreen将其转换成屏幕坐标,最后调用SetCursorPos设置指针的位置。