在PreTranslateMessage中調用t總是出錯的原因分析

        在一個窗口類裏面實現了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();時這個結構的值都更新好多遍了


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章