MFC 模态对话框的实现原理

参考自MFC 模态对话框的实现原理 - 西昆仑 - OSCHINA - 中文开源技术交流社区

1. 模态对话框

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

模态对话框:在模态对话框活动期间,父窗口是无法进行消息响应,独占用户输入;

非模态对话框:各窗口之间不影响。

主要区别:

非模态对话框与 APP 共用消息循环,不会独占用户;

模态对话框独占用户输入,其他界面无法响应。

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

复制代码
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 的消息循环就被阻断。整个程序的消息循环有模态对话框中得消息循环取代。所以给父窗口发送的非窗口消息,一样可以响应。

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

相关推荐
吴声子夜歌37 分钟前
Java数据结构与算法——基本数学问题
java·开发语言·windows
这儿有一堆花4 小时前
用原生脚本编写无害恶作剧
windows
因我你好久不见4 小时前
Windows部署springboot jar支持开机自启动
windows·spring boot·jar
夜流冰4 小时前
Excel - MS Support for Excel: 2 Collaborate
数据库·windows·excel
林瞅瞅5 小时前
PowerShell 启动卡顿?内存飙升?原来是 800MB 的历史记录在作祟!
windows
Shepherd06195 小时前
【Windows Server 实战】WAC 反向代理配置
windows
云小逸6 小时前
【windows系统编程】第一章 Windows 系统核心架构与基础概念
windows·架构
怣疯knight7 小时前
Docker Desktop 4.55.0版本安装成功教程
windows·docker
liulilittle9 小时前
VEthernet 框架实现 tun2socks 的技术原理
网络·windows·c#·信息与通信·通信
独钓寒江雨9 小时前
win11在安全模式下删除360tray.exe
windows·电脑