在一個窗口類裏面實現了PreTranslateMessage函數,響應一個快捷鍵D,然後在裏面實現了一個函數,這個函數裏面需要彈出一個對話框顯示結果。但是一旦調用DoModal(),程序就會掛掉。找尋好久都無果。換做非模態對話框,沒有問題,但是實現頗爲麻煩。最後找到一篇文章,大概描述了原因,雖然沒有看明白:
在PreTranslateMessage(MSG*
pMsg)中調用DoModal()模態窗口如下:
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 }
再單擊對話框上的按鈕時發送斷言中斷,具體位置如下:
可能原因:在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();時這個結構的值都更新好多遍了