三.消息队列
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
#define WM_DESTORY 0x0002 • 1
- 产生时间:当窗口被销毁时发送。它将发送到屏幕中删除窗口后正在销毁的窗口过程。
- 附加信息:
- 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
#define WM_COMMAND 0x0111 • 1
- 产生时间:当用户从“窗口”菜单选择命令时,窗口会收到此消息,(以前称为系统或空间菜单),或者当用户选择最大化按钮,最小化按钮,还原按钮或关闭按钮时
- 附加信息:
- 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); }
这时候,如果我们点击最小化等命令,就会弹出提示框。
效果图:
3.WM_CREATE
#define WM_CREATE 0x0001 • 1
- 产生时间:在窗口创建成功,但还未显示时
当应用程序请求通过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消息。
效果图: