zoukankan      html  css  js  c++  java
  • MFC 模态对话框的实现原理

    1. 模态对话框

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

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

    模态框和非模态框的主要区别:

    1.模态对话框会阻塞线程其他窗口的输入消息,其他窗口无法响应包括用户输入;
    2.模态对话框会中断执行流程,关闭模态窗口,后会继续执行;

    在用户层的主要逻辑如下: 

    TestDlg dlg;
    
    if (dlg.DoModal() == IDOK)
    {
               //处理完毕后的操作
    }
    .......//后续处理

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

    INT_PTR CDialog::DoModal()
    {
        //对话框资源加载
        ......
    
        //在创建模态窗口之前先让父窗口失效,不响应键盘、鼠标产生的消息
        HWND hWndParent = PreModal();
        AfxUnhookWindowCreate();
        BOOL bEnableParent = FALSE;
    
        if (hWndParent && hWndParent != ::GetDesktopWindow() && ::IsWindowEnabled(hWndParent))
        {
            ::EnableWindow(hWndParent, FALSE);
            bEnableParent = TRUE;
                    .......
        }
    
        //创建模态窗口,并进行消息循环,若窗口不关闭,则循环不退出
        AfxHookWindowCreate(this);
        VERIFY(RunModalLoop(dwFlags) == m_nModalResult);
                
        //窗口关闭,销毁窗口
        DestroyWindow();
        PostModal();
    
        //释放资源,并让父窗口有效
            pMainWnd->EnableWindow(TRUE);
    
            //返回
        return m_nModalResult;
    }

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

    int CWnd::RunModalLoop(DWORD dwFlags)
    {
        //要检查窗口状态是否是模态窗口
        //若状态一直为模态,则一直进行消息循环
        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;
                }
            }
    
            //在有消息的情况下取消息处理
            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;
                }
    
                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;
    }

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

    3. APP中的消息循环

    //thrdcore.cpp   
    // main running routine until thread exits   
    int CWinThread::Run()  
    {  
       // for tracking the idle time state   
       BOOL bIdle = TRUE;  
       LONG lIdleCount = 0;  
      
       //消息读取乃至分发 当为WM_QUIT时,退出循环 
       for (;;)  
       {  
          //检查是否为空闲时刻
          while (bIdle &&  
                !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))  
          {  
             // call OnIdle while in bIdle state   
             if (!OnIdle(lIdleCount++))  
                bIdle = FALSE; // assume "no idle" state   
          }  
      
         //有消息,读消息并分发 
         do  
         {  
            // pump message, but quit on WM_QUIT   
            if (!PumpMessage())  
               return ExitInstance();  
      
            // reset "no idle" state after pumping "normal" message   
            if (IsIdleMessage(&m_msgCur))  
            {  
               bIdle = TRUE;  
               lIdleCount = 0;  
            }  
      
         }   
         while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));  
       }  
    }

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

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

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

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

    我之前一直有这样一个疑问,觉得模态对话框中的局部消息循环没有必要,可以通过如下方式达到模态对话框的效果:
    pParentWnd->EnableWindow(FALSE);
    
    CDialog *pDlg;
    pDlg = new CDialog();
    pDlg->Create();
    pDlg->Show();
    
    pParentWnd->EnableWindow(TRUE);
    并且做了个实验,貌似OK。但是这边有个疏漏的是,模态对话框的作用有两个:
    1. 使父窗口失效,无法响应用户的输入
    2. 在当前窗口为处理完毕时,禁止进入后续操作。
    上述例子只达到了要求1,没有达到要求二
     
    所以模态对话框中有如下代码:
    if (dlg.DoModal() == IDOK)
    若对话框没有关闭,是无法进行后续操作的。
    但是按照我先前的理解,如果代码是这样的:
    void CAppDoModelTestApp::OnTestModaltest()
    {
        CWnd* pMainWnd = AfxGetMainWnd();
        pMainWnd->EnableWindow(FALSE);
    
        m_pTestDlg1 = new CModalDlg();
        m_pTestDlg1->Create(IDD_DIALOG1);
        m_pTestDlg1->ShowWindow(SW_SHOW);
    
        m_pTestDlg2 = new CModalDlg();
        m_pTestDlg2->Create(IDD_DIALOG1);
        m_pTestDlg2->ShowWindow(SW_SHOW);
    }
    在对话框TestDlg1后产生后,TestDlg2一样会出现。但是我们模态对话框希望的效果是:在TestDlg1未关闭前,TestDlg2不创建。所以此处体现出了局部消息循环的优势,就是在当前窗口为处理完毕时,一直循环, 拒绝进入后续代码中。
     
     
  • 相关阅读:
    原生js螺旋运动
    拉美电子游戏市场创收45亿美元
    ZOJ 3229 Shoot the Bullet
    Java的压缩、解压及压缩加密、解密解压 样例
    java环境变量配置
    git在myelispse中的安装
    java注解
    Python测试框架doctest
    python中的协程
    Flask log配置,实现按照日期自动生成日志文件
  • 原文地址:https://www.cnblogs.com/2018shawn/p/15013669.html
Copyright © 2011-2022 走看看