关于鼠标的一些细节知识:
通常,我们发消息时,都是对一个特定的窗口,但是对于鼠标消息却不然:只要鼠标跨越窗口或者在某窗口中按下鼠标按键,那么窗口消息处理程序就会收到鼠标消息,而不管该窗口是否活动或者是否拥有输入焦点。鼠标消息一个有21种:10个显示区域消息,11个非显示区域消息
显示区域鼠标消息
当鼠标移过窗口的显示区域时,窗口消息处理程序收到WM_MOUSEMOVE消息。
当在窗口的显示区域中按下或者释放一个鼠标按键时,窗口消息处理程序会接收到下面这些消息:
键 | 按下 | 释放 | 按下(双键) |
左 | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK |
中 | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBLCLK |
右 | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_RBUTTONDBLCLK |
只有对三键鼠标,窗口消息处理程序才会收到MBUTTON消息。
对于这些消息,其lParam值均含有鼠标的位置:低字字节为x坐标,高字节为y坐标,这两个坐标是相对于窗口显示区域左上角的位置。您可以用LOWORD和HIWORD宏来提取这些值:
x = LOWORD (lParam) ;
y = HIWORD (lParam) ;
wParam的值指示鼠标按键以及Shift和Ctrl键的状态:
MK_LBUTTON | 按下左键 |
MK_MBUTTON | 按下中键 |
MK_RBUTTON | 按下右键 |
MK_SHIFT | 按下Shift键 |
MK_CONTROL | 按下Ctrl键 |
举个例子,如果收到了WM_LBUTTONDOWN且wparam & MK_SHIFT为真,则说明左键按下时也按下了Shift键。
这里要强调一下:
1.WM_MOUSEMOVE消息:当您把鼠标移过窗口的显示区域时,Windows并不为鼠标的每个可能的图素位置都产生一个WM_MOUSEMOVE消息。您的程序接收到WM_MOUSEMOVE消息的次数,依赖于鼠标硬件,以及您的窗口消息处理程序在处理鼠标移动消息时的速度。换句话说,Windows不能用未处理的WM_MOUSEMOVE消息来填入消息队列。
2.对于WM_LBUTTONDOWN和WM_LBUTTONUP消息可能只收到一个!比如在一个窗口中按下鼠标按键,然后移动到使用者窗口释放它,就会出现这种情况。类似的情况,当鼠标按键在另一个窗口中被释放时,窗口消息处理程序只能接收到WM_LBUTTONDOWN消息,而没有相应的WM_LBUTTONUP消息。
3.关于双击鼠标:双击的间隔在控制面板里可以设置。但是是否需要相应“双击”则是我们自己写的:在注册窗口时,必须在窗口风格中包含CS_DBLCLKS标识符。这样当你双击时,系统会收到:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP
否则,只会收到:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDOWN
WM_LBUTTONUP
看一个例子程序:
/*--------------------------------------------------CONNECT.C -- Connect-the-Dots Mouse Demo Program(c) Charles Petzold, 1998--------------------------------------------------*/#include <windows.h>#define MAXPOINTS 1000LRESULT 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://是否按下了左键且收到的点数不到1000if (wParam & MK_LBUTTON && iCount < 1000){//记录鼠标位置并在最后iCount+1pt[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) ;}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) ;
}
程序比较简单,就不多说了。
那什么是非显示区域的鼠标消息呢?如果鼠标在窗口的显示区域之外但还在窗口内,Windows就给窗口消息处理程序发送一条“非显示区域”鼠标消息。窗口非显示区域包括标题列、菜单和窗口滚动条。
这些消息通常我们是不用管的,将这些消息传给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参数的低位word为x坐标,高位word为y坐标,但是,它们是屏幕坐标。
最后一个消息是WM_NCHITTEST,它代表“非显示区域命中测试”,Windows应用程序通常把这个消息传送给DefWindowProc,系统会自动的判断你拖动的是标题栏还是边框而做出相应。
MFC一个解决自己发送WM_NCLBUTTONDOWN消息后收不到WM_ONLBUTTONUP的方法
MFC解决自己发送WM_NCLBUTTONDOWN消息收不到WM_LBUTTONUP的方法
最近在做一个MFC拖动窗口的程序,在网上搜到了一个方法,在OnLButtonDown里自己发送
PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x,point.y));
消息。这样一来确实能够在客户区移动窗口了,但是有一个问题:WM_LBUTTONUP消息被忽略了,写在OnLButtonUp里的内容根本没执行。在网上搜解决方法,要么是太复杂,要么没有很好的解决。最后自己试出了一个方法,经测试可行:
在OnNcLButtonDown函数里完成父类函数后自己发送一个WM_ONLBUTTONUP消息。
如下(CWnd应该换成你自己继承的父类,因为我直接从CWnd类继承过来 的,所以是CWnd):
1.处理左键按下消息函数:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point)
{RedrawWindow();Invalidate();//支持拖动PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x,point.y));CWnd::OnLButtonDown(nFlags, point);
}
这样就发送了非客户区左键按下的消息WM_NCLBUTTONDOWN。
2.处理非客户区左键按下消息函数:
afx_msg void OnNcLButtonDown(UINT nHitTest, CPoint point)
{CWnd::OnNcLButtonDown(nHitTest,point);PostMessage(WM_LBUTTONUP, NULL, NULL);
}
PostMessage(WM_LBUTTONUP, NULL, NULL)这一句就是自己发送左键放开的消息WM_LBUTTONUP。
根据这个结果,本人猜测CWnd::OnNcLButtonDown可能是要一直执行到鼠标放开时才结束,WM_LBUTTONUP被忽略了。这导致我们收不到WM_LBUTTONUP消息,所以在其后自己发送WM_LBUTTONUP后就能收到WM_LBUTTONUP消息了。
注意:
这个方法鼠标左键按下事件是有一秒左右延迟的,当把窗口移动到其他窗口上或控件上,松开再单击时可能会单击到那些窗口或控件,大家若有好的方法解决可以发下言。