消息队列概念
- 消息队列是用于存放消息的队列
- 消息在队列中先入后出
- 所有窗口程序都具有消息队列
- 程序(GetMessage)可以从队列中获取消息
消息队列分类
- 系统消息队列
由系统维护的消息队列。存放系统产生的消息,例如鼠标、键盘等。 - 程序消息队列
属于每一个应用程序(线程)的消息队列。由应用程序(线程)维护。
消息和队列关系
- 消息和消息队列的关系
- 当鼠标、键盘等产生消息时,会将消息存放到系统消息队列。 系统会根据存放的消息,找到对应程序的消息队列。 (操作系统会根据消息的目标窗口句柄(hwnd),将消息从系统消息队列分发到相应的线程消息队列。每个GUI线程都有一个独立的消息队列,用于存储和处理该线程创建的窗口的消息)
- 将消息投递到程序的消息队列中。
根据消息和消息队列之间使用关系,将消息分成两类:
-
队列消息:消息的发送和获取,都是通过消息队列完成。
消息发送后,首先放入系统队列,再由系统转发到进程队列,然后通过消息循环,从队列当中获取。
GetMessage:从队列中获取消息
PostMessage:将消息投递到消息队列
常见的队列消息:WM_PAINT、键盘、鼠标、定时器
WM_QUIT消息是极少数必须进队列的消息,否则程序无法结束进程。 -
非队列消息:消息的发送和获取,是直接调用消息的窗口处理完成。
消息发送后,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成消息。
SendMessage:直接调用消息发送给窗口的处理函数,并等候处理结果。
常见的非队列消息:WM_CREATE、WM_SIZE等
消息本质上没有队列消息和非队列消息之分,我们想让它进队列就进队列,想让它不进队列就不进队列,只有极少数的消息才必须进队列或者必须不进队列。
深谈GetMessage原理
- 在程序(线程)消息队列查找消息,如果队列有消息,检查消息是否满足指定条件(HWND,ID范围),不满足条件就不会取出消息,否则从队列取出消息返回。
- 如果程序(线程)消息队列没有消息,向系统消息队列获取属于本程序的消息。如果系统队列的当前消息属于本程序,系统会将消息转发到程序消息队列中。
- 如果系统消息队列也没有消息,检查当前进程的所有窗口的需要重新绘制的区域,如果发现有需要绘制的区域,产生WM_PAINT消息丢到系统队列,系统将该消息分发给程序的消息队列,程序的GetMessage取得消息返回处理。
- 如果没有重新绘制区域,检查定时器如果有到时的定时器:,产生WM_TIMER,返回处理执行。
- 如果没有到时的定时器,整理程序的资源、内存等等。
- GetMessage会继续等候下一条消息,阻塞。如果是PeekMessage会直接返回FALSE,交出程序的控制权。
注意:GetMessage如果获取到是WM_QUIT,函数会返回FALSE。
WM_PAINT消息
- 产生时间:当窗口需要绘制时,或GetMessage无事可做时。
需要绘制是指:
- 窗口首次创建时:当窗口第一次显示时,需要绘制初始内容。
- 窗口大小改变时:当用户调整窗口大小时,需要重新绘制窗口内容。
- 窗口从被覆盖状态变为可见时:当窗口从其他窗口的覆盖下变为可见时,需要重绘被覆盖的部分。
- 附带信息:
wParam:0
lParam:0 - 专职用法:用于绘图。
窗口首次显示时,这个绘制窗口的消息时是由ShowWindow发出的
当用户调整窗口大小时,需要重新绘制窗口内容
既然讲到了绘图消息,就简单绘图看看
InvalidateRect函数是Windows编程中的一个重要函数,用于标记窗口的某个区域为“无效”,从而触发重新绘制该区域。
BOOL InvalidateRect(
[in] HWND hWnd, //窗口句柄
[in] const RECT *lpRect, //区域的矩形坐标
[in] BOOL bErase //重绘前是否先擦除
);
返回值:
如果函数成功,返回值为非零值;如果函数失败,返回值为零。
消息处理步骤:
1.开始绘图
HDC BeginPaint(
HWND hwnd, //绘图窗口
LPPAINTSTRUCT lpPaint //绘图参数的BUFF
); //返回绘图设备句柄HD
2.正式绘图
3.结束绘图
BOOL EndPaint(
HWND hWnd, //绘图窗口
CONST PAINTSTRUCT *lpPaint //绘图参数的指针BeginPaint返回