《Windows API每日一练》6.2 客户区鼠标消息

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

本节必须掌握的知识点:

客户区鼠标消息

第35 练:客户区鼠标消息的处理

6.2.1 客户区鼠标消息

客户区 鼠标消息

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

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

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

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

X = LOWORD (IParam);

y = HIWORD (IParam);

参数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键。

●处理Shift键

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 处理过程依赖Shift和Ctrl键的逻辑处理 | 单键鼠标模拟双键鼠标 |
| if (wParam & MK_SHIFT) //按下Shift { if (wParam & MK_CONTROL) { [按下Shift + Ctrl键]; } else{ [只按下Shift键]; } }else{ //未按Shift if (wParam & MK_CONTROL) { [只按下Ctrl键]; }else{ [Shift和Ctrl都没被按下]; } } | case WM_LBUTTONDOWN: //未按Shift时,直接处理左键 if (!(wParam & MK_SHIFT)) { [这里处理左键]; return 0; } //注意,这里没有return。 //用户按下了鼠标左键+Shift,执行以下代码,模拟右键。 case WM_RBUTTONDOWN: [这里处理右键]; return 0; 【注意】双键鼠标也是可以正常处理的。单键鼠标可以通过按住鼠标左键+Shift,来模拟鼠标右键的功能。 |

【注意】GetKeyState可以通过VK_LBUTTON、VK_RBUTTON、VK_SHIFT、VK_CONTROL等获取鼠标当前状态。但鼠标或键盘未被按下的键不能使用GetKeyState。只有被按下时才会报告其按下状态。(while(GetKeyState(VK_LBUTTON)>=0))是错误的代码。

●鼠标移经窗口的客户区时,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时弹出的消息框就是一个系统模式消息框。

6.2.2 第35练:客户区鼠标消息的处理

/*---------------------------------------------------------------

035 WIN32 API 每日一练

第35个例子CONNECT.C:客户区鼠标消息的处理

SetPixel函数

SetCursor函数

ShowCursor函数

WM_LBUTTONDOWNE消息

WM_MOUSEMOVE消息

WM_LBUTTONUP消息

(c) www.bcdaren.com, 2020

----------------------------------------------------------------*/

#include <windows.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");

...(略)

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)

{

/*测试:非客户区消息

//用于通知应用程序在非客户区(Non-Client Area)

//接收到鼠标消息时进行的命中测试(Hit Test)。

case WM_NCHITTEST:

//直接返回位置信息,阻止系统向所有窗口客户区和非窗口客户区发送鼠标消息

return (LRESULT)HTNOWHERE;

//测试:按下ALT+F、Ctrl+C等系统消息

case WM_SYSKEYDOWN:

//直接返回,使所有系统键盘消息失效

return 0;*/

//按下鼠标左键消息

case WM_LBUTTONDOWN :

iCount = 0;

InvalidateRect (hwnd,NULL ,TRUE );//重绘窗口---清除背景

return 0;

//鼠标移动消息

case WM_MOUSEMOVE :

//按下鼠标左键并且iCount小于1000

if (wParam & MK_LBUTTON && iCount < 1000)

{

//填充坐标数组

pt[iCount].x = LOWORD (lParam);

pt[iCount++].y = HIWORD (lParam);

hdc = GetDC (hwnd);

//设置像素点颜色,RGB(0)黑色

SetPixel (hdc,LOWORD (lParam),HIWORD (lParam),0);

ReleaseDC (hwnd,hdc);

}

return 0;

//松开鼠标左键消息

case WM_LBUTTONUP :

//重新绘制窗口---不清除背景,保留WM_MOUSEMOVE里画下的点。

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 = 0;j < iCount - 1;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);

}

/***************************************************************************

SetPixel函数:指定坐标到指定的颜色设置像素

COLORREF SetPixel(

HDC hdc,

int x, //坐标

int y,

COLORREF color //RGB颜色

);

***************************************************************************

SetCursor函数:设置鼠标形状

HCURSOR SetCursor(

HCURSOR hCursor //IDC_ARROW,IDC_WAIT

);

ShowCursor函数:显示/隐藏鼠标

int ShowCursor(

BOOL bShow //TRUE显示,FALSE隐藏

);

***************************************************************************

WM_LBUTTONDOWNE消息:当光标在窗口的客户区域中时用户按下鼠标左键时发布。

如果未捕获鼠标,则消息将发布到光标下方的窗口。否则,该消息将发布到捕获鼠标的窗口中。

参数wParam:指示各种虚拟键是否按下。此参数可以是以下一个或多个值。

MK_CONTROL 0x0008 CTRL键按下。

MK_LBUTTON 0x0001 鼠标左键按下。

MK_MBUTTON 0x0010 鼠标中键按下。

MK_RBUTTON 0x0002 鼠标右键按下。

MK_SHIFT 0x0004 SHIFT键按下。

MK_XBUTTON1 0x0020 第一个X按钮按下。

MK_XBUTTON2 0x0040 第二个X按钮按下。

lParam:

低位字指定光标的x坐标。坐标相对于客户区域的左上角。

高阶字指定光标的y坐标。坐标相对于客户区域的左上角。

返回值

如果应用程序处理此消息,则应返回零。

***************************************************************************

WM_MOUSEMOVE消息:光标移动时张贴到窗口。如果未捕获鼠标,则消息将发布到包含光标的窗口中。否则,该消息将发布到捕获鼠标的窗口中。

参数与WM_LBUTTONDOWNE消息相同

***************************************************************************

WM_LBUTTONUP消息:当光标在窗口的客户区域中时,用户释放鼠标左键时发布。

如果未捕获鼠标,则消息将发布到光标下方的窗口。否则,该消息将发布到捕获鼠标的窗口中。

参数与WM_LBUTTONDOWNE消息相同

*/

运行结果:

图6-1 客户区鼠标消息

总结

●实例操作方法:

1.第一种------在客户区按下左键,略微移动,再松开左键。

2.第二种------在客户区按下左键,快速移动鼠标。

●己知的问题:在客户区外释放左键,Connnect不会连接这些点,因为没收到WM_LBUTTONUP消息。

●该程序较耗时,绘制时,鼠标变沙漏形,处理WM_PAINT完后回原来的状态。用SetCursor来切换鼠标。ShowCursor隐藏或显示鼠标指针。

●窗口过程:

1.实例CONNECT.C处理了三个鼠标消息。

WM_LBUTTONDOWNE消息:按下鼠标左键时,调用InvalidateRect函数清除背景,重绘窗口。

WM_MOUSEMOVE消息:移动鼠标时,采集不超过1000个鼠标移动坐标点,保存在pt数组中,然后使用SetPixel函数绘制坐标点(系统默认黑色画笔)。

WM_LBUTTONUP消息:松开鼠标左键时,重绘窗口,但是并不清除背景。

2.处理WM_PAINT消息时,CONNECT程序需要耗费一定的时间来绘制直线,因此鼠标指针会变成等待位图。调用SetCursor函数,加载并设置鼠标位图为等待位图,显示鼠标位图。接着使用双循环将所有坐标点连接起来。然后再恢复原鼠标位图。

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

4.动手实验:处理WM_NCHITTEST消息时可以直接返回鼠标位置信息,阻止系统向所有窗口客户区和非窗口客户区发送鼠标消息。

处理WM_SYSKEYDOWN消息时,可以让所有系统键盘消息失效。

下一节我们讲述如何在非窗口客户区捕捉鼠标消息。

相关推荐
梓仁沐白4 小时前
ubuntu+windows双系统切换后蓝牙设备无法连接
windows·ubuntu
网易独家音乐人Mike Zhou5 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
九鼎科技-Leo8 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
搬砖的小码农_Sky8 小时前
C语言:数组
c语言·数据结构
Yang.9910 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
我不瘦但很逗10 小时前
Windows下使用DBeaver连接云数据库(MySQL)
数据库·windows
ashane131411 小时前
Java list
java·windows·list
万里沧海寄云帆11 小时前
Word 插入分节符页码更新问题
windows·microsoft·word
ahadee12 小时前
蓝桥杯每日真题 - 第19天
c语言·vscode·算法·蓝桥杯
Theliars12 小时前
C语言之字符串
c语言·开发语言