模态对话框的消息处理机制分析

                                                    模态对话框的消息处理机制分析
最近在工作中遇到了一个这样的需求:
    用户在我线程A的一个CWnd窗口中点击“设置”按钮,我发送一个UM_CONFIG消息给另外一个线程B,由另外一个线程B响应我的UM_CONFIG消息,弹出一个用于用户设置相关参数

的对话框。这时候出现一个问题,就是当用户多次点击这个“设置”按钮的时候,会弹出多个设置对话框。
     于是同时提到了在我的线程A中用这样一种方法来解决:

     1.在类成员中定义一个BOOL变量m_bIsOnly; 初始化为 TRUE;
     2.在“设置”按钮的响应函数OnConfig中:
          (1)判断
              if(m_bIsOnly==FALSE)
                 return;
          (2)在发送消息之前
             m_bIsOnly=FALSE;
          (3)发送完消息后
             m_bIsOnly=TRUE;

       这样来控制只出现一个设置对话框;


个人是不怎么同意这个解决方法的,因为我觉得不管是用SendMessage或者PostMessage来投递这个UM_CONFIG消息,当我第一次进入
OnConfig()函数时候,其他消息是不能响应的,直到我处理完这个点击事件之后,While消息循环才能够分发下一个消息,才能重新响应点击事件,进入OnConfig()函数。但是第二

次进入之前,m_bIsOnly实际还是TRUE的,因为在第一次退出OnConfig函数前,会重新设置m_bIsOnly=TRUE;
按照这个分析,我觉得这种方法是行不通的。

在我的线程A中加入这个处理逻辑后,不管我是PostMessage(UM_CONFIG)还是SendMessage(UM_CONFIG)给线程B, 效果如下:点击多次“设置”按钮,只会出现一个设置对话框,但

是当关闭设置对话框后,就会出现第二个设置对话框,再次关闭,再次出现新的设置对话框。从这里可以看出,MFC对“设置”按钮自动添加的OnConfig函数响应实际是用

PostMessage来处理的,也就是说用户点击“设置”对话框产生的消息是Post在线程A的消息队列里的。


所以,同事把这个逻辑加到了他的线程B中,他在响应我的UM_CONFIG消息时候,弹出的是个模态的设置对话框。我是调用
SendMessage(UM_CONFIG)给他的,结果还是发现多次点击“设置”按钮的时候,只会出现一个设置对话框,但是关闭后,又会出现第二个设置对话框。奇怪的是不管点击多少次“

设置”,都是出现两个设置对话框。

当把我的发送消息的方式由SendMessage(UM_CONFIG)改为PostMessage(UM_CONFIG)后,问题完美解决,无论用户怎么点击“设置”按钮,都只会出现一个模态的设置对话框,而且

关闭后也不再出现。

这几种情况到底有什么区别,导致效果不同呢?为此我查阅相关资料研究了下,原因就在于模态对话框的消息处理过程,它会导致消息重入:
    当一个窗口时模态对话框时,它的消息处理过程是有点特殊的。模式对话框都有自己的消息循环,它阻塞的是原始的消息循环,但是被对话框的消息循环接替。消息循环的本

质是调用窗口过程,进一步调用你的各种消息响应函数,所以无论有多少个消息循环存在,只要有一个消息循环有效,所有的消息响应函数都能被调用,这也是为什么主窗口还能

响应消息的缘故。多个消息循环的存在会产生某些副作用,比如消息重入,第一次消息响应时弹出一个模式对话框,模式对话框的消息循环2取代原始消息循环1,假设此时主窗口

消息队列里又有一个同样的消息到来,消息循环2也会调用同样的窗口过程(响应函数),此时就能导致消息重入(因为第一次进入这个处理函数还没有返回,本质上这个响应函数

被递归调用了),这也是能弹出多个模式对话框的原因。每个模式对话框都有自己的消息循环,只有最后一个弹出对话框的消息循环才是活动的消息循环,其它所有消息循环(包

括主窗口、之前弹出的模式对话框)全被阻塞。这里的“阻塞”并不是消息被阻塞,而只是DispatchMessage一直没有返回而已,但是其它的消息循环会接替这些工作。
网上有人写出了模拟模态对话框的消息循环的代码:
 BOOL   DoModule()  
  {  
      HWND   hParent   =   ::GetActiveWindow();  
      Create(hParent);  
      CenterWindow();  
      ::EnableWindow(hParent,   FALSE);  
      ShowWindow(SW_RESTORE);  
      SetActiveWindow();  
      MSG   msg;  
      while(TRUE)  
      {  
          while(::PeekMessage(&msg,   NULL,   0,   0,   PM_NOREMOVE))  
          {  
              if(msg.message   ==   WM_QUIT)  
              {  
                  ::EnableWindow(hParent,   TRUE);  
                  ::SetActiveWindow(hParent);  
   
                  return   FALSE;  
              }  
              ::GetMessage(&msg,   NULL,   0,   0);  
              if(msg.message   ==   hParent   &&   msg.message   !=   WM_PAINT)  
              {  
              continue;  
              }  
              ::TranslateMessage(&msg);  
              ::DispatchMessage(&msg);  
              if(!IsWindow())  
              {  
                  ::EnableWindow(hParent,   TRUE);  
                  ::SetActiveWindow(hParent);  
                  return   TRUE;  
              }  
          }  
          else   ::WaitMessage();  
      }  
      return   FALSE;  
  }

这样就可以解释明白了:
    (1)当在我的线程A中使用这个策略解决问题时,由于我的是普通的CWnd窗口,并且是PostMessage方式来调用我的OnConfig()函数的,所以这个时候是不存在消息重入的,

所以根本不能起作用。
    (2)当将这个处理逻辑移到目标线程B,并且我以SendMessage(UM_CONFIG)的时候,这时候是存在消息重入的,因为SendMessage是直接调用的消息处理函数,不存在进入消息

队列等待的情况。但是为什么最终还是会出现两个设置对话框呢,我觉得原因可能在于:点击"设置"按钮的速度过快,导致第一个设置窗口还没有开始设置m_bIsOnlay的值为false

之前,就已经进入了第二个设置窗口的响应。
    (3)当将这个处理逻辑移到目标线程B,并且我以PostMessage(UM_CONFIG)发送消息的时候,问题完美解决。原因:这个时候存在消息重入,由于我是PostMessage的,所以

UM_CONFIG会进入到线程B的消息队列,这样线程B在处理第一个消息的时候,先设置好了m_bIsOnly=false,后才会创建自己的消息循环,取得第二个UM_CONGIG消息,调用消息处理

函数,这时m_bIsOnly=FALSE,所以直接返回,达到了只出现一个设置对话框的目的。


最后总结下消息重入的两种情况:
1.SendMessage
2.模态对话框,MessageBox之类内部拥有自己的消息循环的情况

 

你可能感兴趣的:(VC/MFC)