WainZhang
人生总有许多巧合,两条平行线也可能会有交汇的一天。 人生总有许多意外,握在手里面的风筝也会突然断了线。 在这个熟悉又陌生的城市中,无助地寻找一个陌生又熟悉的身影。

窗口消息

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

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

这些事件可以在程序运行时的任何时间发生,几乎可以按任何顺序发生。如何构造一个不能预先预测执行流(flow)的程序?

为了解决这个问题,Windows使用了消息传递模型。操作系统通过传递消息与应用程序窗口进行通信。消息只是指定特定事件的数字代码。例如,如果用户按下鼠标左键,窗口将接收到具有以下消息代码的消息。

C++

#define WM_LBUTTONDOWN    0x0201

 

一些消息具有与它们相关联的数据。例如,WM_LBUTTONDOWN消息包含鼠标光标的x坐标和y坐标。

要将消息传递给窗口,操作系统将调用为该窗口注册的窗口过程。(现在你知道窗口程序的作用了。)

消息环(The message loop

应用程序在运行时将收到数千条消息。(考虑到每次击键和点击鼠标按钮都会产生一条消息。)另外,应用程序可以有多个窗口,每个窗口都有自己的窗口过程。程序如何接收所有这些消息并将它们发送到正确的窗口过程?应用程序需要一个循环来检索消息并将其发送到正确的窗口。

对于创建窗口的每个线程,操作系统都为窗口消息创建一个队列(queue)。此队列保存了在该线程上创建的所有窗口的消息。队列本身在程序是隐藏不可见的。您不能直接操作队列。但是,您可以通过调用 GetMessage 函数从队列中提取消息。

C++

MSG msg;

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

 

此函数会从队列的头部移除第一个消息。如果队列为空,函数将阻塞,直到另一条消息入队列。事实上,GetMessage阻塞不会使您的程序没有响应。如果没有消息,程序就没有什么可做的。如果您必须执行后台处理,您可以创建额外的线程,在GetMessage等待另一个消息时继续运行。(参见 Avoiding Bottlenecks in Your Window Procedure)。

GetMessage的第一个参数是MSG结构体的地址。如果函数成功,它将在MSG结构体中填充消息信息。这包括目标窗口和消息代码。其他三个参数可以过滤(filter)从队列中获取的消息。通常情况下,这些参数设置为零。

虽然MSG结构体包含了关于消息的信息,但是您几乎永远不会直接检查这个结构体。相反,您会将把它直接传递给另外两个函数:

C++

TranslateMessage(&msg);

DispatchMessage(&msg);

 

 TranslateMessage与键盘输入有关。它将按键(按键向下,按键向上)转换成字符。你并不需要知道这个函数是如何工作的;只需要记住在 DispatchMessage之前调用它。如果您有兴趣,MSDN文档的链接将为您提供更多的信息。

DispatchMessage 函数告诉操作系统调用作为消息目标的窗口过程。换句话说,操作系统在窗口表中查找窗口句柄,找到与窗口关联的函数指针,并调用该函数。

例如,假设用户按下鼠标左键。这导致一系列事件:

  1. 操作系统在消息队列(message queue)上放置一个WM_LBUTTONDOWN消息。
  2. 你编写的程序调用GetMessage函数。
  3. GetMessage从队列中提取WM_LBUTTONDOWN消息并填充MSG结构体。
  4. 你编写的程序调用TranslateMessage 函数和DispatchMessage 函数。
  5. DispatchMessage中,操作系统调用窗口过程(window procedure)。
  6. 你的窗口过程(window procedure)可以响应消息,也可以忽略它

当窗口过程(window procedure)调用结束后,它返回到DispatchMessage。接着将返回到下一个消息的消息循环。只要您的程序正在运行,不断会有消息进入队列。因此,您必须有一个循环,该循环不断地从队列中提取消息并分派它们。你可以把这个循环想成是这样的:

C++

// WARNING: Don't actually write your loop this way.

 

while (1)     

{

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

    TranslateMessage(&msg);

    DispatchMessage(&msg);

}

 

当然,正如所写的,这个循环永远不会结束。这就是GetMessage函数的返回值。通常,GetMessage返回一个非零值。当您想要退出应用程序并跳出消息循环时,请调用 PostQuitMessage函数。

C++

        PostQuitMessage(0);

 

PostQuitMessage 函数将一个WM_QUIT消息放到消息队列中。WM_QUIT是一个特殊的消息:它导致GetMessage 返回零,发出消息循环结束的信号。下面是修改后的消息循环。

syntax

// Correct.

 

MSG msg = { };

while (GetMessage(&msg, NULL, 0, 0))

{

    TranslateMessage(&msg);

    DispatchMessage(&msg);

}

 

只要 GetMessage 返回一个非零值,while循环中的表达式将计算为true。调用PostQuitMessage后,表达式变为false,程序将跳出循环。(这种行为的一个有趣结果是,您的窗口过程永远不会收到WM_QUIT 消息。因此,您不必在窗口过程中为该消息写一个case语句。

下一个问题是什么时候调用PostQuitMessage。我们将在 Closing the Window主题中回到这个问题,在此之前,我们必须编写窗口过程(window procedure)。

发布消息与发送消息(Posted Messages versus Sent Messages

前一节讨论了进入队列的消息。有时,操作系统会直接调用窗口过程,绕开队列。

两个术语可能会令人困惑:

  • 发布消息(Posting a message)意味着消息进入消息队列,并通过消息循环(GetMessage 和 DispatchMessage)发送。
  • 发送消息(Sending a message)意味着消息跳过队列,操作系统直接调用窗口过程。

就目前而言,差异并不十分重要。窗口过程(window procedure)处理所有消息。但是,一些消息绕过队列,直接进入窗口过程。但是,如果您的应用程序在windows之间进行通信,则会产生不同的结果。您可以在 About Messages and Message Queues的主题中找到对这个问题更深入的讨论。

 

posted on 2018-07-26 10:59  WainZhang  阅读(2036)  评论(0编辑  收藏  举报