参考自MFC 模态对话框的实现原理 - 西昆仑 - OSCHINA - 中文开源技术交流社区
在涉及 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;
- }
- 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: 检测队列中是否有消息,并立即返回,不会导致阻塞。
- //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.1 APP 消息循环和模态对话框中局部消息循环的关系

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