June 1995,Microsoft System Journal
Paul DiLascia 是一个自由软件顾问,专长是训练和软件开发(C++ and Windows).他是Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992)的作者.
问:我的问题是OnIdle在通常的文档/视图程序中可以工作,但是看起来在基于对话框的程序中不行。我的CApp::InitInstance调用dlg.DoModal,调用一个函数:不调用OnIdle的CWnd::RunModalLoop。我想我应该在WM_ENTERIDLE中做一些后台处理,但是这个消息是发送到对话框的父窗口的。在我的这种情况下,父窗口不存在。请帮忙!
Jim Kallimani
如你所见,“模态”对话框在MFC4.0中实际上是非模态的。当你调用CDialog::DoModal的时候,MFC并不调用::DialogBox,像它以前所做的,而是调用CreateDialogIndirect (在三思之后)然后通过禁用父窗口并且进入自己的消息循环模拟模态行为。这是::DialogBox所做的本质工作。这样做的好处是MFC拥有对话框的消息循环,以前它被Windows API函数::DialogBox隐藏起来了。这样MFC通过通常MFC的消息泵CWinThread::PumpMessage取模态对话框消息,像其他窗口一样。特别的,你可以为模态对话框重载CWnd::PreTranslateMessage—例如实现加速键。MFC的早期版本允许你为模态对话框实现你自己的PreTranslateMessage,但是没有被系统调用,因为CDialog::DoModal直接执行::DialogBox,直到你的对话框消息处理函数调用EndDialog才返回。同样,使用::DialogBox, 使用通常的MFC方法进行消息处理是不可能的,因为程序控制消失在::DialogBox中,直到对话框结束才返回。
作为替代方案, Windows有它自己的机制, WM_ENTERIDLE, 在模态对话框中进行消息处理。当处理完一个或多个消息之后,Windows 发送 WM_ ENTERIDLE 到一个模态对话框或菜单的所有者,如果消息队列中没有等待的消息的话。只有模态对话框发送WM_ENTERIDLE,而非模态对话框不发送。因为MFC现在使用非模态对话框,甚至是在使用模态对话框的时候实际上也是使用非模态对话框,MFC不得不自己发送WM_ENTERIDLE以模仿模态对话框—-但是仅当对话框有父窗口的时候才这么干。Jim碰到麻烦,因为没有父窗口来接收WM_ENTERIDLE。你的头快昏了吗?
如果MFC通过标准消息泵取模态对话框消息,为什么不调用CWinApp::OnIdle作为自己消息处理的一部分?为题是CWnd::RunModalLoop 调用了CWinThread::PumpMessage但是OnIdle在CWinThread::Run中出现。当你的应用程序调用了InitInstance函数之后,MFC调用CWinThread::Run运行你的应用程序。CWinThread::Run的浓缩形式看起来像这样:
// (from THRDCORE.CPP)
int CWinThread::Run()
{
// 为了空闲状态处理
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
for (;;) {
while (bIdle && !::PeekMessage(...)) {
//当在bIdle状态时调用OnIdle
if (!OnIdle(lIdleCount++))
// 假设"非空闲" 状态
bIdle = FALSE;
}
// 获取/预处理/分派消息
// (调用 CWinThread::PumpMessage)
}
}
我砍掉了很多,以强调空闲处理如何工作。如果没有消息在等待,MFC重复调用CWinThread::OnIdle,传递给它每次增加的一个计数器参数。你可以使用这个参数区分不同种类的空闲处理的优先次序。你可能在空闲计数为1时作格式化,空闲计数为2时更新一个指示当天时间的时钟。当你的OnIdle返回FALSE时,MFC停止调用它并且等待,直到你的线程得到另一个消息,因此空闲循环从头开始。
模态对话框从不执行这个代码,因为CWnd::RunModalLoop直接在自己的消息循环中调用CWinThread::PumpMessage。它没有调用CWinThread::Run,因此从不调用CWinThread::OnIdle。 Redmond 的人员告诉我这是由设计上决定的。显然,在模态对话框中调用OnIdle是危险的,因为许多消息处理函数建立临时CWnd对象,它们被期望在对话框生存期中存在。默认空闲处理的一部分就是释放临时句柄映射。(译者注:临时CWnd对象依赖于临时句柄映射而存在。.)
(我不得不告诉你,依我所见,整个MFC用来连接HWND和CWnd的临时/永久句柄映射机制是整个架构中的灾难之一,甚至比它们的消息映射还要坏。临时映射机制的问题不断出现在程序中—特别是在多线程应用中,使得他们很难用MFC编写。)
这样看来,你如何在基于对话框的应用程序中进行消息处理,当对话框没有父窗口的时候?幸运的是,它易如反掌。MFC开发者提供一个钩子: WM_KICKIDLE。 RunModalLoop 不断发送这个MFC私有消息,当消息队列中没有消息的时候—就像CWinThread::Run调用OnIdle一样。 RunModalLoop甚至还为你提供一个计数器并且依次递增。实际上,WM_KICKIDLE是对话框的OnIdle替代品。 (历史信息:早期版本的MFC为属性表作这个模态/非模态切换和提供WM_KICKIDLE。显然它工作的如此之好,以至于他们决定使所有的模态对话框非模态化。)
要警告你的是:你可能在OnKickIdle函数中,想调用你的主应用程序的OnIdle函数
LRESULT CMyDlg::OnKickIdle(WPARAM, LPARAM lCount)
{
return AfxGetApp()->OnIdle(lCount);
}
MFC人员告诉我这是危险的;因为临时映射问题。在OnKickIdle中执行你的空闲处理会更安全一些。如果有必要,你可以组合共有的空闲处理成为一个辅助函数,在CApp::OnIdle 和 CMyDlg::OnKickIdle中调用。
当我在处理空闲处理的问题的时候,发现不是所有程序员都知道CDocTemplate和CDocument的OnIdle函数! 如果你要在文档或文档模板中执行空闲处理,只需重载这些函数。
Internet:
Paul DiLascia
72400.2702@compuserve.com
From the June 1996 issue of Microsoft Systems Journal.
July 1997,Microsoft System Journal
......
下面我将指出如何使ON_COMMAND_UPDATE_UI处理函数在对话框中工作。在通常的MFC文档/视图应用中,MFC使用内部消息WM_IDLEUPDATECMDUI更新菜单项、工具栏按钮、状态栏格等用户界面对象。作为空闲处理的一部分,IDLEUPDATECMDUI广播到所有你的应用程序的窗口。工具栏、状态栏和对话栏的命令处理依次调用UpdateDialogControls广播另一个命令,CN_UPDATE_COMMAND_UI,到窗口上的所有控件。从你的程序员的角度来看,这些消息是不可见的。你只需实现ON_UPDATE_COMMAND_UI处理你的菜单项和按钮,然后,看? 它们被变魔法似的更新了。(需要更多信息的话,参见我的在1995年6月MSJ.上的文章 "Meandering Through the Maze of MFC Message and Command Routing" )
不幸的是,这个奇妙的UI更新机制不能用于对话框—至少不是自动的。 你必须自己修补一下。幸好它很简单。你只需处理WM_KICKIDLE,一个MFC私有消息;当对话框空闲时发送出来(类似应用程序的OnIdle处理)给.你自己调用UpdateDialogControls。.
LRESULT CTabDialog::OnKickIdle(WPARAM wp,
LPARAM lCount)
{
UpdateDialogControls(this, TRUE);
return 0;
}
CWnd::UpdateDialogControls发送魔术般的CN_ UPDATE_COMMAND_UI 消息给所有对话框控件,结果是现在ON_COMMAND_ UPDATE_UI处理突然在对话框中可以工作了。