1. 模态对话框
在涉及GUI程序开发的过程中,常常有模态对话框以及非模态对话框的概念
模态对话框:在子界面活动期间,父窗口是无法进行消息响应。独占用户输入
非模态对话框:各窗口之间不影响
主要区别:非模态对话框与APP共用消息循环,不会独占用户。
模态对话框独占用户输入,其他界面无法响应
在用户层的主要逻辑如下:
3 | if (dlg.DoModal()
== IDOK) |
在具体实现中,有如下几个步骤:
1. 让父窗口失效 EnableWindow(parentWindow, FALSE)
2. 建立模态对话框自己的消息循环(RunModalLoop)
3. 直至接收关闭消息,消息循环终止,并销毁窗口。
01 | INT_PTR CDialog::DoModal() |
06 | //在创建模态窗口之前先让父窗口失效,不响应键盘、鼠标产生的消息 |
07 | HWND hWndParent
= PreModal(); |
08 | AfxUnhookWindowCreate(); |
09 | BOOL bEnableParent
= FALSE; |
11 | if (hWndParent
&& hWndParent != ::GetDesktopWindow() && ::IsWindowEnabled(hWndParent)) |
13 | ::EnableWindow(hWndParent,
FALSE); |
18 | //创建模态窗口,并进行消息循环,若窗口不关闭,则循环不退出 |
19 | AfxHookWindowCreate( this ); |
20 | VERIFY(RunModalLoop(dwFlags)
== m_nModalResult); |
27 | pMainWnd->EnableWindow(TRUE); |
2. 模态窗口中的消息循环
01 | int CWnd::RunModalLoop( DWORD dwFlags) |
07 | ASSERT(ContinueModal()); |
09 | //
phase1: check to see if we can do idle work |
11 | !::PeekMessage(pMsg,
NULL, NULL, NULL, PM_NOREMOVE)) |
13 | ASSERT(ContinueModal()); |
15 | //
show the dialog when the message queue goes idle |
18 | ShowWindow(SW_SHOWNORMAL); |
23 | //
call OnIdle while in bIdle state |
24 | if (!(dwFlags
& MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0) |
26 | //
send WM_ENTERIDLE to the parent |
27 | ::SendMessage(hWndParent,
WM_ENTERIDLE, MSGF_DIALOGBOX, ( LPARAM )m_hWnd); |
29 | if ((dwFlags
& MLF_NOKICKIDLE) || |
30 | !SendMessage(WM_KICKIDLE,
MSGF_DIALOGBOX, lIdleCount++)) |
32 | //
stop idle processing next time |
40 | ASSERT(ContinueModal()); |
42 | //
pump message, but quit on WM_QUIT |
43 | if (!AfxPumpMessage()) |
45 | AfxPostQuitMessage(0); |
49 | //
show the window when certain special messages rec'd |
51 | (pMsg->message
== 0x118 || pMsg->message == WM_SYSKEYDOWN)) |
53 | ShowWindow(SW_SHOWNORMAL); |
61 | //
reset "no idle" state after pumping "normal" message |
62 | if (AfxIsIdleMessage(pMsg)) |
68 | } while (::PeekMessage(pMsg,
NULL, NULL, NULL, PM_NOREMOVE)); |
72 | m_nFlags
&= ~(WF_MODALLOOP|WF_CONTINUEMODAL); |
GetMessage与PeekMessage的区别:
GetMessage:用于从消息队列读取消息。若队列中没有消息,GetMessage将导致线程阻塞。
PeekMessage:检测队列中是否有消息,并立即返回,不会导致阻塞。
3. APP中的消息循环
02 | //
main running routine until thread exits |
05 | //
for tracking the idle time state |
09 | //消息读取乃至分发
当为WM_QUIT时,退出循环 |
14 | !::PeekMessage(&m_msgCur,
NULL, NULL, NULL, PM_NOREMOVE)) |
16 | //
call OnIdle while in bIdle state |
17 | if (!OnIdle(lIdleCount++)) |
18 | bIdle
= FALSE; // assume "no idle" state |
24 | //
pump message, but quit on WM_QUIT |
28 | //
reset "no idle" state after pumping "normal" message |
29 | if (IsIdleMessage(&m_msgCur)) |
36 | while (::PeekMessage(&m_msgCur,
NULL, NULL, NULL, PM_NOREMOVE)); |
4. 模态对话框中局部消息循环和APP全局消息循环的关系
4.1 APP消息循环和模态对话框中局部消息循环的关系
根据上图可以看出,在APP的消息循环再派发ONOK消息后,调用ModalDlg的响应函数,pWnd->OnOk();在该消息中,
会 进入模态对话框的消息循环,除非将模态对话框关闭,否则APP的DispatchMessage函数一直出不来。
一旦创建了模态对话框,进行局部消息循环,那么APP的消息循环就被阻断。整个程序的消息循环有模态对话框中得消息循环取代。所以给父窗口发送的非窗口消息,一样可以响应。
由于局部消息循环只在对话框中的一个响应函数中,而全局的消息循环也被阻断,局部循环一直运行,如果用户不进行处理并关闭模态对话框,该循环会一直不退出。其他对话框也得不到处理。
4.2 局部消息循环存在的必要性
我之前一直有这样一个疑问,觉得模态对话框中的局部消息循环没有必要,可以通过如下方式达到模态对话框的效果:
1 | pParentWnd->EnableWindow(FALSE); |
8 | pParentWnd->EnableWindow(TRUE); |
并且做了个实验,貌似OK。但是这边有个疏漏的是,模态对话框的作用有两个:
1. 使父窗口失效,无法响应用户的输入
2. 在当前窗口为处理完毕时,禁止进入后续操作。
上述例子只达到了要求1,没有达到要求二
所以模态对话框中有如下代码:
1 | if (dlg.DoModal()
== IDOK) |
若对话框没有关闭,是无法进行后续操作的。
但是按照我先前的理解,如果代码是这样的:
01 | void CAppDoModelTestApp::OnTestModaltest() |
03 | CWnd*
pMainWnd = AfxGetMainWnd(); |
04 | pMainWnd->EnableWindow(FALSE); |
06 | m_pTestDlg1
= new CModalDlg(); |
07 | m_pTestDlg1->Create(IDD_DIALOG1); |
08 | m_pTestDlg1->ShowWindow(SW_SHOW); |
10 | m_pTestDlg2
= new CModalDlg(); |
11 | m_pTestDlg2->Create(IDD_DIALOG1); |
12 | m_pTestDlg2->ShowWindow(SW_SHOW); |
在对话框TestDlg1后产生后,TestDlg2一样会出现。但是我们模态对话框希望的效果是:在TestDlg1未关闭前,TestDlg2不创建。所以此处体现出了局部消息循环的优势,就是在当前窗口为处理完毕时,一直循环, 拒绝进入后续代码中。
转自:http://my.oschina.net/myspaceNUAA/blog/81187