模态对话框原理

1. 模态对话框

在涉及GUI程序开发的过程中,常常有模态对话框以及非模态对话框的概念

模态对话框:在子界面活动期间,父窗口是无法进行消息响应。独占用户输入
非模态对话框:各窗口之间不影响

主要区别:非模态对话框与APP共用消息循环,不会独占用户。
模态对话框独占用户输入,其他界面无法响应

在用户层的主要逻辑如下: 
 
1TestDlg dlg;
2  
3if (dlg.DoModal() == IDOK)
4{
5           //处理完毕后的操作
6}
7.......//后续处理

 

在具体实现中,有如下几个步骤:
1. 让父窗口失效 EnableWindow(parentWindow, FALSE)
2. 建立模态对话框自己的消息循环(RunModalLoop)
3. 直至接收关闭消息,消息循环终止,并销毁窗口。

 
01INT_PTR CDialog::DoModal()
02{
03    //对话框资源加载
04    ......
05  
06    //在创建模态窗口之前先让父窗口失效,不响应键盘、鼠标产生的消息
07    HWND hWndParent = PreModal();
08    AfxUnhookWindowCreate();
09    BOOL bEnableParent = FALSE;
10  
11    if (hWndParent && hWndParent != ::GetDesktopWindow() && ::IsWindowEnabled(hWndParent))
12    {
13        ::EnableWindow(hWndParent, FALSE);
14        bEnableParent = TRUE;
15                .......
16    }
17  
18    //创建模态窗口,并进行消息循环,若窗口不关闭,则循环不退出
19    AfxHookWindowCreate(this);
20    VERIFY(RunModalLoop(dwFlags) == m_nModalResult);
21              
22    //窗口关闭,销毁窗口
23    DestroyWindow();
24    PostModal();
25  
26    //释放资源,并让父窗口有效
27        pMainWnd->EnableWindow(TRUE);
28  
29        //返回
30    return m_nModalResult;
31}

 

2. 模态窗口中的消息循环

01int CWnd::RunModalLoop(DWORD dwFlags)
02{
03    //要检查窗口状态是否是模态窗口
04    //若状态一直为模态,则一直进行消息循环
05    for (;;)
06    {
07        ASSERT(ContinueModal());
08  
09        // phase1: check to see if we can do idle work
10        while (bIdle &&
11            !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
12        {
13            ASSERT(ContinueModal());
14  
15            // show the dialog when the message queue goes idle
16            if (bShowIdle)
17            {
18                ShowWindow(SW_SHOWNORMAL);
19                UpdateWindow();
20                bShowIdle = FALSE;
21            }
22  
23            // call OnIdle while in bIdle state
24            if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)
25            {
26                // send WM_ENTERIDLE to the parent
27                ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);
28            }
29            if ((dwFlags & MLF_NOKICKIDLE) ||
30                !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))
31            {
32                // stop idle processing next time
33                bIdle = FALSE;
34            }
35        }
36  
37        //在有消息的情况下取消息处理
38        do
39        {
40            ASSERT(ContinueModal());
41  
42            // pump message, but quit on WM_QUIT
43            if (!AfxPumpMessage())
44            {
45                AfxPostQuitMessage(0);
46                return -1;
47            }
48  
49            // show the window when certain special messages rec'd
50            if (bShowIdle &&
51                (pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))
52            {
53                ShowWindow(SW_SHOWNORMAL);
54                UpdateWindow();
55                bShowIdle = FALSE;
56            }
57  
58            if (!ContinueModal())
59                goto ExitModal;
60  
61            // reset "no idle" state after pumping "normal" message
62            if (AfxIsIdleMessage(pMsg))
63            {
64                bIdle = TRUE;
65                lIdleCount = 0;
66            }
67  
68        } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
69    }
70  
71ExitModal:
72    m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);
73    return m_nModalResult;
74}

GetMessage与PeekMessage的区别:
GetMessage:用于从消息队列读取消息。若队列中没有消息,GetMessage将导致线程阻塞。
PeekMessage:检测队列中是否有消息,并立即返回,不会导致阻塞。

 

3. APP中的消息循环

 

01//thrdcore.cpp   
02// main running routine until thread exits   
03int CWinThread::Run()  
04{  
05   // for tracking the idle time state   
06   BOOL bIdle = TRUE;  
07   LONG lIdleCount = 0;  
08    
09   //消息读取乃至分发 当为WM_QUIT时,退出循环 
10   for (;;)  
11   {  
12      //检查是否为空闲时刻
13      while (bIdle &&  
14            !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))  
15      {  
16         // call OnIdle while in bIdle state   
17         if (!OnIdle(lIdleCount++))  
18            bIdle = FALSE; // assume "no idle" state   
19      }  
20    
21     //有消息,读消息并分发 
22     do  
23     {  
24        // pump message, but quit on WM_QUIT   
25        if (!PumpMessage())  
26           return ExitInstance();  
27    
28        // reset "no idle" state after pumping "normal" message   
29        if (IsIdleMessage(&m_msgCur))  
30        {  
31           bIdle = TRUE;  
32           lIdleCount = 0;  
33        }  
34    
35     }   
36     while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));  
37   }  
38}


4. 模态对话框中局部消息循环和APP全局消息循环的关系

4.1 APP消息循环和模态对话框中局部消息循环的关系

根据上图可以看出,在APP的消息循环再派发ONOK消息后,调用ModalDlg的响应函数,pWnd->OnOk();在该消息中,
会 进入模态对话框的消息循环,除非将模态对话框关闭,否则APP的DispatchMessage函数一直出不来。
一旦创建了模态对话框,进行局部消息循环,那么APP的消息循环就被阻断。整个程序的消息循环有模态对话框中得消息循环取代。所以给父窗口发送的非窗口消息,一样可以响应。

由于局部消息循环只在对话框中的一个响应函数中,而全局的消息循环也被阻断,局部循环一直运行,如果用户不进行处理并关闭模态对话框,该循环会一直不退出。其他对话框也得不到处理。

4.2 局部消息循环存在的必要性

我之前一直有这样一个疑问,觉得模态对话框中的局部消息循环没有必要,可以通过如下方式达到模态对话框的效果:
 
1pParentWnd->EnableWindow(FALSE);
2  
3CDialog *pDlg;
4pDlg = new CDialog();
5pDlg->Create();
6pDlg->Show();
7  
8pParentWnd->EnableWindow(TRUE);

并且做了个实验,貌似OK。但是这边有个疏漏的是,模态对话框的作用有两个:
1. 使父窗口失效,无法响应用户的输入
2. 在当前窗口为处理完毕时,禁止进入后续操作。
上述例子只达到了要求1,没有达到要求二

所以模态对话框中有如下代码:
 
1if (dlg.DoModal() == IDOK)
若对话框没有关闭,是无法进行后续操作的。
但是按照我先前的理解,如果代码是这样的:
 
01void CAppDoModelTestApp::OnTestModaltest()
02{
03    CWnd* pMainWnd = AfxGetMainWnd();
04    pMainWnd->EnableWindow(FALSE);
05  
06    m_pTestDlg1 = new CModalDlg();
07    m_pTestDlg1->Create(IDD_DIALOG1);
08    m_pTestDlg1->ShowWindow(SW_SHOW);
09  
10    m_pTestDlg2 = new CModalDlg();
11    m_pTestDlg2->Create(IDD_DIALOG1);
12    m_pTestDlg2->ShowWindow(SW_SHOW);
13}

在对话框TestDlg1后产生后,TestDlg2一样会出现。但是我们模态对话框希望的效果是:在TestDlg1未关闭前,TestDlg2不创建。所以此处体现出了局部消息循环的优势,就是在当前窗口为处理完毕时,一直循环, 拒绝进入后续代码中。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值