以消息为基础的事件驱动系统和消息映射机制
前言
本文关注的重点是以消息为基础的事件驱动,换言之就是消息驱动;正在运行的MFC程序中发生的用户操作和其他事件会使 Windows操作系统将消息发送到程序中的窗口。例如,如果用户在窗口中单击鼠标,则 Windows 会在按鼠标左键时发送 WM_LBUTTONDOWN 消息,在释放按钮时发送 WM_LBUTTONUP 消息。 当用户从菜单栏中选择命令时,Windows 还会发送 WM_COMMAND 消息。
(1)消息
A.What(什么是消息)
本质是一个数据结构,用于应用程序不同部分之间进行通信和交互
typedef struct tagMSG {
HWND hwnd; // 接收该消息的窗口句柄
UINT message; // 消息标识符(如 WM_CREATE、WM_SIZE 等)
WPARAM wParam; // 32 位消息的特定附加信息,确切含义依赖于消息值
LPARAM lParam; // 32 位消息的特定附加信息,确切含义也依赖于消息值
DWORD time; // 消息创建时的时间
POINT pt; // 消息创建时鼠标/光标在屏幕坐标系中的位置
} MSG;
- hwnd:表示将要接收这个消息的窗口的句柄,也就是说谁将接收到这个消息
- message:消息的类型标识符,指定了消息的类型,也就是说明了该消息是用来干什么的
- wParam和lParam:这两个成员用于携带消息的附加参数,其具体含义取决于具体的消息类型
- time:消息创建时的时间
- pt:消息创建时鼠标/光标在屏幕坐标系中的位置
B.Which(有哪些类型的消息)
-
窗口消息:
与窗口内部运作有关的消息,如创建窗口(wm_create)、绘制窗口(wm_paint)、鼠标移动(wm_mousemove)、控件颜色(wm_ctlcolor)、水平滚动(wm_hscroll)等
-
命令消息:
当用户从菜单选中一个命令项目、按下一个快捷键或者点击工具栏上的一个按钮时,都将发送 wm_command 命令消息
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) ON_COMMEND(IDD_BUTTON01, OnButton1LButtonDown) END_MESSAGE_MAP()
上述代码表明:当点击控件IDD_BUTTON01时,将调用函数OnButton1LButtonDown()
-
控件通知消息:
当窗口内的子控件发生一些事件(通常由用户输入触发),需要通知父窗口时发送的消息。通知消息只适用于标准的窗口控件,如按钮、列表框、组合框、编辑框,以及 Windows 公共控件如树状视图、列表视图等
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) ON_NOTIFY(LVN_KEYDOWN, IDC_LIST1, OnKeyDownList1) END_MESSAGE_MAP()
当列表控件 IDC_LIST1 发送 LVN_KEYDOWN 通知消息时,OnKeyDownList1 函数就会被调用,从而可以在该函数中进行相应的处理
-
用户定义消息:
step01:使用#define指令定义一个消息标识符
#define WM_MYMSG WM_USER + 8
step02:在类声明中声明消息映射DECLARE_MESSAGE_MAP()
step03:在类中定义消息处理函数afx_msg LRESULT MyMsgHandler(WPARAM wParam, LPARAM lParam);
step04:实现消息映射BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) ON_MESSAGE(WM_MYMSG, OnMyMsgHandler) END_MESSAGE_MAP()
(2)消息队列
A.What(什么是消息队列)
本质是一个存放MSG的队列数据结构,用于存放消息的队列
B.Which(MFC中有哪些消息队列)
系统消息队列:
主要接收来自硬件设备(如鼠标、键盘等)的输入消息,然后将这些消息分配到相应应用程序的线程消息队列中
线程消息队列:
存储与该线程相关的所有消息,包括系统发送的标准 Windows 消息(如 WM_CREATE、WM_SIZE 等)、用户自定义消息以及由其他线程或进程发送给本线程的消息
例如,当用户点击鼠标时,系统会产生一个鼠标点击消息,并将其放入系统消息队列。然后,Windows 会将这个消息传递到相应应用程序的线程消息队列中。应用程序的线程在消息循环中获取到这个消息后,根据消息的类型和参数进行相应的处理,比如调用相应的窗口过程函数。
C.Why(消息队列的作用)
支持事件驱动模型:
通过消息队列,应用程序能够根据不同的消息触发相应的处理逻辑,实现对用户操作和系统事件的响应
缓冲消息:
对于短时间内大量产生的消息,消息队列起到缓冲的作用,避免消息的丢失或处理混乱。它按照先进先出的原则存储消息,保证消息处理的顺序性
跨线程通信:
不同线程之间可以通过向特定线程的消息队列发送消息来进行通信,实现线程之间的协调和数据传递
D.Who(谁管理消息队列)
在MFC中,Window操作系统统一管理着所有的消息队列,包括系统消息队列和线程消息队列。
在一个多线程的应用程序中,不同线程的消息队列由操作系统统一管理,使得每个线程能够按照其自身的逻辑和优先级来处理接收到的消息,而不会出现混乱或冲突
E.How(如何使用消息队列)
-
向消息队列中添加消息:PostMessage()和SendMessage()
PostMessage(),将消息放入线程的消息队列后立即返回,不等待消息被处理;
SendMessage(),直接调用窗口的窗口过程函数来处理消息,并等待处理完成后才返回 -
从消息队列中取消息:GetMessage()和PeekMessage()
GetMessage(),从线程的消息队列中取出一条消息,如果消息队列中没有消息,该函数会阻塞,直到有消息可用或者收到 WM_QUIT 消息
PeekMessage(),与 GetMessage 不同的是,如果消息队列中没有消息,它不会阻塞,而是立即返回 FALSE消息分发的一般步骤:循环读取消息队列中的消息,并进行
MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); //翻译消息 DispatchMessage(&msg); //分发消息 }
TranslateMessage函数会检查传入的消息,如果发现是WM_KEYDOWN和WM_KEYUP消息的组合,且对应的键可以被键盘驱动器映射为 ASCII 字符,它就会将其转换为一条WM_CHAR消息
DispatchMessage函数将消息分发给相应的窗口过程进行处理,因为MSG中包含消息所接收的窗口句柄
(3)消息的处理过程
外部设备或窗口内部事件产生消息,产生的消息进入线程的消息队列中,而WIndow应用程序的主函数会不断调用GetMessage函数从消息列表中获取消息,并进行消息翻译和消息转发实现消息的处理。
(4)消息映射机制
A.What(什么是消息映射机制)
用于将 Windows 消息与类成员函数进行关联和处理的高效机制
B.Why(消息映射机制的作用)
将这些 Windows 消息与特定的类成员函数关联起来。这样,当特定的消息被发送到窗口时,MFC 框架能够根据消息映射找到对应的处理函数,并调用它来执行相应的操作
通过消息映射机制,开发者无需直接处理复杂的 Windows 消息循环和分发逻辑,而是可以专注于编写具体的消息处理代码
C.How(如何进行消息映射)
一般而言,针对不同的消息类型,使用不同的宏进行消息映射
-
窗口消息:
与窗口内部运作有关的消息,如创建窗口(wm_create)、绘制窗口(wm_paint)、鼠标移动(wm_mousemove)、控件颜色(wm_ctlcolor)、水平滚动(wm_hscroll)等
-
命令消息:
当用户从菜单选中一个命令项目、按下一个快捷键或者点击工具栏上的一个按钮时,都将发送 wm_command 命令消息
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) ON_COMMEND(IDD_BUTTON01, OnButton1LButtonDown) END_MESSAGE_MAP()
上述代码表明:当点击控件IDD_BUTTON01时,将调用函数OnButton1LButtonDown()
-
控件通知消息:
当窗口内的子控件发生一些事件(通常由用户输入触发),需要通知父窗口时发送的消息。通知消息只适用于标准的窗口控件,如按钮、列表框、组合框、编辑框,以及 Windows 公共控件如树状视图、列表视图等
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) ON_NOTIFY(LVN_KEYDOWN, IDC_LIST1, OnKeyDownList1) END_MESSAGE_MAP()
当列表控件 IDC_LIST1 发送 LVN_KEYDOWN 通知消息时,OnKeyDownList1 函数就会被调用,从而可以在该函数中进行相应的处理
-
用户定义消息:
step01:使用#define指令定义一个消息标识符
#define WM_MYMSG WM_USER + 8
step02:在类声明中声明消息映射DECLARE_MESSAGE_MAP()
step03:在类中定义消息处理函数afx_msg LRESULT MyMsgHandler(WPARAM wParam, LPARAM lParam);
step04:实现消息映射BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) ON_MESSAGE(WM_MYMSG, OnMyMsgHandler) END_MESSAGE_MAP()