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不创建。所以此处体现出了局部消息循环的优势,就是在当前窗口为处理完毕时,一直循环, 拒绝进入后续代码中。 参考链接:https://my.oschina.net/myspaceNUAA/blog/81187