欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Windows桌面应用程序(1-2-2-2nd) 窗口消息

程序员文章站 2022-07-06 19:59:13
...

GUI应用程序必须响应来自用户和操作系统的事件。

  • 来自用户的事件 包括有人可以与您的程序进行交互的所有方式:鼠标点击,击键,触摸屏手势等等。
  • 来自操作系统的事件 包括程序外的任何可能影响程序行为的任何东西。例如,用户可能插入新的硬件设备,或者Windows可能进入低功耗状态(睡眠或休眠)。

这些事件可以在程序运行的任何时候以几乎任何顺序发生。你如何构建一个无法预测执行流程的程序?
为了解决这个问题,Windows使用消息传递模型。操作系统通过传递消息与应用程序窗口进行通信。消息只是指定特定事件的数字代码。例如,如果用户按下鼠标左键,窗口将收到一条消息,消息代码如下。

#define WM_LBUTTONDOWN 0x0201

一些消息具有与其关联的数据。例如,WM_LBUTTONDOWN消息包含鼠标光标的x坐标和y坐标。
要将消息传递给窗口,操作系统将调用为该窗口注册的窗口过程。(现在你知道窗口过程是什么了。)
消息循环
应用程序在运行时将收到数千条消息。(考虑每个击键和鼠标按钮点击都会生成一条消息。)另外,应用程序可以有多个窗口,每个窗口都有自己的窗口过程。程序如何收到所有这些消息并将其发送到正确的窗口过程?应用程序需要一个循环来获取消息并将其分发到正确的窗口。
对于创建窗口的每个线程,操作系统都会为窗口消息创建一个队列。该队列保存在该线程上创建的所有窗口的消息。队列本身对你的程序是隐藏的。您不能直接操作队列,但可以通过调用GetMessage函数从队列中获取消息。

MSG msg;
GetMessage(&msg,NULL,0,0);

这个函数从队列的前端删除第一条消息。如果队列为空,则阻塞该功能,直到另一个消息入队。GetMessage块事实上不会让你的程序无法响应。如果没有消息,程序没有什么可做的。如果您需要执行后台处理,则可以创建其他线程,以便在GetMessage等待另一个消息时继续运行。(请参阅避免窗口程序中的瓶颈。)
GetMessage的第一个参数是MSG结构的地址。如果该功能成功,则填充MSG结构,包含消息的信息,包括目标窗口和消息代码。其他三个参数使您能够过滤从队列中获取哪些消息。在几乎所有情况下,您都将这些参数设置为零。
尽管MSG结构包含有关消息的信息,但几乎不会直接检查此结构。相反,你会直接把它传递给另外两个函数。

TranslateMessage(&msg); 
DispatchMessage(&msg);

TranslateMessage函数与键盘输入相关;它将按键(按下,松开)转换为字符。你并不需要知道这个功能是如何工作的。只记得在DispatchMessage之前调用它。如果你好奇的话,MSDN文档的链接会给你更多的信息。
DispatchMessage函数通知操作系统调用作为消息目标的窗口的窗口过程。换句话说,操作系统在其窗口表中查找窗口句柄,找到与该窗口关联的函数指针并调用函数。
例如,假设用户按下鼠标左键。这导致了一连串的事件:

  1. 操作系统在消息队列上放置一个WM_LBUTTONDOWN消息。
  2. 您的程序调用GetMessage函数。
  3. GetMessage从队列中提取WM_LBUTTONDOWN消息并填充MSG结构。
  4. 你的程序调用了TranslateMessageDispatchMessage函数。
  5. DispatchMessage中,操作系统调用你的窗口过程。
  6. 您的窗口过程可以响应消息或忽略它。

当窗口过程返回时,它将返回到DispatchMessage,它将返回到下一条消息的消息循环。只要程序正在运行,消息就会继续到达队列中。因此,您需要一个循环,不断地从队列中提取消息并分发消息。你可以把这个循环看作是这样做的:

// WARNING: Don't actually write your loop this way.
while(1){
    GetMessage(&msg,NULL,0,0);
    TranslateMessage(&msg); 
    DispatchMessage(&msg);
}

正如所写,毋庸置疑,这个循环永远不会结束。这就需要GetMessage函数的返回值。正常情况下,GetMessage返回一个非零值。无论何时您想退出应用程序并跳出消息循环,只需调用PostQuitMessage函数即可。

PostQuitMessage(0);

PostQuitMessage函数在消息队列上放置一个WM_QUIT消息。WM_QUIT是一个特殊的消息:它使GetMessage返回零,表示消息循环结束。这是修改后的消息循环。

// Correct.
MSG msg={};
while(GetMessage(&msg,NULL,0,0)){
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

只要GetMessage返回一个非零值,while循环中的表达式计算结果为true。调用PostQuitMessage之后,表达式变为false,程序跳出循环。(这个行为的一个有趣的结果是你的窗口过程永远不会收到一个WM_QUIT消息,所以在你的窗口过程中不需要这个消息的case语句。)
下一个明显的问题是:什么时候应该调用PostQuitMessage?我们将在”关闭窗口“主题中回到这个问题,但首先我们需要编写窗口过程。
发布消息与发送消息
上一节讨论了进入队列的消息。在某些情况下,操作系统会绕过队列直接调用窗口过程。
这个区别的术语可能会令人困惑:

  • 发布消息意味着消息进入消息队列,并通过消息循环(GetMessageDispatchMessage)进行调度。
  • 发送消息意味着消息跳过队列,操作系统直接调用窗口过程。

现在,这个区别并不是很重要。窗口过程处理所有消息,但有些消息绕过队列直接进入窗口过程。但是,如果您的应用程序在窗口之间进行通信,则会有所不同。您可以在关于消息和消息队列主题中找到有关此问题的更全面的讨论。
下一个
编写窗口过程


原文链接:Window Messages