7.2 客户区鼠标消息
当鼠标移过窗口的显示区域时,窗口消息处理程序收到WM_MOUSEMOVE消息。当在窗口的显示区域中按下或者释放一个鼠标按键时,窗口消息处理程序会接收到下面这些消息:
键 |
按下 |
释放 |
按下(双键) |
左 |
WM_LBUTTONDOWN |
WM_LBUTTONUP |
WM_LBUTTONDBLCLK |
中 |
WM_MBUTTONDOWN |
WM_MBUTTONUP |
WM_MBUTTONDBLCLK |
右 |
WM_RBUTTONDOWN |
WM_RBUTTONUP |
WM_RBUTTONDBLCLK |
要接受到双键消息,需要在wndclass.style处增加CS_DBLCLKS
在WM_MOUSEMOVE中,可以用LOWORD和HIWORD宏来提取鼠标的位置:
x = LOWORD (lParam) ; y = HIWORD (lParam) ;
wParam中包含信息。MK前缀代表「鼠标按键」。
MK_LBUTTON |
按下左键 |
MK_MBUTTON |
按下中键 |
MK_RBUTTON |
按下右键 |
MK_SHIFT |
按下Shift键 |
MK_CONTROL |
按下Ctrl键 |
当您把鼠标移过窗口的显示区域时,Windows并不为鼠标的每个可能的图素位置都产生一个WM_MOUSEMOVE消息。您的程序接收到WM_MOUSEMOVE消息的次数,依赖于鼠标硬件,以及您的窗口消息处理程序在处理鼠标移动消息时的速度。
- 窗口消息处理程序可以「拦截鼠标」并且连续地接收鼠标消息,即使此时鼠标在该窗口显示区域之外。您将在本章的后面学习如何拦截鼠标。
- 如果正在显示一个系统模态消息框或者系统模态对话框,那么其它程序就不能接收鼠标消息。当系统模态消息框或者对话框活动时,禁止切换到其它窗口或者程序。一个显示系统模态消息框的例子,是当您关闭Windows时。
实例
#include<windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("Mouse Move"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.hInstance = hInstance; wndclass.lpfnWndProc = WndProc; wndclass.lpszClassName = szAppName; wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.lpszMenuName = 0; if (!RegisterClass(&wndclass)) { MessageBox(NULL, TEXT("错误, 无法注册窗口类."), TEXT("错误"), MB_OK); return 0; } hwnd = CreateWindow(szAppName, TEXT("Mouse Move"), 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[1000]; HDC hdc; static int iCount; PAINTSTRUCT ps; int i, j; 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 = LOWORD(lParam); pt[iCount++].y = HIWORD(lParam); hdc = GetDC(hwnd); SetPixel(hdc, LOWORD(lParam), HIWORD(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); } } SetCursor(LoadCursor(NULL, IDC_ARROW)); ShowCursor(FALSE);//隐藏鼠标 EndPaint(hwnd, &ps); } return DefWindowProc(hwnd, message, wParam, lParam); }
7.2.2 处理shift
组合键测试
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_MOUSEMOVE: if (wParam & MK_CONTROL) { if (wParam & MK_SHIFT) { MessageBox(hwnd, TEXT("按下Ctrl+Shift键"), TEXT("test!"), NULL); return 0; } } } return DefWindowProc(hwnd, message, wParam, lParam); }
7.2.3 鼠标双击
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_LBUTTONDBLCLK: MessageBox(hwnd, TEXT("左键双击"), TEXT("test!"), NULL); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
客户区鼠标消息简单应用
#include<windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("Mouse Move"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.hInstance = hInstance; wndclass.lpfnWndProc = WndProc; wndclass.lpszClassName = szAppName; wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.lpszMenuName = 0; if (!RegisterClass(&wndclass)) { MessageBox(NULL, TEXT("错误, 无法注册窗口类."), TEXT("错误"), MB_OK); return 0; } hwnd = CreateWindow(szAppName, TEXT("Mouse Move"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 600, 600, 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 int cxClient, cyClient; static POINT point; HDC hdc; PAINTSTRUCT ps; TCHAR szBuffer[100]; switch (message) { case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; case WM_MOUSEMOVE: GetCursorPos(&point); InvalidateRect(hwnd, NULL, TRUE); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); TextOut(hdc, cxClient / 2 - 30, cyClient / 2 - 30, szBuffer, wsprintf(szBuffer, TEXT("屏幕坐标(%d, %d)"), point.x, point.y)); ScreenToClient(hwnd, &point); TextOut(hdc, cxClient / 2 - 30, cyClient / 2 , szBuffer, wsprintf(szBuffer, TEXT("客户区坐标(%d, %d)"), point.x, point.y)); EndPaint(hwnd, &ps); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
7.3 非客户区鼠标消息
键 |
按下 |
释放 |
按下(双击) |
左 |
WM_NCLBUTTONDOWN |
WM_NCLBUTTONUP |
WM_NCLBUTTONDBLCLK |
中 |
WM_NCMBUTTONDOWN |
WM_NCMBUTTONUP |
WM_NCMBUTTONDBLCLK |
右 |
WM_NCRBUTTONDOWN |
WM_NCRBUTTONUP |
WM_NCRBUTTONDBLCLK |
可以用两个Windows函数将屏幕坐标转换为显示区域坐标或者反之:
ScreenToClient (hwnd, &pt) ; ClientToScreen (hwnd, &pt) ;
7.3.1 击中测试消息
WM_NCHITTEST,它代表「非显示区域命中测试」。此消息优先于所有其它的显示区域和非显示区域鼠标消息。lParam参数含有鼠标位置的x和y屏幕坐标,wParam 参数另有用途。
Windows应用程序通常把这个消息传送给DefWindowProc,然后Windows用WM_NCHITTEST消息产生与鼠标位置相关的所有其它鼠标消息。对于非显示区域鼠标消息,在处理WM_NCHITTEST时,从DefWindowProc传回的值将成为鼠标消息中的wParam参数,这个值可以是任意非显示区域鼠标消息的wParam值再加上以下内容:
HTCLIENT HTNOWHERE HTTRANSPARENT HTERROR |
显示区域 不在窗口中 窗口由另一个窗口覆盖 使DefWindowProc产生警示用的哔声 |
如果DefWindowProc在其处理WM_NCHITTEST消息后传回HTCLIENT,那么Windows将把屏幕坐标转换为显示区域坐标并产生显示区域鼠标消息。
#define HTERROR (-2) //在屏幕的后面或在窗体之间的线上(使函数DefWindowProc产生一个警示音) #define HTTRANSPARENT (-1) //在一个被其它窗口覆盖的窗口中 #define HTNOWHERE 0 //在屏幕背景或窗口之间的分界线 #define HTCLIENT 1 //在客户区中 #define HTCAPTION 2 //在标题栏中 #define HTSYSMENU 3 //在一个窗口菜单栏或子窗口的关闭按钮上 #define HTGROWBOX 4 //在尺寸框中 #define HTSIZE HTGROWBOX //同HTGROWBOX #define HTMENU 5 //在菜单区域 #define HTHSCROLL 6 //在水平滚动条上 #define HTVSCROLL 7 //在垂直滚动条上 #define HTMINBUTTON 8 //在最小化按钮上 #define HTMAXBUTTON 9 //在最大化按钮上 #define HTLEFT 10 //在窗口的左边框上 #define HTRIGHT 11 //在窗口的右边框上 #define HTTOP 12 //在窗口水平边框的上方 #define HTTOPLEFT 13 //在窗口边框的左上角 #define HTTOPRIGHT 14 //在窗口边框的右上角 #define HTBOTTOM 15 //在窗口的水平边框的底部 #define HTBOTTOMLEFT 16 //在窗口边框的左下角 #define HTBOTTOMRIGHT 17 //在窗口边框的右下角 #define HTBORDER 18 //在不具有可变大小边框的窗口的边框上 #define HTREDUCE HTMINBUTTON //同HTMINBUTTON #define HTZOOM HTMAXBUTTON //同HTMAXBUTTON #define HTSIZEFIRST HTLEFT //同HTLEFT #define HTSIZELAST HTBOTTOMRIGHT //同HTBOTTOMRIGHT #define HTOBJECT 19 //忽略该标识符, 已废弃 #define HTCLOSE 20 //在关闭按钮上 #define HTHELP 21 //在帮助按钮上
#include<windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("Mouse Move"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.hInstance = hInstance; wndclass.lpfnWndProc = WndProc; wndclass.lpszClassName = szAppName; wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.lpszMenuName = 0; if (!RegisterClass(&wndclass)) { MessageBox(NULL, TEXT("错误, 无法注册窗口类."), TEXT("错误"), MB_OK); return 0; } hwnd = CreateWindow(szAppName, TEXT("Mouse Move"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 600, 600, 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) { TCHAR szBuffer[100]; static POINT pt; switch (message) { case WM_NCLBUTTONDOWN: pt.x = LOWORD(lParam); pt.y = HIWORD(lParam); ScreenToClient(hwnd, &pt); switch (wParam) { case HTCAPTION: wsprintf(szBuffer, TEXT("击中标题栏,客户区坐标(%d, %d)"), pt.x, pt.y); MessageBox(hwnd, szBuffer, TEXT("test"), NULL); break; case HTMINBUTTON: wsprintf(szBuffer, TEXT("击中最小化,客户区坐标(%d, %d)"), pt.x, pt.y); MessageBox(hwnd, szBuffer, TEXT("test"), NULL); break; case HTMAXBUTTON: wsprintf(szBuffer, TEXT("击中最大化,客户区坐标(%d, %d)"), pt.x, pt.y); MessageBox(hwnd, szBuffer, TEXT("test"), NULL); break; } return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }