Windows消息,消息循环的处理,消息队列,键盘消息,鼠标消息,定时器消息

news2024/12/23 13:59:14

上一章节中我们带大家编写了第一个Windows程序,并且带大家学习了注册窗口,创建窗口,这一章中我们来学习Windows消息,学习对消息循环处理的原理,并且带领大家学习一些常见的消息。


文章目录

  • 一.消息基础
    • 1.消息概念及其作用
    • 2.消息的作用
    • 3.派发消息的过程
    • 4.回调函数(窗口处理函数)
    • 5.浅谈消息相关函数
  • 二.消息循环处理的原理
    • 1.消息循环处理的阻塞
    • 2.在应用程序中调出控制台调试程序
    • 3.发送消息
    • 4.消息分类
  • 三.消息队列
    • 1.消息队列概念
    • 2.消息队列分类
    • 3.消息和消息的关系
    • 4.深谈`GetMessage`函数
    • 5.WM_PAINT消息
    • 6.`SendMessage`函数与`PostMessage`函数的不同(从实操理解)
  • 四.Windows常见消息
    • 1.WM-DESTROY
    • 2.WM_SYSCOMMAND
    • 3.WM_CREATE
    • 4.WM_SIZE
    • 5.WM_QUIT
  • 五.键盘消息
    • 1.键盘消息分类
    • 2.字符消息 WM_CHAR
  • 六.鼠标消息
    • 1.鼠标消息分类
      • - 基本鼠标消息
      • - 双击消息
      • - 滚轮消息
    • 2.鼠标基本消息
    • 3.鼠标双击消息
    • 消息产生顺序:
    • 4.鼠标滚轮消息
  • 七.定时器消息
  • 1.定时器消息介绍
  • 2.创建和销毁定时器


一.消息基础

1.消息概念及其作用

在Windows平台下,消息组成:

  • 窗口句柄
  • 消息ID
  • 消息的两个附加信息
  • 消息产生的时间
  • 产生消息时,鼠标的位置
    我们来看看微软定义的消息结构到底是怎样的:
typedef struct tagMSG{
	HWND hwnd;         //接收消息的窗口句柄
	UINT message;      //消息类型(消息标识符)
	WPARAM wParam;     //关于消息的附加消息
	LPARAM lParam;     //关于消息的附加消息
	DWORD time;        //消息的产生时间
	POINT pt;          //发布消息时的光标位置(以屏幕坐标系表示)
}

MSDN官方文档msg结构

2.消息的作用

当系统通知窗口工作时,就采用消息的方式派发给窗口的消息处理函数,以完成窗口的工作。

3.派发消息的过程

  • 1.根据消息的窗口句柄,找到相对应的窗口
  • 2.找到保存窗口数据的内存
  • 3.找到消息处理函数
  • 4.Wndproc(…){
    回到自己的代码(处理消息);
    }

4.回调函数(窗口处理函数)

每个窗口都必须有回调函数。在应用程序中定义的回调函数,用于处理发送到窗口的消息WNDPROC类型定义指向此回调函数的指针,wndproc名称时应用程序中定义的函数名称的占位符。
我们来看看官方定义的窗口处理函数的原型:

LRESULT CALLBACK WindowProc(
	HWND hwnd;      //窗口句柄
	UINT uMsg;      //消息ID
	WPARAM wParam;  //消息附加参数
	LPARAM lParam;  //消息附加参数
){......};

MSDN官方文件解释WNDPROC函数
在这里为大家解释:
LRESULT是程序返回到Windows的整数数值。它包含程序对特定消息的响应。此值的含义取决于消息代码。
CALLBACK是函数的调用约定。

当系统通知窗口时,会调用窗口处理函数,同时将消息ID等附加消息传给窗口处理函数,在窗口处理函数中,不处理的消息,使用缺省窗口处理函数(如DefWindowProc。

5.浅谈消息相关函数

GetMessage函数–获取本进程的消息

Bool GetMessage(
	LPMSG lpMsg;       //指向MSG结构的指针,该结构从线程的消息队列中结构消息
	HWND hwnd;         //要检索其消息的窗口句柄,窗口必须属于当前线程
	UINT wMsgFilterMin;  //要检索的最低消息值的整数值
	UINT wMsgFilterMax;  //要检索的最高消息值的整数值
);

TranslateMessage函数–翻译消息

BOOL TranslateMessage(
	const MSG* lpMsg;
);

二.消息循环处理的原理

1.消息循环处理的阻塞

GetMessage函数: 从系统中获取消息,将消息从系统中移除,阻塞函数。当无系统消息时,等候下一条消息。
PeekMessage函数: 以查看的方式从系统中获取消息,可以不将消息从系统中移除,非阻塞函数。当系统无消息时,返回FALSE,继续执行后续代码。

BOOL PeekMessage(
	LPMSG lpMsg;       //指向接收信息的MSG结构的指针
	HWND hwnd;         //要检索其消息的窗口句柄,窗口必须属于当前线程
	UINT wMsgFilterMin; //要检查的消息范围的第一条消息的值
	uint wMsgFilterMax; //要检查的消息范围的最后一条消息的值
	UINT wRemoveMsg;  //指定如何处理消息(删除/不删除消息等)
}

MSDN官方文档解释PeekMessage函数
学习了PeekMessage函数之后我们来看看我们在上一章中写的消息循环的代码:

MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd,NULL, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int) msg.wParam;
}

那么我们就会发现,我们在消息循环中只使用GetMessage函数的话,那么效率就会很低,因为GetMessage函数经常阻塞。在这里我们学习了PeekMessage函数之后,我们就可以将PeekMessage函数当作侦察兵,先让他去看看有没有消息,如果有消息的话,我们就可以在再去GetMessage等函数。
看看我们优化后的代码:

	while (1) {
		if(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
			if (GetMessage(&msg, NULL, 0, 0)) {
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else {
				return 0;
			}
		}
		else {
			//在空闲的时候,我们想让它做什么就可以填在这里
		}
	}

2.在应用程序中调出控制台调试程序

在我们编写控制台程序的时候,我们可以随时输出来调试程序,那么我们在编写Windows程序的时候,我们无法在程序中直接调试程序,那么我们就需要调出控制台来调试我们的程序,在这里给出在我们编写的Windows程序中调出控制台来调试代码的方法:

  • 首先,我们需要定义一个全局变量(HANDLE)类型,用于接收标准输出句柄
  • 使用AllocConsole()函数在Windows程序中增加DOS窗口
  • 使用GetstdHandle函数接收标准输出句柄,并且用之前定义的HANDLE类型的全局变量接收
    使用示例:
HANDLE g_hOutput = 0;
...
AllocConsole();
g_hOutput = GetstdHandle(STD_OUTPUT_HANDLE);

使用示例
其中,sprintf函数的作用是:将设置格式的数据写入字符串。
我们来看看作用效果:
DOS窗口调试

3.发送消息

SendMessage函数:发送消息,会等候消息的处理结果

LRESULT SendMessage(
	HWND hWnd;      //窗口的过程句柄将接收消息
	UINT Msg;       //要发送的消息
	WPARAM wParam;  //其他的消息特定信息
	LPARAM lParem;  //其他的消息特定信息
);

返回值(LRESULT类型):返回值指定消息处理的结果,这取决于发送的消息。
MSDN官方文档解释SendMessage函数
PostMessage函数:投递消息,消息发出后立刻返回,不等候消息的处理结果

BOOL PostMessage(
	HWND hWnd;             //窗口的句柄,窗口过程是接收消息
	UINT Msg;              //要发布的消息
	WPARAM wParam;         //其他的消息特定信息
	LPARAM lParam;         //其他的消息特定消息
);

MSDN官方文档解释PostMessage函数
这里关于两个发送消息函数的不同,大家可以看文档,本章后面的消息队列也会从实操角度讲到。

4.消息分类

  • 系统消息–ID范围(0~0x03FF)(1024个)
    • 由系统定义的消息,可以在程序中直接使用
  • 用户自定义消息–ID范围(ox400~0x7FFF)(31743个)
    • 由用户自己定义,满足用户自己的需求,由用户自己发出消息,自己处理
  • 自定义消息宏:WM_USER(0x400)

三.消息队列

1.消息队列概念

  • 消息队列时用于存放信息的队列
  • 消息在队列中先入先出
  • 所有窗口都有消息队列
  • 程序(GetMessage函数)可以从消息队列中获取消息

2.消息队列分类

  • 系统消息队列
    • 由系统维护的消息队列,存放系统产生的消息,例如鼠标消息,键盘消息等
  • 程序消息队列
    • 属于每一个应用程序(线程)的消息,队列,由应用程序(线程)维护。

3.消息和消息的关系

该标题下的讲解如果大家未能理解,请大家移步我的另一篇博客,事件,消息,消息处理函数,第一个图形界面程序(附带官方解释链接,该篇博客也会讲解到消息队列,相信大家会有更深刻的理解。
这里给出一张图,帮助大家理解:
消息队列

  • 消息和消息队列的关系:
    • 1.当鼠标,键盘产生消息时,会将消息存到到系统消息队列
    • 2.系统会根据存放的信息(接收消息的窗口句柄),找到对应的应用程序消息队列
    • 3.将消息投递到程序的消息队列中
  • 根据消息和消息队列的关系,可以将消息分为两类:
    • 队列消息 - - - 消息的发送和获取,都是通过消息队列完成
    • 非队列消息 - - - 消息的发送和获取,是直接调用消息的窗口处理函数完成,不进入消息队列
  • 队列消息发送后,首先放入系统消息队列中,然后通过消息循环,从队列中获取
    • GetMessage - - - 从消息队列中获取消息
    • PostMessage - - - 将消息投递到消息队列
  • 常见的队列消息:
    • WM_PAINT,键盘消息,定时器消息,以及WM_Quit消息
  • 非队列消息:
    • 消息发送时,首先查找接收窗口的窗口处理函数,直接调用窗口处理函数,完成消息
    • SendMessage:直接将消息发送给窗口的窗口处理函数,并等候结果
    • 常见非队列消息:WM_CREAT,WM_SIZE等。

4.深谈GetMessage函数

  • 在线程(程序)消息队列中查找消息,如果队列有消息,检查该消息是否满足指定条件(hWnd窗口句柄,查找ID范围),不满足条件就不会取出消息,否则从消息队列中取出消息并返回
  • 如果线程(程序)中没有消息,像系统队列中获取属于本程序的消息。(系统消息队列中每隔一段时间,就会将消息派发给相应的程序消息队列,当GetMessage函数从系统队列中获取消息,那么就会打破这个时间限制)。如果系统队列中的消息属于本应用程序,系统会将消息派发到应用程序消息队列中。
  • 如果系统队列中也没有属于该应用程序的消息,检查当前进程所有窗口需要重新绘制的区域,如果发现有需要绘制的区域,产生WM_PAINT消息,获得消息返回处理
  • 如果没有重新绘制区域,检查定时器,如果有到时定时器产生的WM_TIMER消息,返回处理执行
  • 如果没有到时的定时器,整理程序的资源,内存等等
  • GetMessage会继续等待下一条消息的,PeekMessage会返回FALSE,交出程序的控制权
  • 注意: GetMessage函数如果获取到的是WM_Quit,函数会返回FALSE

5.WM_PAINT消息

  • 产生时间:当窗口需要重新绘制时/GetMessage函数“没有事干”的时候
  • 附加消息:附加消息全为0
  • 专职用法:用于绘图

6.SendMessage函数与PostMessage函数的不同(从实操理解)

我们在本专栏上一篇博客中讲解了解决点击关闭按钮后程序无法正常退出的问题,我们使用到了PostQuitMessage(0)函数的方法让程序正常退出,上一章节中由于大家还是不太懂消息循环机制,我们在这里再为大家细细讲解一下,我们程序无法正确退出,实际上是GetMessage函数阻塞,消息循环没有退出,所以程序无法正常退出,我们使用Post Quit Message(0)函数,实际上是给操作系统发送了一个WM_QUIT消息,让GetMessage函数返回0,退出循环。
那么既然是给操作系统发送一个WM_QUIT消息,那我们就用SendMessage函数和PostMessage函数自行向操作系统发送消息,以此来看看这两个函数的不同之处:
我们在回调函数中修改代码:

case WM_DESTROY:
	{
		//PostQuitMessage(0);
		//SendMessage(NULL, WM_QUIT, 0, 0);
		PostMessage(NULL, WM_QUIT, 0, 0);
		return 0;
	}

我们将PostQuitMessage(0)注释掉,然后分别使用两个函数,待程序执行后,点击关闭按钮,查看程序是否能正常退出。我们发现,当使用SendMessage函数向操作系统发送消息,程序仍然无法正常退出,所以我们得出结论:PostQuitMessage函数使用PostMessage向操作系统发送消息。
我们在上文中讲到,PostMessage函数直接将消息扔到消息队列中,GetMessage函数迟早会抓到这个消息,使程序退出;而SendMessage函数直接调用回调函数,并等候消息处理结果,所以SendMessage函数并未返回,成为了一个阻塞函数,程序也就无法正常退出。


四.Windows常见消息

1.WM-DESTROY

MSDN官方文档解释WM_DESTROY消息

#define WM_DESTORY 0x0002
  • 产生时间:当窗口被销毁时发送。它将发送到屏幕中删除窗口后正在销毁的窗口过程。
  • 附加信息:
    • wParam:未使用
    • lParam:未使用
  • 一般用法:常用于窗口被销毁前,做相应的善后处理,例如资源,内存等。

我们来到回调函数中写一下处理这个消息的过程:

LRESULT CALLBACK WindowProc(
	IN  HWND hwnd,
	IN  UINT uMsg,
	IN  WPARAM wParam,
	IN  LPARAM lParam
)
{
	char output[256] = { 0 };
	switch (uMsg)
	{
	//常见消息
	case WM_DESTROY: {
		sprintf(output, TEXT("窗口即将被销毁,将向消息队列中发送WM_QUIT消息"));
		WriteConsole(g_hOUTPUT,output,strlen(output), 0, 0);
		PostMessage(NULL, WM_QUIT, 0, 0);
	}
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

2.WM_SYSCOMMAND

MSDN官方文档解释WM_SYSCOMMAND消息

#define WM_COMMAND 0x0111
  • 产生时间:当用户从“窗口”菜单选择命令时,窗口会收到此消息,(以前称为系统或空间菜单),或者当用户选择最大化按钮,最小化按钮,还原按钮或关闭按钮时
  • 附加信息:
    • wParam:具体点击的位置,如关闭按钮,最大化按钮等
    • lParam:光标的位置坐标
      • LOWORD(lParam):水平位置
      • HIWORD(lParam):垂直位置
      • 在这里我们介绍一下HIWORD和LOWORD,我们知道lParam为4字节字符,那么如何表示两条信息呢?我们使用LOWORD和HIWORD宏,就可以分别取出低两字节和高两字节数据,分别表示水平位置和垂直位置。
  • 一般用法:常用在关闭按钮时,提示用户处理。

我们来到回调函数中来写一下WM_COMMAND消息的处理:

LRESULT CALLBACK WindowProc(
	IN  HWND hwnd,
	IN  UINT uMsg,
	IN  WPARAM wParam,
	IN  LPARAM lParam
)
{
	char output[256] = { 0 };
	switch (uMsg)
	{
	//常见消息
	case WM_DESTROY: {
		sprintf(output, TEXT("窗口即将被销毁,将向消息队列中发送WM_QUIT消息"));
		WriteConsole(g_hOUTPUT,output,strlen(output), 0, 0);
		PostMessage(NULL, WM_QUIT, 0, 0);
		break;
		}
	case WM_SYSCOMMAND:{
			sprintf(output, TEXT("WM_COMMAND消息收到,将弹出提示窗口\n"));
			WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
			MessageBox(hwnd, "记住我!!!", "别忘了", MB_YESNO);
		break;
	}
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

这时候,如果我们点击最小化等命令,就会弹出提示框。
效果图:
WM_COMMAND消息效果图

3.WM_CREATE

MSDN官方文档解释WM_CREATE消息

#define WM_CREATE 0x0001
  • 产生时间:在窗口创建成功,但还未显示时
    当应用程序请求通过CreateWindowEx或CreateWindow函数创建窗口时发送。(函数返回之前发送消息)新窗口的窗口过程在创建窗口后收到此消息,但在窗口变为可见之前
  • 附加信息:
    • wParam:未使用
    • lParam:指向CREATESTRUCT的指针,其中包含有关正在创建的窗口的信息。通过这个指针可以获取CreateWindowEx函数中,全部的12个参数信息,
  • 返回值:如果应用程序处理此消息,它应返回零以继续创建窗口。 如果应用程序返回 –1,则窗口将被销毁, CreateWindowEx 或 CreateWindow 函数返回 NULL 句柄。
  • 一般用法:常用于初始化窗口的参数,资源等,包括创建子窗口等

我们来到回调函数中处理一下WM_CREATE消息:

LRESULT CALLBACK WindowProc(
	IN  HWND hwnd,
	IN  UINT uMsg,
	IN  WPARAM wParam,
	IN  LPARAM lParam
)
{
	char output[256] = { 0 };
	switch (uMsg)
	{
	//常见消息
	case WM_DESTROY: {
		sprintf(output, TEXT("窗口即将被销毁,将向消息队列中发送WM_QUIT消息"));
		WriteConsole(g_hOUTPUT,output,strlen(output), 0, 0);
		PostMessage(NULL, WM_QUIT, 0, 0);
		break;
		}
	case WM_SYSCOMMAND:{
			sprintf(output, TEXT("WM_COMMAND消息收到,将弹出提示窗口\n"));
			WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
			MessageBox(hwnd, "记住我!!!", "别忘了", MB_YESNO);
		break;
	}
	case WM_CREATE: {
		sprintf(output, TEXT("检测到WM_CREATE消息,将创建窗口。\n"));
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

我们在第一次打开程序的时候就需要初始化窗口参数,所以在第一次打开程序的时候,操作系统就会产生WM_CREATE消息。
效果图:
WM_CREATE消息效果图

4.WM_SIZE

MSDN官方文档解释WM_SIZE消息

#define WM_SIZE 0X0005
  • 产生时间:当窗口大小改变后
  • 附加信息:
    • wParam:请求的大小调整类型
    • lParam:
      • LOWORD:变化后的宽度
      • HIWORD:变化后的高度
  • 返回值:如果应用程序处理此消息,它返回0。
  • 一般用法:常用于窗口大小变化后,调整窗口内各个部分的布局

我们来到回调函数中处理WM_SIZE消息:

LRESULT CALLBACK WindowProc(
	IN  HWND hwnd,
	IN  UINT uMsg,
	IN  WPARAM wParam,
	IN  LPARAM lParam
)
{
	char output[256] = { 0 };
	switch (uMsg)
	{
	//常见消息
	case WM_DESTROY: {
		sprintf(output, TEXT("窗口即将被销毁,将向消息队列中发送WM_QUIT消息"));
		WriteConsole(g_hOUTPUT,output,strlen(output), 0, 0);
		PostMessage(NULL, WM_QUIT, 0, 0);
		break;
		}
	case WM_SYSCOMMAND:{
			sprintf(output, TEXT("检测到WM_COMMAND消息,将弹出提示窗口\n"));
			WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
			MessageBox(hwnd, "记住我!!!", "别忘了", MB_YESNO);
		break;
	}
	case WM_CREATE: {
		sprintf(output, TEXT("检测到WM_CREATE消息,将创建窗口。\n"));
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	case WM_SIZE: {
		sprintf(output, "iParam:窗口宽变化为:%d,窗口高变化为:%d \n", HIWORD(lParam), LOWORD(lParam));
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

效果图:
WM_SIZE消息效果图

5.WM_QUIT

MSDN官方文档解释WM_QUIT消息

#define WM_QUIT 0X0012
  • 产生时间:指示终止应用程序的请求,并在应用程序调用PostQuitMessage函数时生成,此消息导致GetMessage函数返回0。
  • 附加信息:
    • wParam:PostQuitMessage函数传递的参数(PostQuitMessage函数中给定的退出代码)
    • lParam:未使用
  • 一般用法:用于结束消息循环,当GetMessage函数抓到这个消息后,会返回FALSE,结束while循环。

我们来到回调函数中处理这个消息:
WM_QUIT消息效果图


五.键盘消息

1.键盘消息分类

  • WM_KEYDOWN----键盘被按下时产生

  • WM_KEYUP----键盘被放开时产生

  • WM_SYSKEYDOWN----系统键被按下时产生

  • WM_SYSKEYUP----系统键被放开时产生

  • 附加信息:

    • wParam:按键的虚拟键码
    • lParam:按键的参数,例如按下多少次

    2.字符消息 WM_CHAR

  • TranslateMessage函数在转换WM_KEYDOWN消息时,对于可见的字符可以产生WM_CHAR消息,对于不可见字符,不会产生此消息

  • 附加信息

    • wParam:输入字符的ASCII字符编码码值
    • lParam:按键的相关参数

这里我给出一段伪代码,帮助大家理解GetMessage函数抓到字符消息后,TranslateMessage函数翻译此消息的过程:

TranslateMessage(&Msg){
	if(Msg.message != WM_KEYDOWN){
		return ;
	}else{
	根据Msg.wParam(键码值)可以获知哪个按键被按下
	if(不可见字符){
		return;
	}
	查看Capslock键是否处于打开状态
	if(打开){
		PostMessage(Msg.hwnd,WM_CHAR,......);
	}else{
		PostMessage(Msg.hwnd,WM_CHAR,......);
	}
}

我们来到回调函数中来看看键盘消息:

LRESULT CALLBACK WindowProc(
	IN  HWND hwnd,
	IN  UINT uMsg,
	IN  WPARAM wParam,
	IN  LPARAM lParam
)
{
	char output[256] = { 0 };
	switch (uMsg)
	{
	//常见消息
	case WM_QUIT: {
		MessageBox(hwnd, "确定退出程序吗?", "退出", MB_YESNO);
		break;
	}
	case WM_DESTROY: {
		sprintf(output, TEXT("窗口即将被销毁,将向消息队列中发送WM_QUIT消息"));
		WriteConsole(g_hOUTPUT,output,strlen(output), 0, 0);
		PostMessage(NULL, WM_QUIT, 0, 0);
		break;
		}
	case WM_SYSCOMMAND: {
			sprintf(output, TEXT("检测到WM_COMMAND消息,将弹出提示窗口\n"));
			WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
			MessageBox(hwnd, "记住我!!!", "别忘了", MB_YESNO);
		break;
	}
	case WM_CREATE: {
		sprintf(output, TEXT("检测到WM_CREATE消息,将创建窗口。\n"));
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	case WM_SIZE: {
		sprintf(output, "lParam:窗口宽变化为:%d,窗口高变化为:%d \n", HIWORD(lParam), LOWORD(lParam));
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	//键盘消息
	case WM_KEYDOWN: {
		sprintf(output, "检测到WM_KEYDOWN消息,键码值:%d.\n", wParam);
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	case WM_KEYUP: {
		sprintf(output, "检测到WM_KEYUP消息,键码值:%d.该按键被放开\n", wParam);
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

我们来看看效果:
WM_KEY消息效果
我们不难发现,每个键位都有一个专属的键码值,而且我们发现,大写和小写的键码值一样,是通过TranslateMessage函数来检测CapsLock是否打开区分大小写的。


六.鼠标消息

1.鼠标消息分类

- 基本鼠标消息

  • WM_LBUTTONDOWN----鼠标左键按下
  • WM_RBUTTONDOWN----鼠标右键按下
  • WM_LBUTTONUP----鼠标左键抬起
  • WM_RBUTTONUP----鼠标右键抬起
  • WM_MOUSEMOVE----鼠标移动消息

- 双击消息

  • WM_LBUTTONBLOCK----鼠标左键双击
  • WM_RBUTTONBLOCK----鼠标右键双击

- 滚轮消息

  • WM_MOUSEWHEEL----鼠标滚轮消息

2.鼠标基本消息

  • WM_LBUTTONDOWN----鼠标左键按下
  • WM_RBUTTONDOWN----鼠标右键按下
  • WM_LBUTTONUP----鼠标左键抬起
  • WM_RBUTTONUP----鼠标右键抬起
  • WM_MOUSEMOVE----鼠标移动消息
  • 产生时间:当鼠标有动作时
  • 附加信息:
    • wParam:其他按键状态,例如Ctrl/Shift按键状态
    • lParam:鼠标的位置,窗口客户区坐标系
      • LOWORD x
      • HIWORD y
  • 一般情况下,按下/抬起消息成对出现,在鼠标移动过程中,会根据移动速度,产生一系列WM-MOUSEMOVE消息。

3.鼠标双击消息

  • WM_LBUTTONBLCLK----鼠标左键双击
  • WM_RBUTTONBLCLK----鼠标右键双击
  • 附加信息:
    • wParam:其他按键状态(Ctrl,Shift等)
    • lParam:鼠标位置,窗口客户区坐标系
    • 消息产生顺序:

      • 这里我们以双击左键为例:
      • 1.WM_LBUTTONDOWN
      • 2.WM_LBUTTONUP
      • 3.WM_LBUTTONBLCLK
      • 4.WM_LBUTTONUP
    • 使用时注意要在注册窗口时增加CS_DBLCLKS风格

4.鼠标滚轮消息

WM_MOUSEWHEEL

  • 附加信息:
    • wParam:
      • LOWORD: 其他按键状态
      • HIWORD:滚轮的偏移量,通过正负值可以确定滚轮滚动方向(正:向前滚动,负:向后滚动
    • lParam:鼠标当前位置(屏幕坐标系)
      • LOWORD:x
      • HIWORD:y
  • 使用:通过偏移量,获取滚动方向和距离

我们来到回调函数中来处理鼠标消息:

LRESULT CALLBACK WindowProc(
	IN  HWND hwnd,
	IN  UINT uMsg,
	IN  WPARAM wParam,
	IN  LPARAM lParam
)
{
	char output[256] = { 0 };
	switch (uMsg)
	{
	//常见消息
	case WM_QUIT: {
		MessageBox(hwnd, "确定退出程序吗?", "退出", MB_YESNO);
		break;
	}
	case WM_DESTROY: {
		sprintf(output, TEXT("窗口即将被销毁,将向消息队列中发送WM_QUIT消息"));
		WriteConsole(g_hOUTPUT,output,strlen(output), 0, 0);
		PostMessage(NULL, WM_QUIT, 0, 0);
		break;
		}
	case WM_SYSCOMMAND: {
			sprintf(output, TEXT("检测到WM_COMMAND消息,将弹出提示窗口\n"));
			WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
			MessageBox(hwnd, "记住我!!!", "别忘了", MB_YESNO);
		break;
	}
	case WM_CREATE: {
		sprintf(output, TEXT("检测到WM_CREATE消息,将创建窗口。\n"));
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	case WM_SIZE: {
		sprintf(output, "lParam:窗口宽变化为:%d,窗口高变化为:%d \n", HIWORD(lParam), LOWORD(lParam));
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	//键盘消息
	case WM_KEYDOWN: {
		sprintf(output, "检测到WM_KEYDOWN消息,键码值:%d.\n", wParam);
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	case WM_KEYUP: {
		sprintf(output, "检测到WM_KEYUP消息,键码值:%d.该按键被放开\n", wParam);
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	//鼠标消息
	case WM_LBUTTONDOWN: {
		sprintf(output, "检测到WM_LBUTTONDOWN消息,鼠标左键被按下。\n");
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	case WM_LBUTTONUP: {
		sprintf(output, "检测到WM_LBUTTONUP消息,鼠标左键被放开。\n");
		WriteConsole(g_hOUTPUT, output, strlen(output),0,0);
		break;
	}
	case WM_RBUTTONDOWN: {
		sprintf(output, "检测到WM_RBUTTON消息,鼠标右键被按下。\n");
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	case WM_RBUTTONUP: {
		sprintf(output, "检测到WM_RBUTTON消息,鼠标右键被放开。\n");
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
	}
	case WM_MOUSEMOVE: {
		sprintf(output, "检测到WM_MOUSEMOVE消息,鼠标移动中,鼠标位置(%d,%d).\n", LOWORD(lParam), HIWORD(lParam));
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	case WM_MOUSEWHEEL: {
		sprintf(output, "鼠标滚轮滚动中,偏移量:%d,鼠标当前位置(%d,%d)\n",HIWORD(wParam), LOWORD(lParam), HIWORD(lParam));
		WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0);
		break;
	}
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

我看看看处理效果:
鼠标消息处理效果


七.定时器消息

1.定时器消息介绍

  • 产生时间:
    在程序中创建定时器,当达到时间间隔时,定时器会向(实际上是GetMessage函数)程序发送一个WM-TIMER消息。定时器的精度是毫秒,但是准确度极低(原因是:我们在上文中深谈GetMessage函数的时候讲过,当没有消息时,GetMessage函数会做很多事情,只有当前面的事都做完了之后才会检查定时器),例如设置间隔为1000ms,但是会在非1000ms时发送消息。
  • 附加消息:
    • wParam:定时器ID
    • lParam:定时器出席函数的指针

2.创建和销毁定时器

  • 创建定时器
UINT_PTR SetTimer(
	HWND hWnd,              //定时器窗口句柄
	UINT_PTR nIDEvent,      //定时器ID
	UINT uElapse,           //时间间隔(ms)
	TIMEPROC lpTimeFunc     //定时器函数处理指针,一般不使用,设置为NULL
	);创建成功返回非0,失败返回0
  • 关闭定时器
BOOL killTimer(
	HWND hWnd,        //定时器窗口聚句柄
	UINT UideVENT     //定时器ID
	);

今天的内容就分享到这里,如果文章中出现错误之处或者是我个人理解不到位的地方,还请大家指出来,我会非常虚心地学习,下一篇文章我们来为大家讲解Windows程序资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/508771.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AOF 持久化详解

文章目录 AOF 相关配置AOF 文件的修复AOF 文件格式RESP 协议查看 AOF 文件清单文件 AOF RewriteRewrite 策略手动Rewrite自动Rewrite AOF 持久化过程AOF优缺点AOF与RDB混合持久化 AOF (Append Only File) 是把所有对内存进行修改的指令(写操作)以独立日志…

《花雕学AI》用AI创造清晨的美好:ChatGPT+DALL-E 2 生成“早上好”的场景图

早晨是一天中最美好的时刻,也是最适合与AI对话的时刻。想象一下,当你醒来,打开手机,就能看到一个AI为你生成的“早上好”的场景图,是不是很温馨?这就是ChatGPTDALL-E 2(新Bing) 的魅…

Elevate:全世界最小的介入泵,融资五千万美金

近日,以色列医疗器械公司Magenta Medical宣布完成5500万美元的C轮融资,该公司主要产品Elevate是目前全球最小的心脏介入泵,主要用于治疗急性心力衰竭患者和高危PCI的辅助。该泵采用了创新的设计和材料,可以通过桡动脉插入&#xf…

哪款远程控制软件可以远程玩游戏?

远程控制软件可以让你在不同设备之间进行远程控制。许多人可能会想知道,哪款远程控制软件可以在远程玩游戏时享受更好的游戏体验。 首先,在寻找适合远程游戏的远程控制软件之前,我们需要知道什么是远程游戏和远程控制。 远程游戏是一种允许玩…

第十二章_Redis单线程 VS 多线程

Redis为什么选择单线程? 是什么 这种问法其实并不严谨,为啥这么说呢? Redis的版本很多3.x、4.x、6.x,版本不同架构也是不同的,不限定版本问是否单线程也不太严谨。 1 版本3.x ,最早版本,也就是大家口口相…

一文读懂 DNS 解析

导读 文章为“一文读懂域名与网站系列”第二篇,上篇文章主要介绍了域名的注册、建站和管理,通过本文你可以了解以下几个问题: 域名的结构、常用解析记录的类型 DNS 解析的过程 DNS 解析拓展知识 众所周知,互联网中的地址其实是…

想让行车记录仪协助道路病害自动化检测?可以!

针对【RGB3DS道路表观病害信息智慧检测系统】,我们着重介绍过其与道路检测车做集成预装或者处理道路检测车数据的极大便利,其中之一便是可高效输出带有道路检测车桩号标记的病害报表,这是因为道路检测车数据本身具有规范性。 那么如果使用道…

Linux(类Unix)系统可执行程序ELF文件格式详解

我们知道一个Linux程序饱和程序代码和初始数据,那么这些程序二进制代码和初始数据在可执行程序文件中是怎么进行存储呢?这便是ELF文件格式要解决的问题。 一个Linux执行程序的内存结构粗略可划分为 代码段、数据段、BSS、堆、栈,如下图所示&…

Golang Gin 使用路由分类处理请求

在前面已经学习了gin框架如何处理请求,解析请求,返回数据。 在实际的项目当中,项目往往是以模块化来进行划分和开发的,所谓的模块化就是按照功能来划分,比如会有产品模块,会有用户模块,会将用户…

如何用ChatGPT协助搭建品牌视觉体系(VI)?

该场景对应的关键词库(18个): VI体系、品牌、目标市场、品牌DNA、人群特征、设计理念、标志设计、配色方案、字体选择、图形元素、价值观、形象、客户经理、需求、品牌定位、目标受众、主色调、辅助色 提问模板(2个)&…

并发编程09:ThreadLocal

文章目录 9.1 ThreadLocal简介9.1.1 面试题9.1.2 是什么?9.1.3 能干吗?9.1.4 API介绍9.1.5 永远的helloworld讲起9.1.6 总结 9.2 ThreadLocal源码分析9.2.1 源码解读9.2.2 Thread、ThreadLocal、ThreadLocalMap关系9.2.3 总结 9.3 ThreadLocal内存泄漏问…

基于html+css的图展示53

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

网络:TCP协议三次握手与四次挥手

一、理解TCP报文 TCP报文格式_TCP报文包含哪些内容: TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接。 TCP在发送数据前必须在彼此间建立连接,这里连接意思是:双方需要内保存对方信息(例如&…

leecode100_第7题接雨水_双指针

1 题目 2 分析 简述:两个指针分别指向两端,通过判断柱子的升降趋势来判断是否可以接水。 思路如下: 变量l_max,r_max分别记录当前时刻,左右遍历过的柱子的最高的高度,因为它奠定了水能积多高。分情况判断&#xff1a…

2437. 有效时间的数目

2437.有效时间的数目 给你一个长度为 5 的字符串 time ,表示一个电子时钟当前的时间,格式为 “hh:mm” 。最早 可能的时间是 “00:00” ,最晚 可能的时间是 “23:59” 。 在字符串 time 中,被字符 ? 替换掉的数位是 未知的 &am…

【Python三方库】使用tle2czml库将tle数据转为czml数据

原文作者:我辈李想 版权声明:文章原创,转载时请务必加上原文超链接、作者信息和本声明。 文章目录 一、安装tle2czml二、tle2czmlc创建czml三、tle转成czml1.字符串2.文件(网络文件) 四、czml参数修改 一、安装tle2czm…

【PWN · ret2syscall】[Wiki] ret2syscall

初次接触到ret2syscall,而ret2syscall的题目目前没有在各大平台的题目类型筛选中找到,所以还是刷一刷Wiki的经典题目吧!过程中遇到很多问题,包括偏移量的计算、ret2syscall原理的理解等等。尝试以萌新的视角,来分享、解…

网页版的 Redis 可视化工具来了,已开源

介绍 轻量级Redis缓存图形化管理工具,包含redis的5种数据类型的CRUD操作 软件架构 后端 springboot 2.2.2.RELEASE JDK 1.8 jedis 3.2.0 commons-lang3 3.5 hutool-core 5.1.1 fastjson 1.2.62 h2database 1.4.200 前端 vue-admin 1.0.5 axios 0.15.3 …

【Linux】exec函数族

目录 1、exec函数族的介绍2、exec相关函数 1、exec函数族的介绍 2、exec相关函数 #include <unistd.h> int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ ); /* - path 需要指定的执行的文件的路径或者名称&#xff0c;相对路径or绝对路径- arg …

港联证券“中特估值”重塑可转债市场

中国特色估值体系&#xff08;简称“中特估值”&#xff09;正在重构以银行为核心的可转债市场。 尽管周二市场有所回落&#xff0c;但如火如荼的中特估值行情对可转债市场的影响巨大。受益于中信银行等强势上涨的表现&#xff0c;银行转债集体活跃。其中&#xff0c;齐鲁转债、…