• MFC 模态对话框的实现原理


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

    1. 模态对话框

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

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

    主要区别:

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

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

    1. TestDlg dlg;
    2. if (dlg.DoModal() == IDOK)
    3. {
    4. //处理完毕后的操作
    5. }
    6. .......//后续处理

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

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

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

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

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

    3. APP 中的消息循环

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

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

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

    根据上图可以看出,在 APP 的消息循环再派发 ONOK 消息后,调用 ModalDlg 的响应函数,pWnd->OnOk (); 在该消息中,
    会 进入模态对话框的消息循环,除非将模态对话框关闭,否则 APP 的 DispatchMessage 函数一直出不来。

    一旦创建了模态对话框,进行局部消息循环,那么 APP 的消息循环就被阻断。整个程序的消息循环有模态对话框中得消息循环取代。所以给父窗口发送的非窗口消息,一样可以响应。

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

  • 相关阅读:
    算法笔记-第七章-链表(未完成)
    Python用若干列的数据多条件筛选、去除Excel数据并批量绘制直方图
    unity游戏画质设置功能实现
    vue2.x版本中computed和watch的使用入门详解-computed篇
    Shell综合应用案例,归档文件、发送消息
    第 18章 安全架构设计理论与实践
    腾讯云国际版云服务器欠费说明
    PPT大珩助手使用方法的详细介绍
    webp批量转换为png、jpg工具
    【教3妹学mysql】联合索引问题优化
  • 原文地址:https://blog.csdn.net/xy18990/article/details/139317608