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

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

相关推荐
凯子坚持 c10 分钟前
CANN 性能剖析实战:从原始事件到交互式火焰图
windows·microsoft
开开心心就好1 小时前
发票合并打印工具,多页布局设置实时预览
linux·运维·服务器·windows·pdf·harmonyos·1024程序员节
獨枭1 小时前
PyCharm 跑通 SAM 全流程实战
windows
仙剑魔尊重楼2 小时前
音乐制作电子软件FL Studio2025.2.4.5242中文版新功能介绍
windows·音频·录屏·音乐·fl studio
PHP小志2 小时前
Windows 服务器怎么修改密码和用户名?账户被系统锁定如何解锁
windows
专注VB编程开发20年3 小时前
vb.net datatable新增数据时改用数组缓存
java·linux·windows
仙剑魔尊重楼4 小时前
专业音乐制作软件fl Studio 2025.2.4.5242中文版新功能
windows·音乐·fl studio
rjc_lihui5 小时前
Windows 运程共享linux系统的方法
windows
失忆爆表症5 小时前
01_项目搭建指南:从零开始的 Windows 开发环境配置
windows·postgresql·fastapi·milvus
MSTcheng.5 小时前
CANN ops-math算子的跨平台适配与硬件抽象层设计
c++·mfc