在PreTranslateMessage中调用t总是出错的原因分析

来源:互联网 发布:淘宝商家不提供发票 编辑:程序博客网 时间:2024/05/29 04:45

        在一个窗口类里面实现了PreTranslateMessage函数,响应一个快捷键D,然后在里面实现了一个函数,这个函数里面需要弹出一个对话框显示结果。但是一旦调用DoModal(),程序就会挂掉。找寻好久都无果。换做非模态对话框,没有问题,但是实现颇为麻烦。最后找到一篇文章,大概描述了原因,虽然没有看明白:


PreTranslateMessage(MSG* pMsg)中调用DoModal()模态窗口如下:

 1 BOOL CMainDlg::PreTranslateMessage(MSG* pMsg)
 2 {
 3     // TODO: Add your specialized code here and/or call the base class
 4     if ( pMsg->message == WM_LBUTTONDOWN)
 5     {
 6         GetWindowRect(m_oldRect);
 7         ::SetCapture(this->m_hWnd);
 8         m_bCanDrag = TRUE;
 9         m_lastPt = pMsg->pt ;
10     }
11     else if ( pMsg->message == WM_LBUTTONUP)
12     {
13         if( m_bCanDrag )
14         {
15             ::ReleaseCapture();
16             m_bCanDrag = FALSE;
17             GetWindowRect(m_newRect);
18             if (m_oldRect.EqualRect(m_newRect))
19             {
20                 GetMainItemID(pMsg);//调用对话框函数
21                 //return TRUE;
22             }
23         }
24     } 
25     else if( pMsg->message == WM_MOUSEMOVE)
26     {
27         if( m_bCanDrag )
28         {
29             CRect rc;
30             GetWindowRect(&rc);
31             rc.OffsetRect( pMsg->pt.x - m_lastPt.x , pMsg->pt.y - m_lastPt.y  ) ;
32             m_lastPt = pMsg->pt;
33             this->MoveWindow( rc );
34         }
35     }
36 
37     return CDialog::PreTranslateMessage(pMsg);
38 }

39  void CMainDlg::GetMainItemID(MSG* pMsg)
40 {
41     if (pMsg->hwnd == GetDlgItem( IDC_BTN_MYCOMPUTER )->m_hWnd)
42     {
43        CTestDlg dlg;
44        dlg.DoModal();
45     }
46 }

再单击对话框上的按钮时发送断言中断,具体位置如下:


::IsWindow(m_hWnd)

函数功能:该函数确定给定的窗口句柄是否标识一个已存在的窗口。
函数原型:BOOL IsWindow(HWND hWnd);
参数:
hWnd:被测试窗口的句柄。
返回值:如果窗口句柄标识了一个已存在的窗口,返回值为非零;如果窗口句柄未标识一个已存在窗口,返回值为零。

可能原因:在PreTranslateMessage里的获取对应m_hWnd,DoModal()模态对话框退出后,m_hWnd不是有效的窗口句柄。
解决办法:处理完WM_LBUTTONUP后,需要返回TRUE。


通过以上内容,可以清晰的找到解决方法,

就是在PreTranslateMessage函数里面,如果调用了DoModal(),就需要直接return TRUE。不用再按顺序的执行
 return CXXDlg::PreTranslateMessage(pMsg); 
但是为什么这么做,还是不太明白,留作继续学习。


-------------------------------------------------------------------------------------------------------------
后续:刚发完,就看到另一篇文章讲述的更为详细,同转:


dlg.DoModal()截住了界面消息,所以返回时原来的pMsg的内容已经更改了,消息,窗口句柄都不在是if以前的值了,而且窗口句柄应该是对话框里的子窗口的句柄,所以调用CFrameWnd::PreTranslateMessage(pMsg); 
时pMsg的窗口句柄是个无效值(窗口已销毁) 


BOOL CViewUP::PreTranslateMessage(MSG* pMsg) 
{
    if (pMsg->message == WM_KEYDOWN)
   {
       if(pMsg->wParam =='M' || pMsg->wParam == 'm')//暂时为按“M”键退出系统
       {
          AfxGetApp()->m_pMainWnd->SendMessage(WM_CLOSE,0L,0L);
          return TRUE;
        }
        else if(pMsg->wParam=='Z' || pMsg->wParam == 'z')//暂时为按“Z”键启动就地系统
        {
            //激活上一个窗口还是退出??因须要而定
             CWnd* pWnd = FindWindow(NULL,_T("就地站_JD"));
             if (pWnd)
             {
                   pWnd->ShowWindow(SW_SHOWNA );//SW_SHOWMAXIMIZED);
                   pWnd->SetForegroundWindow();
                   return TRUE;
              }

             // CAONumValueDlg aoDlg; 
             // aoDlg. DoModal();
         }
     }
    return CView::PreTranslateMessage(pMsg);
}


注意事项 

模态窗口极大地简化了一些需要和用户交互的操作,好处显而易见。但这里还是要指出一些需要注意的地方,否则使用的时候很可能会出问题。

影响PreTranslateMessage机制 

在使用MFC,WTL等进行开发的时候,经常用到PreTranslateMessage机制,这个机制可以让我们在消息被派发之前先做一些事情。很多人以为PreTranslateMessage是Windows本身支持的,其实不然。PreTranslateMessage是MFC和WTL自己引入的一个概念,完全是和Windows无关的。在MFC和WTL的消息循环中,这两个库的设计者在消息分发之前,人为的加了一些代码,使得整个架构支持这一套机制。

正是如此,如果在正常的流程中弹出了模态窗口,就会使正常的PreTranslateMessage机制失效。因为模态窗口中已经包含了一个消息循环,接管了线程中缺省的消息循环。而这个消息循环是在DialogBox这个API函数中执行的,显然不可能再有PreTranalateMessage机制了。

为了解决这一问题,只有让模态窗口也使用和UI线程相同的消息循环,MFC正是这么做的。在MFC中,对话框类的DoModal函数,并不是调用DialogBox函数,而是直接使用CreateWindows创建一个非模态窗口,在窗口创建成功之后再调用MFC自己的消息循环,这样就可以让PreTranslateMessage继续生效。同时在窗口创建出来之后,必须再做一些别的操作,使这个模态窗口的父窗口失效(一般直接把窗口Disable掉)。同时消息循环里有合适的退出条件,并有恢复现场的一些操作,具体可以查看MFC的DoModal函数。

WTL到目前为止,貌似暂时还没有一个合适的方案来解决这个问题。事实上WTL的PreTranslateMessage机制实现的其实是有点问题的,或许以后会在这方面做一定的增强。

可能导致崩溃 

这是一个严重问题,在条件合适的情况下,这个崩溃是必然的。

因为模态窗口弹出来之后,模态窗口后面的代码在窗口关闭之前将不会得到执行。然而此时整个窗口是在正常运行的,对于一些极端的情况,是极有可能造成崩溃的。下面看一个例子:


void CTestDlg::OnOK()

{

CInputDialog dlg;

If(dlg.DoModal() == IDOK)

{

m_nValue = dlg.GetValue();

UpdateData(FALSE);

}

}


这是一段典型的MFC代码,在绝大多数情况下,不会有任何问题。但是由于模态窗口弹出的时候,只是父窗口不能操作,但别的窗口完全还能正常运行,这时候就非常有可能由于某种原因,CTestDlg类已经销毁了,而CInputDialog却不知道,还在继续执行,结果到了IDOK之后,对CTestDialog类的成员变量m_nValue赋值,就会出现崩溃了。

这个问题,如果在多线程的情况下,将会更加严重。因为在多线程的情况下,将会有更加多的不可预料的因素,所以使用的时候要更加小心。


改成这样就OK了, 
if (pMsg-> message == WM_CHAR) 

MSG msg = *pMsg;//后来发现这样还是有点问题,模态对话框回车后,鼠标不见了 
CMyDlg dlg; 
dlg.DoModal(); 
*pMsg = msg; //后来发现这样还是有点问题,模态对话框回车后,鼠标不见了


return TRUE;//最终方法还是在这里直接返回吧,破坏消息循环总是不好的。

我估计是MFC保存了一个当前消息的结构来跟踪消息路由,dlg.DoModal();时这个结构的值都更新好多遍了


0 0
原创粉丝点击