您的位置:首页 > 其它

MFC 模态对话框的实现原理

2013-08-14 18:51 393 查看

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的消息循环就被阻断。整个程序的消息循环有模态对话框中得消息循环取代。所以给父窗口发送的非窗口消息,一样可以响应。

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

4.2 局部消息循环存在的必要性

我之前一直有这样一个疑问,觉得模态对话框中的局部消息循环没有必要,可以通过如下方式达到模态对话框的效果:

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: