论模式和非模式对话框

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();
}


你可能感兴趣的:(论模式和非模式对话框)