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

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



一.消息基础

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程序资源。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shad0w-2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值