摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P223

第 6 章已经讲到,Windows 只把键盘消息发送到当前具有输入焦点的窗口。鼠标消息则不同:当鼠标经过窗口或在窗口内被单击,则即使该窗口是非活动窗口或不带输入焦点,窗口过程还是会收到鼠标消息。Windows 定义了 21 种鼠标消息。不过,其中 11 种消息与客户区无关,称为“非客户区消息”。Windows 应用程序经常忽略这类消息。

当鼠标移经窗口客户区时,窗口过程接收 WM_MOUSEMOVE 消息。在窗口客户区内按下或释放鼠标按钮时,窗口过程接收如下表所示的消息:

按  钮 按  下 释  放 第二次按下按钮
 左键  WM_LBUTTONDOWN  WM_LBUTTONUP  WM_LBUTTONDBLCLK
 中键  WM_MBOTTONDOWN  WM_MBUTTONUP  WM_MBUTTONDBLCLK
 右键  WM_RBUTTONDOWN  WM_RBUTTONUP  WM_RBUTTONDBLCLK

窗口过程只对三键鼠标接收 MBUTTON 消息;只对双键鼠标接收 RBUTTON 消息。而只有当窗口类被定义成接收鼠标双击时,窗口过程才接收 DBLCLK(双击)消息。

对所有这些消息来说,参数 lParam 包含了鼠标的位置信息,其中低位字表示 x 坐标,高位字表示 y 坐标,它们都是相对于窗口客户区左上角的相对坐标。利用 LOWORD 宏和 HIWORD 宏,可以获取这些坐标值:

x = LOWORD (lParam);
y = HIWORD (lParam);

参数 wParam 表示鼠标按钮、Shift 键和 Ctrl 键的状态。可以利用 WINUSER.H 头文件中定义的位掩码来测试参数 wParam。前缀 MK 代表“鼠标键”(mouse key)。

MK_LBUTTON            按下左键
MK_MBUTTON            按下中键
MK_RBUTTON            按下右键
MK_SHIFT              按下 Shift 键
MK_CONTROL            按下 Ctrl 键

例如,当接收到 WM_LBUTTONDOWN 消息时,若

wParam & MK_SHIFT

的值为 TRUE(非零),则表示按下左键的同时按下了 Shift 键。

鼠标移经窗口的客户区时,Windows 系统不会为鼠标经过的每个像素位置都产生 WM_MOUSEMOVE 消息。程序收到的 WM_MOUSEMOVE 消息个数取决于鼠标硬件和窗口过程处理鼠标移动消息的速度。换言之,如果消息队列里还有未处理的 WM_MOUSEMOVE 消息,Windows 就不会重复向消息队列中添加该消息。试验下面这个 CONNECT 程序,可以对 WM_MOUSEMOVE 消息的产生速度有一个全面的了解。

若在非活动窗口的客户区内按下鼠标左键,Windows 会将该窗口变为活动窗口,并向窗口过程发送 WM_LBUTTONDOWN 消息。当窗口过程接收到 WM_LBUTTONDOWN 消息时,程序就能够安全地保证该窗口是活动窗口。但是,在事先没有接收 WM_LBUTTONDOWN 消息的情况下,窗口过程仍然可以接收 WM_LBUTTONUP 消息。比如,当用户在其他窗口内按下鼠标,再移动到用户窗口,然后释放,此时就会发生这种情况。类似地,当移动鼠标到另一个窗口再释放时,前一个窗口过程在接收 WM_LBUTTONDOWN 消息后,就接收不到相应的 WM_LBUTTONUP 消息。

前面这些规则有两个例外:

  • 即使鼠标位于窗口的客户区之外,窗口过程也有办法“捕获鼠标”,并且继续接收鼠标消息。本章会在后面讲述如何捕获鼠标。
  • 若正在显示一个系统模式消息框或系统模式对话框,则其他任何程序都不能接收鼠标消息。当系统模式消息框或对话框处于活动状态时,它们会阻止系统切换到另一个窗口。例如,关闭 Windows 时弹出的消息框就是一个系统模式消息框。

7.2.1  简单的鼠标处理示例

为了使用户对 Windows 系统向程序发送鼠标消息的机制有一个全面的了解,CONNECT 程序进行了一些简单的鼠标处理。

/*--------------------------------------------------------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 ("This 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 = 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) ;
}

CONNECT 程序处理以下三种鼠标消息。

  • WM_LBUTTONDOWN    CONNECT 程序情况客户区。
  • WM_MOUSEMOVE          如果按下左键,CONNECT 程序就在客户区的鼠标位置上画一个黑点,并保存点的坐标。
  • WM_LBUTTONUP           CONNECT 程序将客户区内每个点都与其他点相连。显示结果有时会呈现出漂亮的设计图案,有时会变成浓密的一团。(如图 7-2 所示。)

CONNECT 程序的操作方法如下:将鼠标指针移到客户区,按下左键,略微移动鼠标,再释放左键。在按下左键时快速移动鼠标,就可以得到一条经过多个点的曲线。

CONNECT 程序利用了三个在第 5 章讨论过的 GDI 函数:SetPixel函数在按下左键时为每个 WM_MOUSEMOVE 消息绘制一个黑色像素点。(在高分辨率显示设备中,人眼几乎看不见一个单独的像素点。)绘制直线则需要用到 MoveToEx 函数和 LineTo 函数。

在用户释放左键时,如果鼠标指针已经移出客户区,CONNECT 程序就不会连接这些点,因为程序没有接收到 WM_LBUTTONUP 消息。此时如果再将鼠标移入客户区,并按下左键,CONNECT 程序就是清空客户区。如果想在客户区外释放鼠标,并继续设计图形,就可以在客户区外按下鼠标的左键,再将鼠标移入客户区。

CONNECT 程序最多能够保存 1000 个点。假设点的数目为 P,那么在 CONNECT 程序中,所画线条的数目等于 P * (P - 1) / 2。对 1000 个点来说,几乎需要画 500 000 条直线。取决于具体的硬件,这可能要耗费大约 1 分钟的时间。Windows 98 是一个抢占式多任务环境,因此在这段时间里,用户可以切换到其他程序。但是,当 CONNECT 程序处于忙碌状态时,用户不能对 CONNECT 程序做其他的任何操作(比如移动窗口或调整大小)。在第 20 章中,我们将会讨论如何处理类似的这种问题。

CONNECT 程序需要耗费一定的时候来绘制直线,因此,鼠标指针会变成沙漏形,并在处理 WM_PAINT 消息时回到原来的形状。这就需要两次调用 SetCursor 函数来切换两个备用指针。CONNECT 程序还调用了两次 ShowCursor 函数,其中第一次调用时参数为 TRUE,第二次调用时参数为 FLASE。

有时,“跟踪”(tracking) 一次常被用来指代程序处理鼠标移动的方式。然而,跟踪并不意味着程序的窗口过程要使用一个循环来不停地主动监视鼠标在显示设备上的运动。相反,窗口过程只会被动地处理每个到达的鼠标消息,然后迅速退出并将控制返还给 Windows 系统。

7.2.2  处理Shift键

当 CONNECT 程序接收到 WM_MOUSEMOVE 消息时,程序会对参数 wParam 和 MK_LBUTTON 进行位于(AND)运算,从而判断是否按下了左键。利用参数 wParam 还可以判断 Shift 键的状态。例如,当处理过程依赖于 Shift 键和 Ctrl 键的状态时,可能会用到类似下面的逻辑处理:

if (wParam & MK_SHIFT)
{if (wParam & MK_CONTROL){[按下 Shift+Ctrl 组合键]} else {[按下 Shift 键]}
}
else
{if (wParam & MK_CONTROL){[按下 Ctrl 键]}else{[Shift 键和 Ctrl 键都没有被按下]}
}

如果想在程序中同时使用鼠标的左键和右键,同时又想兼顾那些使用单键鼠标的用户,那么可以这样编写代码,使左键配合 Shift 键等效于右键。这时,鼠标的按钮处理可能如下所示:

case WM_LBUTTONDOWN:if (!(wParam & MK_SHIFT)){[这里处理左键]return 0;}// 表示用户还按下了 Shift 键来模拟鼠标右键,所以交给下面的 WM_RBUTTONDOWN 代码处理
case WM_RBUTTONDOWN:[这里处理右键]return 0;   

利用虚拟键代码 VK_LBUTTON、VK_RBUTTON、BK_MBUTTON、VK_SHIFT 和 VK_CONTROL,Windows 的 GetKeyState 函数也能够返回鼠标按钮或 Shift 等键的状态。当 GetKeyState 函数 返回一个负值时,表示 已按下了鼠标按钮或相应的 Shift 键或 Ctrl 键。GetKeyState 函数返回的是当前鼠标或键盘的状态,因此,状态信息与正被处理的消息是完全同步的。对于键盘中未被按下的键,不能使用 GetKeyState 函数;同样,在没有按下鼠标时,也不能使用 GetKeyState 函数。所以,请不要像下面这样做:

while (GetKeyState (VK_LBUTTON) >= 0); // 这是错误的代码!

在消息的处理过程中,当调用 GetKeyState 函数时,只有在已经按下左键后,GetKeyState 函数才会报告左键被按下的状态。

7.2.3  鼠标双击

鼠标双击是指连续两次快速地单击。为了达到双击的效果,两次单击不仅要在物理位置上十分靠近(默认情况下,是一个平均系统字体字符宽度、半个字符高度的区域内)。还必须发生在特定的时间间隔之内。这个间隔称为“双击速度”。在控制面板中,可以改变这个时间间隔。

如果想让窗口过程接收鼠标双击消息,那么在调用 RegisterClass 初始化窗口类结构时,必须在窗口风格字段中包含标识符 CS_DBLCLKS:

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

如果窗口类型没有包含 CS_DBLCLKS,那么当用户连续两次快速单击左键时,窗口过程接收的消息顺序如下:

WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDOWN
WM_LBUTTONUP

也许窗口过程还会在这些按钮消息中间接接收其他消息。用户若想定义自己的双击处理函数,可以利用 Windows 的 GetMessageTime 函数,得到两个 WM_LBUTTONDOWN 消息之间的间隔时间长度。

若窗口类的风格包含 CS_DBLCLKS,那么双击鼠标后,窗口过程会接收到如下消息:

WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP

第二个 WM_LBUTTONDOWN 消息被简单地替换成 WM_LBUTTONDBLCLK 消息。

如果双击的第一次单击与鼠标单击所执行的功能一致,那么处理双击消息就要容易得多。这是,第二次单击(WM_LBUTTONDBLCLK 消息)只需在地刺单击之后执行一些其他操作。例如,考察对 Windows 资源管理器文件列表的鼠标操作。鼠标单击选中文件,此时 Windows 资源管理器反相显示该文件。鼠标双击时执行下面两步:第一次单击会选中文件,正如鼠标单击;第二次单击指示 Windows 资源管理器打开文件。这是多么简洁的逻辑处理操作。如果双击中第一次单击与鼠标所执行的功能不一致,那么鼠标的处理逻辑就要复杂得多。

7.2 客户区鼠标消息相关推荐

  1. Win32 鼠标消息 - 客户区鼠标消息、非客户区鼠标消息、击中测试、鼠标滚轮

    注:以下内容为学习笔记,多数是从书本.资料中得来,只为加深印象,及日后参考.然而本人表达能力较差,写的不好.因非翻译.非转载,只好选原创,但多数乃摘抄,实为惭愧.但若能帮助一二访客,幸甚! 注:以下内 ...

  2. 10 Windows编程——鼠标消息

    和鼠标相关的三个属于:click,double-click,drag 鼠标消息和键盘消息不同:只要鼠标跨越某个窗口,或者在某个窗口中按键,消息. 客户去鼠标消息 WM_MOUSEMOVE WM_[L, ...

  3. windows鼠标消息

    最开始为windows消息迷惑的时候是一个叫做sendMessage的函数: LRESULT SendMessage(   HWND hWnd,   UINT Msg,   WPARAM wParam ...

  4. windows主要鼠标消息

    windows主要鼠标消息 WM_LBUTTONDOWN 鼠标停留在客户区,同时鼠标左键按下 WM_LBUTTONUP   鼠标左键释放 WM_RBUTTONDOWN   鼠标停留在客户区,邮件按下 ...

  5. WINDOWS 逻辑坐标 设备坐标 屏幕坐标 客户区坐标

    WINDOWS 逻辑坐标 设备坐标 屏幕坐标 客户区坐标 收藏 设置坐标映射 (1)Windows坐标系统 Windows坐标系分为逻辑坐标系和设备坐标系两种,GDI支持这两种坐标系.一般而言, GD ...

  6. 【转】windows程序设计(14):鼠标消息详解

    关于鼠标的一些细节知识: 通常,我们发消息时,都是对一个特定的窗口,但是对于鼠标消息却不然:只要鼠标跨越窗口或者在某窗口中按下鼠标按键,那么窗口消息处理程序就会收到鼠标消息,而不管该窗口是否活动或者是 ...

  7. 窗口非客户区绘制位图与设计

    原文:http://www.haogongju.net/art/1891934 首先介绍在指定区域绘制位图 为了能够在指定的区域中输出位图,需要使用设备上下文CDC类的StretchBlt方法.由于我 ...

  8. Visual C++游戏编程基础之利用鼠标消息实现贴图

    一.鼠标键入消息 1.WM_LBUTTONDBLCLK       双击鼠标左键: 2.WM_LBUTTONDOWN         单击鼠标左键: 3.WM_LBUTTONUP            ...

  9. Win32学习(七) 鼠标消息

    基本鼠标消息 WM_LBUTTONDOWN - 鼠标左键按下 WM_LBUTTONUP - 鼠标左键抬起 WM_RBUTTONDOWN - 鼠标右键按下 WM_RBUTTONUP - 鼠标右键抬起 W ...

最新文章

  1. python分数运算使用Fraction模块
  2. android碎片调用动态碎片,Android Training - 使用碎片创建一个动态UI
  3. 问题小结(2)-dialog内容动态变化(调用系统方法时)
  4. rar 文件头crc版本_php实现rar文件的读取和解压
  5. Linux学习总结(14)——Linux权限控制
  6. P4219-[BJOI2014]大融合【LCT】
  7. 从生产故障解锁RocketMQ集群部署的最佳实践
  8. Android-support-v4源码查看
  9. 串口接收到的字符型数据如何转化成数字
  10. Docker生产环境配置——设置direct-lvm模式
  11. 远程登录服务器时,提示未被授予终端服务器登录权限?
  12. BT6无线破解+局域网共享(附BT6下载地址)
  13. python制作圣诞贺卡_制作这种动态圣诞节电子贺卡,美炸了!
  14. R语言 kNN 对鸢尾花进行分类
  15. 异步FIFO设计原理及Verliog源代码
  16. 使用HTML5实现网页音乐播放器
  17. Android12之fatal error: ‘processinfo/ProcessInfoService.h‘ file not found
  18. 【jQuery】实现跑马灯
  19. 阿里云OCR图片识别
  20. 电子电路硬件学习目标

热门文章

  1. 请问C#中 if(fonrdlg .ShowDialog () ==DialogResult .OK )怎么理解
  2. 『转贴』当中国和美国的32对灵魂在阴曹地府相遇.....
  3. Mac XCode链接苹果手机iPhone总是不断连接断开、断断续续的解决方法
  4. c语言冰雹猜想用while,c/c++开发分享P5727 冰雹猜想
  5. 嵌入式C编程之错误处理(附代码例子)!
  6. Java项目:手机商城管理系统(java+JSP+bootstrap+servlet+Mysql)
  7. mac远程登录控制linux,Mac下远程控制Linux
  8. 嘉祥计算机学校电话号码,嘉祥县职业中专
  9. 关于《流浪地球》炸裂的口碑,机器学习竟然是这样评价的————Python文本情感极性分析详解(下)
  10. angular11实现自定义语音播放器