鼠标
判断鼠标是否连接:
fMouse = GetSystemMetrics(SM_MOUSEPRESENT) ;
连接时,fMouse 的值为 非0,否则为 0。
鼠标的按钮个数:
cButtons = GetSystemMetrics(SM_CMOUSEBUTTONS) ;
如果没安装鼠标,则 cButtons 为 0。
鼠标指针具有一个单像素精度的“热点”。(箭头的顶点即热点) 热点在显示设备上指示了一个精确的位置。
当鼠标移经窗口客户区时,窗口过程接收 WM_MOUSEMOVE 消息。
在窗口客户区内按下或者释放鼠标按钮时,窗口过程接收如下表所示的消息:
| 按钮 | 标识符 | 按下 | 释放 | 第二次按下按钮 |
| 左键 | LBUTTON | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK |
| 中键 | MBUTTON | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBLCLK |
| 右键 | RBUTTON | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_RBUTTONDBLCLK |
对所有这些消息来说,参数 lParam 包含了鼠标的位置信息,其中低位字表示 x 坐标,髙位字表示 y 坐标,
它们都是相对于窗口客户区左上角的相对坐标。利用 LOWORD 和 HIWORD 宏,可以获取这些坐标值:
x = LOWORD(lParam) ;
y = HIWORD(lParam) ;
参数 wParam 表示鼠标按钮,Shift 和 Ctrl 键的状态。前缀 MK 代表“鼠标键”。
| 状态码 | 按键行为 |
| MK_LBUTTON | 按下左键 |
| MK_MBUTTON | 按下中键 |
| MK_RBUTTON | 按下右键 |
| MK_SHIFT | 按下 Shift 键 |
| MK_CONTROL | 按下 Ctrl 键 |
例如,当接受到 WM_LBUTTONDOWN 消息时,若 wParam & MK_SHIFT 的值为 非0,则表示按下左键的同时按下了 Shift 键。
wParam & SHIFT 是使用位操作进行判断。
DEMO CODE:
#include <windows.h> #include <windowsx.h> #define MAXPOINTS 1000 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Connect") ; 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 ("Connect-the-Points Mouse 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 POINT pt[MAXPOINTS] ; static int iCount ; HDC hdc ; int i, j ; PAINTSTRUCT ps ; switch (message) { case WM_LBUTTONDOWN: iCount = 0 ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case WM_MOUSEMOVE: if (wParam & MK_LBUTTON && iCount < 1000) { pt[iCount ].x = GET_X_LPARAM (lParam) ; pt[iCount++].y = GET_Y_LPARAM (lParam) ; hdc = GetDC (hwnd) ; SetPixel (hdc, GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam), 0) ; ReleaseDC (hwnd, hdc) ; } return 0 ; case WM_LBUTTONUP: InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; for (i = 0 ; i < iCount - 1 ; i++) for (j = i + 1 ; j < iCount ; j++) { MoveToEx (hdc, pt[i].x, pt[i].y, NULL) ; LineTo (hdc, pt[j].x, pt[j].y) ; } ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
鼠标双击:
鼠标双击是指连续两次快速地单击。为了达到双击的效果,两次单击不仅要在物理位置上十分靠近,
还必须发生在特定的时间间隔内。这个间隔称为“双击速度”。
如果想让窗口过程接收鼠标双击消息,那么在调用 RegisterClass 初始化窗口类结构时,
必须在窗口风格字段中包含标识符 CS_DBLCLKS :
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;
如果窗口类型没有包含 CS_DBLCLKS, 那么当用户连续两次快速单击左键时,
窗口过程接收的消息顺序如下:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDOWN
WM_LBUTTONUP
如果包含的话:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP
非客户区鼠标消息:
系统一般不需要用户处理非客户区鼠标消息。用户只需要将这些消息发送给 DefWindowProc,从而使 Windows 执行系统函数。
消息的标识符包含了字母"NC",表示非客户区。如果鼠标在窗口的非客户区移动,窗口过程就会接收 WM_NCMOUSEMOVE 消息。
鼠标按钮产生的消息如下表所示:
| 按钮 | 按下 | 释放 | 第二次按下按钮 |
| 左键 | WM_NCLBUTTONDOWN | WM_NCLBUTTONUP | WM_NCLBUTTONDBLCLK |
| 中键 | WM_NCMBUTTONDOWN | WM_NCMBUTTONUP | WM_NCMBUTTONDBLCLK |
| 右键 | WM_NCRBUTTONDOWN | WM_NCRBUTTONUP | WM_NCRBUTTONDBLCLK |
非客户区鼠标消息的参数 wParam 和 lParam 与客户区鼠标消息的参数有些不同。
参数 wParam 表示非客户区鼠标移动或单击的位置。它的值被设定成一个以 HT 为 首的标识符。
参数 lParam 的低位字包含 x 坐标,高位字包含 y 坐标。这些都是屏幕坐标而不是客户区坐标。
击中测试消息:
WM_NCHITTEST 表示“非客户区击中测试”。这个消息的优先级高于其他所有客户区和非客户区鼠标消息。
Windows 应用程序通常会把这个消息发送给 DefWindowProc()。
然后 Windows 会利用 WM_NCHITTEST 消息来产生所有其他和鼠标位置相关的鼠标消息。
对非客户区消息来说,DefWindowProc() 处理 WM_NCHITTEST 消息后返回一个可用于鼠标消息参数 wParam 的值。
这个返回值可以是任何一个非客户区鼠标消息的 wParam 参数的值,也可以是如下所示的一些值:
| 值 | 意思 |
| HTCLIENT | 客户区 |
| HTNOWHERE | 不在任何窗口 |
| HTTRANSPARENT | 被另一个窗口覆盖的窗口 |
| HTERROR | 使函数 DefWindowProc 产生一个警示声 |
DEMO CODE:
#include <windows.h> #include <windowsx.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 = GET_X_LPARAM (lParam) / cxBlock ; y = GET_Y_LPARAM (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) ; }

浙公网安备 33010602011771号