zoukankan      html  css  js  c++  java
  • Win32编程API 基础篇 -- 4.消息循环

    消息循环

    理解消息循环

      为了编写任何即使是最简单的程序,了解windows程序的消息循环和整个消息发送结构是非常有必要的。既然我们已经尝试了一点消息处理的东西,我们应该对整个程序有更深入的理解,如果你没有理解消息是怎么发生的和它们运行的机制,那接下来的内容你会感到很蛋疼。

     

    什么是消息?

      一条消息是一个整数值,如果你查阅你的头文件(这是个好的查阅API的工作惯例)你会发现像下面的东西:

     

    1 #define WM_INITDIALOG                   0x0110
    2 #define WM_COMMAND                      0x0111
    3 
    4 #define WM_LBUTTONDOWN                  0x0201

     

      还有很多类似的东西。在windows中消息几乎被运用在基本水平上的所有通信,如果你想要一个窗口控制(只是一个制定的窗口)做某些事情你需要发送给它一条消息;同样的如果另一个窗口想让你做某些事情,它会发送给你一条消息;如果某些像用户点击键盘、移动鼠标、点击按钮的事件发生,那么相应的消息会被系统发送到受影响的窗口,如果你是其中一个受影响的窗口,那么你需要处理消息并采取相应的行动。

      每条窗口消息都可能有两个参数,wParam和lParam,最初wParam是16位而lParam是32位,但是在win32中它们都是32位。不是每条消息都需要用到这些参数,用到时的用法也不同,举个例子,WM_CLOSE消息就都没有用到这两个参数,这时我们可以忽略这两个采纳数;而WM_COMMAND就同时用到了这两个参数,wParam包含了两个值,HIWORD(wParam)是一条通知消息(如果可用的话),LOWORD(wParam)是一个发送消息的控制或菜单id。

      lParam是控制发送消息或NULL(如果消息不是从控制中来的话)的HWND(窗口句柄)

      HIWORD()和LOWORD()被windows宏定义,这两个值都是windows中的宏定义,一个指32位(0xFFFF0000)的高16位(FFFF),一个指32位的低16位(0000);在win32中一个WORD表示的是16位,而DWORD(double word)才表示32位

      你可以通过PostMessage()或SendMessage()这两种方式发送消息。PostMessage()将消息插入到消息队列中然后马上返回,这意味着即使一次PostMessage()的调用被完成时,消息可能还没被处理;SendMessage()直接发送消息给窗口并且会不会立即返回,知道窗口完成了对消息的处理。如果我们想关闭一个窗口,我们可以通过PostMessage(hwnd, WM_CLOSE, 0, 0)发送一个WM_CLOSE的消息,这跟我们点击窗口右上方的X按钮有同样的效果,注意在WM_CLOSE消息的相应处理中,wParam和lParam都是0,这是因为刚才提到的在WM_CLOSE消息处理中没有用到这两个参数。

     

     

    对话框

     

      一旦你开始使用对话框盒子,为了跟它们交流你将需要发送消息给控制。你可以通过使用GetDlgItem()通过使用ID先获取控制的句柄然后使用SendMessage(),或者你可以使用SendDlgItemMessage(),这个方法组合了上面的两个步骤。你给它一个窗口句柄和孩子ID将会得到一个孩子句柄,然后向他发送消息。SendDlgItemMessage()和类似的APIGetDlgItemText()将会作用在所有的窗口上,而不仅仅是对话框。

     

     

    什么是消息队列?

      让我们试着想象当你忙着处理WM_PAINT的时候突然用户用键盘输入一堆东西,会发生什么事情?你绘图的时候被键盘打断应该抛弃吗?当然不是!很明显不管抛弃哪个都不是正确的做法,所以我们有了消息队列。新增POST过来的消息会被添加到消息队列中来,当消息被处理时消息就会从消息队列中被移除,这确保了你不会错过一条消息,如果你正在处理一条消息,那么其他消息就会在队列中等待直到你开始处理它们。

     

     

    什么是一个消息环?

    1 while(GetMessage(&Msg, NULL, 0, 0) > 0)
    2 {
    3     TranslateMessage(&Msg);
    4     DispatchMessage(&Msg);
    5 }

    1、在消息队列里,消息环调用GetMessage()方法,如果你的消息队列空了,那么你的程序

       将会停止然后等待消息,相当于挂起的状态

    2、当一个事件发生时,会导致一条消息加入到消息队列中(比如系统注册一个鼠标点击)

       GetMessage()返回一个正值表明有一个消息要处理,并且复制给我们传递给它的MSG数

       据结构的成员,方法也可能会返回0如果消息是WM_QUIT,如果是发生错误的话方法会

       返回负数。

    3、我们得到消息(在Msg变量中)然后把它传递给TranslateMessage(),这会产生一点额外

       的处理,将虚拟的键盘点击翻译成字符消息,这个步骤实际上是可选的,如果不需要时

       它不会发生。

    4、一旦上面的步骤完成,我们把消息传递给DispatchMessage(),DispatchMessage()会做的

       工作就是拿到消息,检查它是哪个窗口的然后找到对应窗口的消息处理程序,接着调用

       消息处理程序,并且将窗口的句柄,消息,wParam和lParam四个参数传递给它。

    5、在你的消息处理程序中,你会检查参数消息,然后对它做任何你想做的事情,如果你没

       有处理指定的消息,那么默认它会调用DefWindowProc()方法,这个消息处理的默认操作,

       通常意味着什么都不做。

    6、一旦你已经完成了消息处理,你的消息处理程序会返回,DisPatchMessage()返回,然后

       我们又回到了环开始的地方。

     

      这是窗口程序中一个非常重要的概念,你的消息处理程序不是神奇地由系统调用,实际上你是间接地通过DispatchMessage()调用。如果你想要的话,你可以在窗口句柄中使用GetWindowLong()直接调用窗口处理程序。

     

    1 while(GetMessage(&Msg, NULL, 0, 0) > 0)
    2 {
    3     WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
    4     fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
    5 }

     

      我试着用前面的代码,但是并没有正常工作,因为有各种各样的问题比如Unicode/Ansi编码翻译等,调用定时器回调方法不会占用,这可能会打破我们目前的所有但微不足道的程序,所以你可以尝试,但在实际的代码中不要真的这么做: )

      注意,我们使用GetWindowLong()来检索窗口相关的消息处理程序,为什么我们不直接调用WndProc()呢?嗯,我们的消息环是要对我们程序中所有窗口都适用的,包括有自己的窗口过程的按钮和列表框,所以我们应该保证调用了正确的窗口过程,因为多个窗口可以使用同一个窗口过程,所以第一个参数(窗口句柄)用来告诉窗口过程消息属于哪个窗口的。

      正如你所看到的,你的应用程序的大多数时间都花费在执行这个消息循环,你可以愉悦地发送消息给可以处理它们的快乐的窗口。但是当你想退出程序时你会怎么做?因为我们使用了一个while()循环,如果GetMessage()返回了false,循环将会结束并且到达我们WinMain()的终点接着退出程序。这正是PostQuitMessage()完成的东西,它把WM_QUIT消息放入队列中而不是返回一个正值,GetMessage()会填充Msg数据结构的内容然后返回0,在这个时候Msg的wParam成员就包含了你传递给PostQuitMessage()的值,这里你可以忽略它,从WinMain()的返回值将会使用在进程结束的退出代码中。

    重点:GetMessgae()将会返回-1如果出现错误的话,确保记住这个,否则在某些时候会让你很蛋疼。。。即使GetMessage()被定义用来返回一个布尔值,它也能够返回TRUE或FALSE之外的值,因为BOOL使用UINT(unsigned int)来定义的。下面的代码例子可能可以运行,但是不会正确地处理一下条件:

     

    1     while(GetMessage(&Msg, NULL, 0, 0))
    2 
    3     while(GetMessage(&Msg, NULL, 0, 0) != 0)
    4 
    5     while(GetMessage(&Msg, NULL, 0, 0) == TRUE)

     

      上面都是错的!就像我刚刚提到的,它可能跟我第一个教程里使用的差不多,只要GetMessage()没有失败它会工作地很好,即使你的代码是正确的,这个也不是正确的,但是在很多情况下这个代码是错误的不能正常工作,当GetMessage()发生失败的时候!这里我们郑重说明和纠正,请原谅我错过了一些要点。

     

    1 while(GetMessage(&Msg, NULL, 0, 0) > 0)

     

      这个才是正确的!

      我希望现在你对窗口消息循环有更好的理解,如果还不是非常理解的话,别害怕,一旦你使用它们一段时间之后情况会好很多。

      PS.由于本人英文水平所限,只能翻译到这个程度了,有纰漏还望多多指出,附上本篇翻译的英文原版教程地址:http://www.winprog.org/tutorial/message_loop.html

     

  • 相关阅读:
    【笔记】求数据前n个主成分以及对高维数据映射为低维数据
    使用sklearn中的fetch_mldata的错误情况以及可能可行的解决方法
    【笔记】求数据的对应主成分PCA(第一主成分)
    【笔记】主成分分析法PCA的原理及计算
    【笔记】如何确定梯度计算的准确性以及调试梯度下降法
    【笔记】随机梯度下降法
    【笔记】线性回归中的梯度下降法(实现以及向量化并进行数据归一化)
    AttributeError: module 'numpy' has no attribute 'num'
    灵雀云CTO陈恺:从“鸿沟理论”看云原生,哪些技术能够跨越鸿沟?
    容器云在证券行业的探索与实践
  • 原文地址:https://www.cnblogs.com/huyihao/p/5851601.html
Copyright © 2011-2022 走看看