Mqt-2003
小豆
論模式和非模式對話框
論模式和非模式對話框
作者:孟慶濤 Email: [email protected]
論模式和非模式對話框... 1
1:摘要... 1
2:模式對話框的顯示... 1
3:模式對話框的循環等待... 3
4:模式對話框的循環終止... 6
5:與OK和Cancle按鈕的聯繫... 6
1:摘要
模式對話框使用dlg.DoModal()函數,程序會在你按下OK或者Cancle按鈕之前處於等待狀態。然後點擊OK或者Cancle按鈕,就可以調用EndDialog函數消除模式對話框。
相比之下,非模式對話框可能要顯得複雜,你要使用Create函數創建非模式對話框,並且在推出時,必須調用CWnd::DestroyWindow函數銷燬窗口。而且要注意的是,你若想點擊OK按鈕使非模式對話框推出,要重寫OnOK函數,使其調用CWnd::DestroyWindow。
那麼,這是爲什麼呢?模式對話框的實現真的比非模式要簡單嗎?讓我們看一下CDialog::DoModal()的源代碼。
2:模式對話框的顯示
INT_PTR CDialog::DoModal()
{
***********************************************
//加載模板資源
************************************************
ASSERT(m_lpszTemplateName != NULL || m_hDialogTemplate != NULL ||
m_lpDialogTemplate != NULL);
// load resource as necessary
LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;
HGLOBAL hDialogTemplate = m_hDialogTemplate;
HINSTANCE hInst = AfxGetResourceHandle();
if (m_lpszTemplateName != NULL)
{
hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);
HRSRC hResource = ::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);
hDialogTemplate = LoadResource(hInst, hResource);
}
if (hDialogTemplate != NULL)
lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);
// return -1 in case of failure to load the dialog template resource
if (lpDialogTemplate == NULL)
return -1;
***********************************************
//使父窗口無效
***********************************************
HWND hWndParent = PreModal();
AfxUnhookWindowCreate();
BOOL bEnableParent = FALSE;
if (hWndParent && hWndParent != ::GetDesktopWindow() && ::IsWindowEnabled(hWndParent))
{
::EnableWindow(hWndParent, FALSE);
bEnableParent = TRUE;
}
TRY
{
***********************************************
//創建非模式對話框
***********************************************
AfxHookWindowCreate(this);
if (CreateDlgIndirect(lpDialogTemplate,
CWnd::FromHandle(hWndParent), hInst))
{
if (m_nFlags & WF_CONTINUEMODAL)
{
// enter modal loop
DWORD dwFlags = MLF_SHOWONIDLE;
if (GetStyle() & DS_NOIDLEMSG)
dwFlags |= MLF_NOIDLEMSG;
***********************************************
//關鍵:調用RunModalLoop函數,程序進入其內的for循環
//所以,模式對話框在點擊OK或Cancel前,程序會暫時等待。
***********************************************
VERIFY(RunModalLoop(dwFlags) == m_nModalResult);
}
***********************************************
//在父窗口可用前,先隱藏對話框(注:暫時還沒有銷燬)
***********************************************
if (m_hWnd != NULL)
SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW|
SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);
}
}
CATCH_ALL(e)
{
DELETE_EXCEPTION(e);
m_nModalResult = -1;
}
END_CATCH_ALL
***********************************************
//使父窗口可用,並且激活父窗口
***********************************************
if (bEnableParent)
::EnableWindow(hWndParent, TRUE);
if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)
::SetActiveWindow(hWndParent);
***********************************************
//銷燬對話框
***********************************************
// destroy modal window
DestroyWindow();
PostModal();
// unlock/free resources as necessary
if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL)
UnlockResource(hDialogTemplate);
if (m_lpszTemplateName != NULL)
FreeResource(hDialogTemplate);
return m_nModalResult;
}
3:模式對話框的循環等待
從上面的代碼,我們可以發現,模式對話框的底層爲我們實現了對話框的create和destroywindow,所以我們可以只管dlg.domoadl()來顯示,然後調用EndDialog來結束。那麼EndDialog 的作用是什麼呢?我們看它裏面的循環函數,就可以理解,原來Enddialog的作用其實是爲了跳出循環函數RunModalLoop,使程序繼續執行。
具體代碼如下:
int CWnd::RunModalLoop(DWORD dwFlags)
{
ASSERT(::IsWindow(m_hWnd)); // window must be created
ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);
HWND hWndParent = ::GetParent(m_hWnd);
m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);
MSG *pMsg = AfxGetCurrentMessage();
**************************************************
//通過for (;;),使程序處於循環等待狀態。
****************************************************
for (;;)
{
ASSERT(ContinueModal());
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
{
ASSERT(ContinueModal());
// show the dialog when the message queue goes idle
if (bShowIdle)
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
// call OnIdle while in bIdle state
if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)
{
// send WM_ENTERIDLE to the parent
::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);
}
if ((dwFlags & MLF_NOKICKIDLE) ||
!SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))
{
// stop idle processing next time
bIdle = FALSE;
}
}
// phase2: pump messages while available
do
{
ASSERT(ContinueModal());
// pump message, but quit on WM_QUIT
if (!AfxPumpMessage())
{
AfxPostQuitMessage(0);
return -1;
}
// show the window when certain special messages rec'd
if (bShowIdle &&
(pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
*************************************************************8
//通過判斷,跳出循環,可以斷定,EndDialog 和ContinueModal有聯繫
**************************************************************
if (!ContinueModal())
goto ExitModal;
// reset "no idle" state after pumping "normal" message
if (AfxIsIdleMessage(pMsg))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
}
ExitModal:
m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);
return m_nModalResult;
}
4:模式對話框的循環終止
EndDialog函數調用EndMoadlLoop函數,以便跳出循環。
void CDialog::EndDialog(int nResult)
{
ASSERT(::IsWindow(m_hWnd));
if (m_nFlags & (WF_MODALLOOP|WF_CONTINUEMODAL))
EndModalLoop(nResult);
::EndDialog(m_hWnd, nResult);
}
BOOL CWnd::ContinueModal()
{
return m_nFlags & WF_CONTINUEMODAL;
}
void CWnd::EndModalLoop(int nResult)
{
ASSERT(::IsWindow(m_hWnd));
*****************************************************
// m_nModalResult的值爲IDOK或者IDCANCEL,它將作爲DoModal的返回值
*****************************************************
m_nModalResult = nResult;
// make sure a message goes through to exit the modal loop
if (m_nFlags & WF_CONTINUEMODAL)
{
m_nFlags &= ~WF_CONTINUEMODAL;
PostMessage(WM_NULL);
}
}
5:與OK和Cancle按鈕的聯繫
爲什麼按下OK或者Cancle按鈕會終止模式對話框呢?因爲它們都調用了EndDialog函數,代碼如下:
注:IDOK和IDCANCEL將會作爲DoModal的返回值。
從下面的代碼可以看出,OnOK()和OnCancel()消息響應函數並沒有調用DestroyWindow,它們只是調用了EndDialog跳出循環,並沒有銷燬窗庫。對模式對話框,DoModal函數自動調用DestroyWindow,而對非模式對話框,我們若要使用OK或者Cancle按鈕結束對話框,必須重寫OnOK按鈕以使其調用DestroyWindow銷燬窗口。
void CDialog::OnOK()
{
if (!UpdateData(TRUE))
{
TRACE(traceAppMsg, 0, "UpdateData failed during dialog termination./n");
// the UpdateData routine will set focus to correct item
return;
}
EndDialog(IDOK);
}
void CDialog::OnCancel()
{
EndDialog(IDCANCEL);
}