一位读者问了这样一个问题:


如果我创建了一个可见的模态对话框,却对用户来说不可用。举个例子,假设我在程序中的其他位置收到一个事件,并且我从事件中调用模态 CDialog 上的 DestroyWindow。我注意到 OnDestroy 是在 CDialog 上调用的,但在将 WM_QUIT 消息发送到模态对话框的消息循环之前,DoModal 永远不会退出。这会导致什么问题?不幸的是,我目前还真的没有找到方法来避免这种情况。

实际上,我不确定这位读者具体想问的问题是什么。如前所述,问题是”这会导致什么问题”

但他在自己的问题中回答了这个问题:导致的问题是: “DoModal 永远不会退出,直到 WM_QUIT 消息发送到模态对话框的消息循环中为止。”

我猜想,他真正的问题是:”为什么销毁窗口不起作用?”。
然后是后续问题: “关闭模式对话框的正确方法是什么?”

这个问题的第一个问题是,它假设我知道 CDialog 是什么。从它的名字来看,我将假设这是一个用于管理对话框的 MFC 类。但是,您甚至不必知道,就可以回答仅根据 Win32 原则的第一个问题:

DestroyWindow 不是退出模态对话框的方式,而应该使用 EndDialog 退出模式对话框。
DestroyWindow 技术适用于非模态对话框。

但是,让我们从另一个角度来看这个问题,这就是我今天的观点:你有 MF C源代码。不要害怕阅读它。特别是因为我个人不使用 MFC,我甚至不知道使用 MFC 进行应用程序设计的基本原理,我一直都是在 Win32 上工作。因此,我不知道答案,但阅读 MFC 源代码的 15 分钟很快就揭示了销毁窗口不起作用的原因。

下面是我寻找答案的过程,我想,你也可以做到,没有什么是你自己做不到的。

CDialog 类的 DoModal 方法调用 CWnd 的 RunModalLoop 来运行对话循环。如果查看 CWnd 的RunModalLoop 方法,则可以看到它退出模态循环的条件。
这是删除了不相关细节的代码。(它们无关紧要,因为它们与模态循环的退出方式无关。

打开网易新闻 查看更多图片

从上面的代码来看,只有两种方法可以退出这个循环。首先是收到 WM_QUIT 消息。第二个是 CWnd 的 ContinueModal 是否决定模态循环已完成。这位读者已经提到了模态循环的退出消息方面,因此只剩下 CWnd 的 ContinueModal 方法的研究了。

CWnd 的 ContinueModal 方法实现非常简单。

BOOL CWnd::ContinueModal()
{
return m_nFlags & WF_CONTINUEMODAL;
}

因此,循环退出的唯一另一种方式是:有人清除 WF_CONTINUEMODAL 标志。
稍微动下脑筋,我们就会发现,只有三个地方清除了这个标志。

一个是 CPropertyPage,它是 CDialog 的派生类,因此在这里无关紧要。(在以后的搜索中,我将忽略 CPropertyPage。

第二个位于标签 ExitModal 后面的上方行中。

第三种是这种方法:

void CWnd::EndModalLoop(int nResult)
{
// this result will be returned from CWnd::RunModalLoop
m_nModalResult = nResult;
// make sure a message goes through to exit the modal loop
if (m_nFlags & WF_CONTINUEMODAL)
{
m_nFlags &= ~WF_CONTINUEMODAL;
PostMessage(WM_NULL);
}
}

此方法仅在一个位置调用:

void CDialog::EndDialog(int nResult)
{
if (m_nFlags & (WF_MODALLOOP|WF_CONTINUEMODAL))
EndModalLoop(nResult);
::EndDialog(m_hWnd, nResult);
}

在最后一步之后,CDialog 的 EndDialog 方法从 CDialog 中的四个位置调用。

如果在对话框初始化期间发生某些灾难性错误,则从 CDialog 的 HandleInitDialog 和 CDialog 的InitDialog 调用它。它从 CDialog 的 OnOK 和 CDialog 的 OnCancel 调用,以响应用户单击“确定”或“取消”按钮。

请注意,当有人从外部强行销毁对话框时,不会调用 CDialog 的 EndDialog 方法。

这就是为什么销毁对话框窗口不会破坏模态循环的原因:如果要跳出模式循环,唯一的选择是直接或间接(例如,通过 CDialog 的 EndDialog 方法)发布退出消息或调用 CWnd 的 EndModalLoop 方法。

请注意,MFC 模式循环通过在退出模式循环中断时重新发布退出消息来遵守退出消息的约定。(尽管它确实应该从退出消息中发布 wParam,而不仅仅是发布零。

因此,解决方法是不要使用 DestroyWindow 销毁对话框(您应该知道不要先验地这样做,因为这不是退出模式对话框的方式),而是通过调用 CDialog 的 EndDialog 传递结果代码,让 CDialog 的 DoModal 的调用方知道对话框在异常情况下退出。

我花了十五分钟来研究,花了一个多小时来写。所有这些都是为了回答一个问题,你应该能够用一点时间来研究源码以解决问题。
你是个聪明人,对自己有信心。你可以的,我知道你可以。

总结

程序的世界是实在而纯粹的。任何问题,只要有源代码,就可以花时间研究它,最终都可以解决问题。
这可是比人好打交道多了。

最后

Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《Don’t be helpless: I don’t know anything about MFC modal loops, but unlike some people, I’m not afraid to find out》

打开网易新闻 查看更多图片