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