客户区鼠标消息

由上一回我们得知Windows只把键盘消息发送给拥有输入焦点的窗口,而鼠标消息与此不同:只要鼠标跨越窗口或者在某窗口下按下鼠标键,那么窗口过程就会收到鼠标消息,不管该窗口是否活动或者是否拥有输入焦点。

当在窗口的客户区中按下或者释放一个鼠标按键时,窗口过程会接收到下面这些消息:

按下

释放

按下(双键)

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

WM_MBUTTONDOWN

WM_MBUTTONUP

WM_MBUTTONDBLCLK

WM_RBUTTONDOWN

WM_RBUTTONUP

WM_RBUTTONDBLCLK

对于三键鼠标,窗口过程才会收到MBUTTON消息(当然现在这种鼠标很不常见了,常见的是两键一轮的鼠标,你会发现鼠标轮除了滚动还可以点击,点击鼠标轮就会产生三键鼠标点击中键的效果,同样会产生MBUTTON的消息)。

仅当定义的窗口类能接收DBLCLK(双击)消息之后,窗口过程才能接受这类消息。既然说到此我们就先来看一下关于鼠标双击的消息。鼠标双击是指在短时间内单击鼠标键两次。要确定为双击,这两次单击必须发生在其相互的物理位置十分接近的状况下,并且发生在指定的时间间隔内。你可以在“控制面板”中改变时间间隔。

一般窗口过程是接收不到双击键的鼠标消息的,如果希望窗口过程能够收到这种消息,那么在调用RegisterClass初始化窗口类结构时,必须在窗口风格中包含CS_DBLCLKS标识符:

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

如果在窗口风格中未包含CS_DBLCLKS,而用户在短时间内双击了鼠标键,那么窗口过程会接收到下面这些消息:

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDOWN

WM_LBUTTONUP

如果你的窗口类别风格中包含了CS_DBLCLKS,那么双击鼠标键时窗口过程将收到如下消息:

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

WM_LBUTTONUP

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

双击中的第一次单击操作完成单击的功能,第二次单击操作(WM_LBUTTONDBLCLK消息)则完成第一次按键以外的事情。看一个双击的“慢动作回放”:例如,看看“Windows 资源管理器”中是如何用鼠标来操作文件列表的。第一次单击操作将选中文件,“Windows资源管理器”用反白显示出被选择的文件。第二次单击操作则指示“Windows 资源管理器”打开该文件。

别怪我啰嗦,再举一个单击的例子吧。如果你在非活动窗口的客户区中按下鼠标左键,Windows将把活动窗口改为在其中按下鼠标按键的这个窗口,然后把WM_LBUTTONDOWN消息送到该窗口的窗口过程。当释放鼠标左键时,则Windows再把WM_LBUTTONUP消息送到该窗口的窗口过程。常规情况下,BUTTONUP与BUTTONDOWN消息会成对出现在一个窗口中,但也会有例外。如果鼠标按键在另一个窗口中被释放,则这个窗口的窗口过程只能接收到WM_LBUTTONDOWN消息,而没有相应的WM_LBUTTONUP消息。同理,另一个窗口的窗口过程在未接收到WM_LBUTTONDOWN消息的情况下却先接收到了WM_LBUTTONUP消息。当然这个例子有点“极端”,但确实存在这种情况。

我们再来认识一个鼠标消息WM_MOUSEMOVE。当鼠标移过窗口的客户区,窗口过程就会收到一系列此消息。当然在你把鼠标移过客户区时,Windows并不为鼠标经过的每个可能的像素位置都产生一条WM_MOUSEMOVE消息。你的程序接收到WM_MOUSEMOVE消息的次数依赖于鼠标硬件以及你的窗口过程处理鼠标移动消息的速度。

对于上面这10个消息来说,其lParam值均含有鼠标的位置:低位字为x坐标,高位字为y坐标,这两个坐标是相对于窗口客户区左上角的位置。您可以用LOWORD和HIWORD宏来提取这些值:

x = LOWORD (lParam) ;

y = HIWORD (lParam) ;

wParam的值指示鼠标按键以及Shift和Ctrl键的状态。你可以使用头文件WINUSER.H中定义的与运算来测试wParam。(如果忘了lParam和wParam,就去复习一下第四回中消息结构体的讲解)

MK_LBUTTON

按下左键

MK_MBUTTON

按下中键

MK_RBUTTON

按下右键

MK_SHIFT

按下Shift键

MK_CONTROL

按下Ctrl键

MK前缀代表“鼠标按键”。

例如,如果收到了WM_LBUTTONDOWN消息,而且值

wparam &MK_SHIFT

是TRUE(非0),你就知道当左键按下时也按下了Shift键。

再来看一个例子,如果在程序中一种功能的实现依赖于鼠标单击和Shift、Ctrl键的组合,可以使用如下方法:

if (wParam & MK_SHIFT)
{
 if (wParam & MK_CONTROL)
 {
 //按下了Shift和Ctrl键
 }
 else
 {
 //只按下了Shift键
 }
}
 else
 {
 if (wParam & MK_CONTROL)
 {
 //只按下了Ctrl键
 }
 else
 {
 //Shift和Ctrl键均未按下
 }
}

非客户区鼠标消息

在窗口的客户区内移动或按下鼠标按键时,将产生前面讲的10种消息。如果鼠标在窗口的客户区之外但还在窗口内,Windows就给窗口过程发送一条“非客户区”鼠标消息。窗口非客户区包括标题栏、菜单和窗口滚动条。

通常,我们不需要处理非客户区鼠标消息,而是将这些消息传给DefWindowProc,从而使Windows执行系统功能。就这方面来说,非客户区鼠标消息类似于系统键盘消息WM_SYSKEYDOWN、WM_SYSKEYUP和WM_SYSCHAR。

非客户区鼠标消息几乎完全与显示区域鼠标消息相对应。消息中含有字母“NC”以表示是非客户区消息。

如果鼠标在窗口的非客户区中移动,那么窗口过程会接收到WM_NCMOUSEMOVE消息。

鼠标按键会产生如下表所示的消息。

按下

释放

按下(双击)

WM_NCLBUTTONDOWN

WM_NCLBUTTONUP

WM_NCLBUTTONDBLCLK

WM_NCMBUTTONDOWN

WM_NCMBUTTONUP

WM_NCMBUTTONDBLCLK

WM_NCRBUTTONDOWN

WM_NCRBUTTONUP

WM_NCRBUTTONDBLCLK

对非客户区的10个鼠标消息,wParam和lParam参数与客户区的10个鼠标消息的wParam和lParam参数有一定的差别。

wParam参数指明移动或者按鼠标按键的非客户区位置。它设定为以HT开头的标识符之一(HT表示 “命中测试”)。

lParam参数的低位字为x坐标,高位字为y坐标,但是,它们都是屏幕坐标,而不是像客户区鼠标消息那样指的是客户区坐标。①

命中测试消息

这个消息是WM_NCHITTEST,它代表“非客户区命中测试”。从它的名字来看它应该在“非客户区鼠标消息”中讲才对呀,既然我把它单拿出来,自然是有道理的。它的作用比较“特殊”,而且要讲的内容比较多。

当光标移动或鼠标按下、释放时,Windows系统就会发送此消息,并且此消息优先于其它的客户区和非客户区鼠标消息。这里的“优先于”怎么讲?就是Windows用WM_NCHITTEST消息产生所有其它的鼠标消息,这种由消息引出其它消息的思想在Windows中是很普遍的。(想一想我们在第四回是不是曾经遇到过?)Windows用这个消息来做什么?  “HITTEST”就是“命中测试”的意思,WM_NCHITTEST消息用来获取鼠标当前命中的位置。WM_NCHITTEST的消息响应函数会根据鼠标当前的坐标来判断鼠标命中了窗口的哪个部位,消息响应函数的返回值指出了部位。再补充一点,此消息的lParam 参数含有鼠标位置的x和y屏幕坐标,wParam参数没有用。

为了便于理解,我先描述一下Windows对鼠标键按下的响应流程:

1. 确定鼠标键点击的是哪个窗口。Windows会用表记录当前屏幕上各个窗口的区域坐标,当鼠标驱动程序通知Windows鼠标键按下了,Windows根据鼠标的坐标确定它点击的是哪个窗口。

2. 确定鼠标键点击的是窗口的哪个部位。Windows会向鼠标键点击的窗口发送WM_NCHITTEST消息,来询问鼠标键点击的是窗口的哪个部位。窗口程序通常把这个消息传送给DefWindowProc默认处理,然后Windows用WM_NCHITTEST消息产生与鼠标位置相关的所有其它鼠标消息。具体地说,就是在处理WM_NCHITTEST消息时,从DefWindowProc返回的值将成为其它鼠标消息中的wParam参数。一般来说,WM_NCHITTEST消息是系统来处理的,我们用户一般不用去主动去处理它。

3. 根据鼠标键点击的部位给窗口发送相应的消息。例如:如果WM_NCHITTEST的消息响应函数的返回值是HTCLIENT,表示鼠标点击的是客户区,同时Windows将把屏幕坐标转换为客户区坐标并产生相应的客户区鼠标消息,在这里就是Windows向窗口发送WM_LBUTTONDOWN消息;如果WM_NCHITTEST的消息响应函数的返回值不是HTCLIENT(比如说是HTCAPTION),即鼠标点击的是非客户区,Windows就会向窗口发送wParam等于HTCAPTION的WM_NCLBUTTONDOWN消息。

其返回值有很多,现在简单列举一部分吧,仅供参考。

HTNOWHERE -不在窗口中

HTCLIENT - 客户区

HTCAPTION - 标题

HTSYSMENU - 系统菜单

HTMENU - 菜单

HTHSCROLL - 水平滚动条

HTVSCROLL - 垂直滚动条

HTMINBUTTON - 最小化按钮

HTMAXBUTTON - 最大化按钮

HTLEFT - 左边界

HTRIG - 右边界

HTTOP - 上边界

HTTOPLEFT - 左上角

HTTOPRIG - 右上角

HTBOTTOM - 下边界

HTBOTTOMLEFT - 左下角

HTBOTTOMRIG- 右下角

HTCLOSE- 关闭按钮

再来看个实际例子吧,方便大家理解一下上面的内容。

大家想一下我们如何来屏蔽鼠标键的操作,让其失效?我们上面讲过“Windows用WM_NCHITTEST消息产生所有其它的鼠标消息”,我们可以在窗口过程中加入以下语句:

case WM_NCHITTEST:

return (LRESULT)HTNOWHERE;//由于窗口过程返回值是LRESULT类型的,这里

//行了强制类型转换

我们把第二回的代码模板中“caseWM_CREATE”语段和“case WM_PAINT”语段(行59-68)删掉,加入上面的语段,运行一下。是不是发现鼠标对窗口(包括客户区和非客户区)所有的点击、拖动操作都失效了,因为我们截获了WM_NCHITTEST消息,返回HTNOWHERE ,“欺骗”操作系统这时鼠标“不在”窗口中的,虽然鼠标确实在窗口中。再来讲一个“欺骗”的例子吧。一般我们鼠标单击一个窗口标题栏的时才可拖动窗口移动,如果我们要想实现鼠标只要单击窗口任一个位置就可以拖动窗口的功能,这该怎么办呢?照葫芦画瓢即可:

case WM_NCHITTEST:

return (LRESULT)HTCAPTION;

这样只要你在窗口中任意位置单击鼠标,系统就会由WM_NCHITTEST消息引发wParam 值标志标题栏的WM_NCLBUTTONDOWN消息,这时当前窗口将处于 “拖拽状态”(Windows内部记录了每个窗口的状态信息)。由于标识了“拖拽状态”,则从此刻起到鼠标键放开之前,你的鼠标移动状况完全由Windows跟踪。它根据鼠标的移动,使得窗口作“同步”移动。

注意,这个过程中,窗口不会收到WM_NCMOUSEMOVE消息,因为窗口和鼠标是“同步”移动的,你的鼠标相对于窗口是静止的。

注:但问题同时也出现了, 我想在右击这个窗体客户区的时候弹出一个菜单,当我完成 WM_RBUTTONDOWN 这个消息的时候,发现窗体收不到这个消息, 将WM_NCHITTEST消息的实现去掉就可以了,看了一原因是:

因为你在WM_NCHITTEST中处理了鼠标消息,把它定位成HTCAPTION,也就是鼠标在标题栏上,而标题栏属于非客户区(NC);非客户区的事件消息都是以WM_NC开头的。也就是说,当你的WM_NCHITTEST返回HTCAPTION时,原来可以用WM_LBUTTONDOWN处理的消息,你只能用WM_NCLBUTTONDOWN来处理。

解决方法:同时处理WM_NCHITTEST和WM_NCRBUTTONDOWN,而不处理WM_RBUTTONDOWN。

最后一个鼠标消息

我们平时常用的鼠标轮来移动滚动条,当鼠标轮转动时就会产生WM_MOUSEWHEEL消息。关于这个消息我将在以后和滚动条一起讲。

①对屏幕坐标,显示器左上角的x和y的值为0。当往右移时x的值增加,往下移时y的值增加。

你可以用两个Windows函数将屏幕坐标转换为客户区坐标或者反之:

ScreenToClient (hwnd, &pt) ;

ClientToScreen (hwnd, &pt) ;

这里pt是POINT结构。这两个函数转换了保存在结构中的值,而且没有保留以前的值。注意,如果屏幕坐标点在窗口客户区的上面或者左边,客户区坐标x或y值就是负值。

Windows编程 鼠标的更多相关文章

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

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

  2. 学习windows编程 day6 之处理鼠标移动

    #define POINT_MAX 1000 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lPara ...

  3. 【Windows编程】系列第六篇:创建Toolbar与Statusbar

    上一篇我们学习了解了如何使用Windows GDI画图,该应用程序都是光光的静态窗口,我们使用Windows应用程序,但凡稍微复杂一点的程序都会有工具栏和状态栏,工具栏主要用于一些快捷功能按钮.比如典 ...

  4. 【Windows编程】系列第十篇:文本插入符

    大家知道,在使用微软的编程环境创建工程时会让你选择是控制台模式还是Windows应用程序.如果选择控制台的console模式,就会在运行时出现一个黑洞洞的字符模式窗口,里面就有等待输入一闪一闪的插入符 ...

  5. 【Windows编程】系列第七篇:Menubar的创建和使用

    上一篇我们学习了利用windows API创建工具栏和菜单栏,与上一篇紧密联系的就是菜单栏,菜单栏是一个大多数复杂一些的Windows应用程序不可或缺的部分.比如下图就是Windows自带的记事本的菜 ...

  6. 【Windows编程】系列第九篇:剪贴板使用

    上一篇我们学习了常见的通用对话框,本篇来了解剪贴板的使用,它常用于复制粘贴功能. 剪贴板是Windows最早就加入的功能,由于该功能非常实用,我们几乎每天都会使用到.通过剪贴板,我们就可以将数据从一个 ...

  7. 【Windows编程】系列第三篇:文本字符输出

    上一篇我们展示了如何使用Windows SDK创建基本控件,本篇来讨论如何输出文本字符. 在使用Win32编程时,我们常常要输出文本到窗口上,Windows所有的文本字符或者图形输出都是通过图形设备接 ...

  8. Windows编程入门程序详解

    引用:http://blog.csdn.net/jarvischu/article/details/8115390 1.     程序 /******************************* ...

  9. MFC-01-Chapter01:Hello,MFC---1.1 Windows 编程模型

    1.1 Windows编程模型 为传统的操作系统编写的程序使用的是过程化模型,即程序从头到尾按顺序执行.例如C程序,从main函数入口开始执行,中间调用不同的函数一直到程序结束返回,这种过程是程序本身 ...

随机推荐

  1. Java排序之计数排序

    Java排序之计数排序 计数排序思路 计数排序适用于有明确范围的数组,比如给定一个数组,且知道所有值得范围是[m,n].这个时候可以使用一个n-m+1长度的数组,待排序的数组就可以散在这个数组上,数组 ...

  2. pwn学习日记Day15 《程序员的自我修养》读书笔记

    程序编译链接过程: 1.调用cc1程序,这个程序实际上就是GCC的C语言编译器,它将"hello.c"编译成一个临时的汇编文件"/tmp/ccUhtGSB.s" ...

  3. java 测试框架

    项目开发过程中使用的单元测试框架有Junit.TestNG以及Mockito,Junit和TestNG使用的比较多,Mockito最近才开始使用. TestNG与JUnit的相同点 1. 使用anno ...

  4. web前端——Vue.js基础学习

    近期项目的前端页面准备引入Vue.js,看了网上一些简介,及它和JQuery的对比,发现对于新入门的前端开发来说,Vue 其实也是比较适用的一个框架,其实用性不比JQuery差,感觉还挺有意思,于是研 ...

  5. Android开发final的用法

    Android开发final的用法   final如果修饰类,该类不能被继承: final如果修饰变量,该变量不能被修改,不能再重新赋值,即变为常量: final如果修饰方法,该方法不能被重写: 此外 ...

  6. kotlin陪伴对象

    在kotlin中并没有静态类成员的概念,但并不等于实现类似于静态类成员的功能,陪伴对象可以解决这个问题 fun main(arg: Array<String>) { val create ...

  7. 详解python中@的用法

    python中@的用法 @是一个装饰器,针对函数,起调用传参的作用. 有修饰和被修饰的区别,‘@function'作为一个装饰器,用来修饰紧跟着的函数(可以是另一个装饰器,也可以是函数定义). 代码1 ...

  8. oracle 中SQL 语句开发语法 SELECT INTO含义

    oracle 中SQL 语句开发语法 SELECT INTO含义 在ORACLE中SELECT INTO是如何使用的,什么意思?和SQL SERVER的不一样?   和sqlserver的不一样sql ...

  9. Qt编写安防视频监控系统11-动态换肤

    一.前言 Qt中的动态换肤技术是非常一流的,直接调用qApp->setStyleSheet(qss);就可以对整个应用程序进行换肤,如果样式表内容不多,或者对应的贴图不对,效率还是蛮好的,不过据 ...

  10. python中hashlib模块用法示例

    python中hashlib模块用法示例 我们以前介绍过一篇Python加密的文章:Python 加密的实例详解.今天我们看看python中hashlib模块用法示例,具体如下. hashlib ha ...