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.DoModal()来显示,然后调用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); }
如下代码是局部变量,函数执行完对象生命周期就已经结束,所以无法显示对话框。解决方案有两种,一种是把对话框定义为类的成员变量,另一种是定义为指针在堆上分配内存,而且非模式对话框,必须调用ShowWindow()来显示窗口
BOOL CMFCListCtrlDlg::OnInitDialog() { CTestDLG Dlg; Dlg.Create(IDD_DIALOG1,this); Dlg.ShowWindow(SW_SHOW); }
你要使用Create函数创建非模式对话框,并且在退出时,必须调用CWnd::DestroyWindow函数销毁窗口。而且要注意的是,你若想点击OK按钮使非模式对话框退出,要重写OnOK函数,使其调用CWnd::DestroyWindow。因为EndDialog只是隐藏了非模式对话框的显示。
如下的代码中对话框指针是一个局部变量,在执行完时候地址就丢失了,造成内存的泄漏,解决方法一是窗口指针定义为类的成员变量,在析构函数中delete,方法二是重载对话框类中的PostNcDestroy()虚函数,×××××修改后的对话框类是不能同时用于模式和非模式对话框的创建的××××××××。
void CMFCClass::OnBnClicked() { CTestDLG *dlg=new CTestDLG(); dlg->Create(IDD_DIALOG1,this); dlg->ShowWindow(SW_SHOW); }
void CTestDLG::PostNcDestroy() { delete this; CDialog::PostNcDestroy(); }