// hide the window before enabling the parent, etc.
if (m_hWnd != NULL)
SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW| SWP_NOSIZE|SWP_NOMOVE| SWP_NOACTIVATE|SWP_NOZORDER); } }
CATCH_ALL(e) {
DELETE_EXCEPTION(e); m_nModalResult = -1; }
END_CATCH_ALL
//Enable并且激活父窗口 if (bEnableParent)
::EnableWindow(hWndParent, TRUE);
if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)
::SetActiveWindow(hWndParent);
//::EndDialog仅仅关闭了窗口,现在销毁窗口 DestroyWindow(); PostModal();
// 必要的话,解锁/释放资源
if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL) UnlockResource(hDialogTemplate); if (m_lpszTemplateName != NULL) FreeResource(hDialogTemplate); return m_nModalResult; }
从DoModal的实现可以看出:
它首先Disable对话框窗口的父窗口;然后使
用::CreateIndrectDialog创建对话框窗口,使用子类化的方法用AfxWndProc(或者AfxBaseProc)替换了原来的窗口过程,并把原来的窗口过程保存在CWnd的成员变量m_pfnSuper中。原来的窗口过程就是::DialogBox等创建对话框窗口时指定的,是Windows内部提供的对话框“窗口类”的窗口过程。取代(Subclass)原来“窗口类”的窗口过程的方法如同 4.4.1节描述的CWnd::Create。
在::CreateIndirectDialog创建对话框窗口后,会发送WM_INITDIALOG消息给对话框的对话框过程(必要的话,还有WM_SETFONT消息)。但是MFC取代了原来的对话框窗口过程,这两个消息如何送给对话框过程呢?处理方法如下节所描述。 3. 使用原对话框窗口过程作消息的缺省处理
对话框的消息处理过程和其他窗口并没有什么不同。这里主要分析的是如何把一些消息传递给对话框原窗口过程处理。下面,通过解释MFC对WM_INITDIALOG消息的处理来解释MFC窗口过程和原对话框窗口过程的关系及其协调作用。
MFC提供了WM_INITDIALOG消息的处理函数
CDialog::HandleInitDialog,WM_INITDIALOG消息按照标准Windows的处理送给HandleInitDialog处理。
HandleInitDialog调用缺省处理过程Default,导致CWnd的Default函数被调用。CWnd::Default的实现如下:
LRESULT CWnd::Default() {
// call DefWindowProc with the last message _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
return
DefWindowProc(pThreadState->m_lastSentMsg.message,
pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam); }
顺便指出,从Default的实现可以看出线程状态的一个用途:它把本线程最新收到和处理的消息记录在成员变量m_lastSentMsg中。
在Default的实现中,CWnd的DefWindowsProc被调用,其实现如下:
LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
//若“窗口超类(SuperClass)”的窗口过程m_pfnSuper非空,则调用它 if (m_pfnSuper != NULL)
return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);
//在MFC中,GetSuperWndProcAddr的作用就是返回m_pfnSuper,为什么还
//要再次调用呢?因为虽然该函数现在是Obsolete,但原来曾经是有用的。如
//果返回非空,就调用该窗口过程进行处理,否则,由Windows进行缺省处理。 WNDPROC pfnWndProc;
if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)
return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam); else
return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam); }
综合上述分析,HandleInitDialog最终调用了窗口过程m_pfnSuper,即Windows提供给“对话框窗口类”的窗口过程,于是该窗口过程调用了对话框过程AfxDlgProc,导致虚拟函数OnInitDialog被调用。 顺便提一下,CWnd::AfxCallWndProc在处理
WM_INITDIALOG消息之前和之后都会有一些特别的处理,如尝试把对话框放到屏幕中间。具体实现这里略。
OnInitDialog的MFC缺省实现主要完成三件事情: 调用ExecInitDialog初始化对话框中的控制;调用UpdateData初始化对话框控制中的数据;确定是否显示帮助按钮。所以,程序员覆盖该函数时,一定要调用基类的实现。
MFC采用子类化的方法取代了对话框的窗口过程,实现了12.1节描述的模式对话框窗口的一些特性,原来SDK下对话框过程要处理的东西大部分转移给MFC窗口过程处理,如处理控制窗口的控制通知消息等。如果不能处理或者必须借助于原来的窗口过程的,则通过缺省处理函数Default传递给原来的窗口过程处理,如同这里对WM_INITDIALOG的处理一样。
4. Dialog命令消息和控制通知消息的处理
通过覆盖CWnd的命令消息发送函数OnCmdMsg,CDialog实现了自己的命令消息发送路径。在
4.4.3.3节,曾经分析了CDialog::OnCmdMsg函数,这里给出其具体实现:
BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo) {
//首先,让对话框窗口自己或者基类处理 if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE;
//如果还未处理,且是控制通知消息或者状态更新消息或者系统命令 //则停止进一步的发送
if ((nCode != CN_COMMAND && nCode != CN_UPDATE_COMMAND_UI) ||
!IS_COMMAND_ID(nID) || nID >= 0xf000) {
return FALSE; // not routed any further }
//尝试给父窗口处理
CWnd* pOwner = GetParent(); if (pOwner != NULL) {
#ifdef _DEBUG