|
![](static/image/common/ico_lz.png)
楼主 |
发表于 2006-12-8 10:29:57
|
显示全部楼层
<LI>
<OL>
<LI><B>模式对话框窗口过程</B>
<LI>
<LI>本小节讨论对话框的窗口过程。
<LI>AfxWndProc是所有的MFC窗口类使用的窗口过程,它取代了模式对话框原来的窗口过程(Windows提供),那么,MFC如何完成Win32下对话框窗口的功能呢?
<LI>考查模式对话框的创建过程。CDialog: oModal用来创建模式对话框窗口并执行有关任务,和DoModal相关的是MFC内部使用的成员函数CDialog: reModal和CDialog: ostModal。下面分别讨论它们的实现。
<LI>
<DIV align=justify>HWND CDialog: reModal()</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>// cannot call DoModal on a dialog already constructed as modeless</DIV>
<LI>
<DIV align=justify>ASSERT(m_hWnd == NULL);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// allow OLE servers to disable themselves</DIV>
<LI>
<DIV align=justify>AfxGetApp()->EnableModeless(FALSE);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// 得到父窗口</DIV>
<LI>
<DIV align=justify>CWnd* pWnd = CWnd::GetSafeOwner(m_pParentWnd, &m_hWndTop);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// 如同CWnd处理其他窗口的创建,设置一个窗口创建HOOK</DIV>
<LI>
<DIV align=justify>AfxHookWindowCreate(this);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//返回父窗口的句柄</DIV>
<LI>
<DIV align=justify>return pWnd->GetSafeHwnd();</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>void CDialog: ostModal()</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>//取消窗口创建前链接的HOOK</DIV>
<LI>
<DIV align=justify>AfxUnhookWindowCreate(); // just in case</DIV>
<LI>
<DIV align=justify>//MFC对话框对象和对应的Windows对话框窗口分离</DIV>
<LI>
<DIV align=justify>Detach(); // just in case</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// m_hWndTop是当前对话框的父窗口或所属窗口,则恢复它</DIV>
<LI>
<DIV align=justify>if (::IsWindow(m_hWndTop))</DIV>
<LI>
<DIV align=justify>::EnableWindow(m_hWndTop, TRUE);</DIV>
<LI>
<DIV align=justify>m_hWndTop = NULL;</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>AfxGetApp()->EnableModeless(TRUE);</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>int CDialog: oModal()</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>// can be constructed with a resource template or InitModalIndirect</DIV>
<LI>
<DIV align=justify>ASSERT(m_lpszTemplateName != NULL ||</DIV>
<LI>
<DIV align=justify>m_hDialogTemplate != NULL || m_lpDialogTemplate != NULL);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//加载对话框资源</DIV>
<LI>
<DIV align=justify>LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;</DIV>
<LI>
<DIV align=justify>HGLOBAL hDialogTemplate = m_hDialogTemplate;</DIV>
<LI>
<DIV align=justify>HINSTANCE hInst = AfxGetResourceHandle();</DIV>
<LI>
<DIV align=justify>//查找资源(见9.5.2节),找到了就加载它</DIV>
<LI>
<DIV align=justify>if (m_lpszTemplateName != NULL)</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);</DIV>
<LI>
<DIV align=justify>HRSRC hResource = </DIV>
<LI>
<DIV align=justify>::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);</DIV>
<LI>
<DIV align=justify>hDialogTemplate = LoadResource(hInst, hResource);</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//锁定加载的资源</DIV>
<LI>
<DIV align=justify>if (hDialogTemplate != NULL)</DIV>
<LI>
<DIV align=justify>lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// return -1 in case of failure to load the dialog template resource</DIV>
<LI>
<DIV align=justify>if (lpDialogTemplate == NULL)</DIV>
<LI>
<DIV align=justify>return -1;</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//创建对话框前禁止父窗口,为此要调用PreModal得到父窗口句柄</DIV>
<LI>
<DIV align=justify>HWND hWndParent = PreModal();</DIV>
<LI>
<DIV align=justify>AfxUnhookWindowCreate();</DIV>
<LI>
<DIV align=justify>CWnd* pParentWnd = CWnd::FromHandle(hWndParent);</DIV>
<LI>
<DIV align=justify>BOOL bEnableParent = FALSE;</DIV>
<LI>
<DIV align=justify>if (hWndParent != NULL && ::IsWindowEnabled(hWndParent))</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>::EnableWindow(hWndParent, FALSE);</DIV>
<LI>
<DIV align=justify>bEnableParent = TRUE;</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//创建对话框,注意是无模式对话框</DIV>
<LI>
<DIV align=justify>TRY</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>//链接一个HOOK到HOOK链以处理窗口创建,</DIV>
<LI>
<DIV align=justify>//如同4.4.1节描述的CWnd类窗口创建一样</DIV>
<LI>
<DIV align=justify>AfxHookWindowCreate(this);</DIV>
<LI>
<DIV align=justify>//CreateDlgIndirect间接调用::CreateDlgIndirect,</DIV>
<LI>
<DIV align=justify>//最终调用了::CreateWindowEX来创建对话框窗口。</DIV>
<LI>
<DIV align=justify>//HOOK过程_AfxCbtFilterHook用子类化的方法</DIV>
<LI>
<DIV align=justify>//取代原来的窗口过程为AfxWndProc。</DIV>
<LI>
<DIV align=justify>if (CreateDlgIndirect(lpDialogTemplate, CWnd::FromHandle(hWndParent), hInst))</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>if (m_nFlags & WF_CONTINUEMODAL)</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>// enter modal loop</DIV>
<LI>
<DIV align=justify>DWORD dwFlags = MLF_SHOWONIDLE;</DIV>
<LI>
<DIV align=justify>//RunModalLoop接管整个应用程序的消息处理</DIV>
<LI>
<DIV align=justify>if (GetStyle() & DS_NOIDLEMSG)</DIV>
<LI>
<DIV align=justify>dwFlags |= MLF_NOIDLEMSG;</DIV>
<LI>
<DIV align=justify>VERIFY(RunModalLoop(dwFlags) == m_nModalResult);</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// hide the window before enabling the parent, etc.</DIV>
<LI>
<DIV align=justify>if (m_hWnd != NULL)</DIV>
<LI>
<DIV align=justify>SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW|</DIV>
<LI>
<DIV align=justify>SWP_NOSIZE|SWP_NOMOVE|</DIV>
<LI>
<DIV align=justify>SWP_NOACTIVATE|SWP_NOZORDER);</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify>CATCH_ALL(e)</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>DELETE_EXCEPTION(e);</DIV>
<LI>
<DIV align=justify>m_nModalResult = -1;</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify>END_CATCH_ALL</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//Enable并且激活父窗口</DIV>
<LI>
<DIV align=justify>if (bEnableParent)</DIV>
<LI>
<DIV align=justify>::EnableWindow(hWndParent, TRUE);</DIV>
<LI>
<DIV align=justify>if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)</DIV>
<LI>
<DIV align=justify>::SetActiveWindow(hWndParent);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//::EndDialog仅仅关闭了窗口,现在销毁窗口</DIV>
<LI>
<DIV align=justify>DestroyWindow();</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify> ostModal();</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// 必要的话,解锁/释放资源</DIV>
<LI>
<DIV align=justify>if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL)</DIV>
<LI>
<DIV align=justify>UnlockResource(hDialogTemplate);</DIV>
<LI>
<DIV align=justify>if (m_lpszTemplateName != NULL)</DIV>
<LI>
<DIV align=justify>FreeResource(hDialogTemplate);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>return m_nModalResult;</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>从DoModal的实现可以看出:
<LI>它首先Disable对话框窗口的父窗口;然后使用::CreateIndrectDialog创建对话框窗口,使用子类化的方法用AfxWndProc(或者AfxBaseProc)替换了原来的窗口过程,并把原来的窗口过程保存在CWnd的成员变量m_pfnSuper中。原来的窗口过程就是: ialogBox等创建对话框窗口时指定的,是Windows内部提供的对话框“窗口类”的窗口过程。取代(Subclass)原来“窗口类”的窗口过程的方法如同 4.4.1节描述的CWnd::Create。
<LI>在::CreateIndirectDialog创建对话框窗口后,会发送WM_INITDIALOG消息给对话框的对话框过程(必要的话,还有WM_SETFONT消息)。但是MFC取代了原来的对话框窗口过程,这两个消息如何送给对话框过程呢?处理方法如下节所描述。</LI></OL>
<DIV align=justify>
<OL>
<LI>
< ><B>使用原对话框窗口过程作消息的缺省处理</B> </P>
< >
< >对话框的消息处理过程和其他窗口并没有什么不同。这里主要分析的是如何把一些消息传递给对话框原窗口过程处理。下面,通过解释MFC对WM_INITDIALOG消息的处理来解释MFC窗口过程和原对话框窗口过程的关系及其协调作用。</P>
< >MFC提供了WM_INITDIALOG消息的处理函数CDialog::HandleInitDialog,WM_INITDIALOG消息按照标准Windows的处理送给HandleInitDialog处理。</P>
< >HandleInitDialog调用缺省处理过程Default,导致CWnd的Default函数被调用。CWnd: efault的实现如下:</P>
< align=justify>LRESULT CWnd: efault()</P>
< align=justify>{</P>
< align=justify>// call DefWindowProc with the last message</P>
< align=justify>_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();</P>
<P align=justify>return DefWindowProc(pThreadState->m_lastSentMsg.message,</P>
<P align=justify>pThreadState->m_lastSentMsg.wParam,</P>
<P align=justify>pThreadState->m_lastSentMsg.lParam);</P>
<P align=justify>}</P>
<P>顺便指出,从Default的实现可以看出线程状态的一个用途:它把本线程最新收到和处理的消息记录在成员变量m_lastSentMsg中。</P>
<P>在Default的实现中,CWnd的DefWindowsProc被调用,其实现如下:</P>
<P align=justify>LRESULT CWnd: efWindowProc(UINT nMsg, </P>
<P align=justify>WPARAM wParam, LPARAM lParam)</P>
<P align=justify>{</P>
<P align=justify>//若“窗口超类(SuperClass)”的窗口过程m_pfnSuper非空,则调用它</P>
<P align=justify>if (m_pfnSuper != NULL)</P>
<P align=justify>return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);</P>
<P align=justify></P>
<P align=justify>//在MFC中,GetSuperWndProcAddr的作用就是返回m_pfnSuper,为什么还</P>
<P align=justify>//要再次调用呢?因为虽然该函数现在是Obsolete,但原来曾经是有用的。如</P>
<P align=justify>//果返回非空,就调用该窗口过程进行处理,否则,由Windows进行缺省处理。</P>
<P align=justify>WNDPROC pfnWndProc;</P>
<P align=justify>if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)</P>
<P align=justify>return : efWindowProc(m_hWnd, nMsg, wParam, lParam);</P>
<P align=justify>else</P>
<P align=justify>return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);</P>
<P align=justify>}</P>
<P align=justify>综合上述分析,HandleInitDialog最终调用了窗口过程m_pfnSuper,即Windows提供给“对话框窗口类”的窗口过程,于是该窗口过程调用了对话框过程AfxDlgProc,导致虚拟函数OnInitDialog被调用。</P>
<P align=justify>顺便提一下,CWnd::AfxCallWndProc在处理WM_INITDIALOG消息之前和之后都会有一些特别的处理,如尝试把对话框放到屏幕中间。具体实现这里略。</P>
<P align=justify>OnInitDialog的MFC缺省实现主要完成三件事情:</P>
<P align=justify>调用ExecInitDialog初始化对话框中的控制;调用UpdateData初始化对话框控制中的数据;确定是否显示帮助按钮。所以,程序员覆盖该函数时,一定要调用基类的实现。</P>
<P align=justify></P>
<P align=justify>MFC采用子类化的方法取代了对话框的窗口过程,实现了12.1节描述的模式对话框窗口的一些特性,原来SDK下对话框过程要处理的东西大部分转移给MFC窗口过程处理,如处理控制窗口的控制通知消息等。如果不能处理或者必须借助于原来的窗口过程的,则通过缺省处理函数Default传递给原来的窗口过程处理,如同这里对WM_INITDIALOG的处理一样。</P>
<P align=justify></P>
<LI><B>Dialog命令消息和控制通知消息的处理</B>
<P>
<P align=justify>通过覆盖CWnd的命令消息发送函数OnCmdMsg,CDialog实现了自己的命令消息发送路径。在4.4.3.3节,曾经分析了CDialog::OnCmdMsg函数,这里给出其具体实现:</P>
<P align=justify>BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,</P>
<P align=justify>AFX_CMDHANDLERINFO* pHandlerInfo)</P>
<P align=justify>{</P>
<P align=justify>//首先,让对话框窗口自己或者基类处理</P>
<P align=justify>if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))</P>
<P align=justify>return TRUE;</P>
<P align=justify>//如果还未处理,且是控制通知消息或者状态更新消息或者系统命令</P>
<P align=justify>//则停止进一步的发送</P>
<P align=justify>if ((nCode != CN_COMMAND && nCode != CN_UPDATE_COMMAND_UI) ||</P>
<P align=justify>!IS_COMMAND_ID(nID) || nID >= 0xf000)</P>
<P align=justify>{</P>
<P align=justify>return FALSE; // not routed any further</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//尝试给父窗口处理</P>
<P align=justify>CWnd* pOwner = GetParent();</P>
<P align=justify>if (pOwner != NULL)</P>
<P align=justify>{</P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>if (afxTraceFlags & traceCmdRouting)</P>
<P align=justify>TRACE1("Routing command id 0x%04X to owner window.\n", nID);</P>
<P align=justify>#endif</P>
<P align=justify>ASSERT(pOwner != this);</P>
<P align=justify>if (pOwner->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))</P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>// 最后,给当前线程对象处理</P>
<P align=justify>CWinThread* pThread = AfxGetThread();</P>
<P align=justify>if (pThread != NULL)</P>
<P align=justify>{</P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>if (afxTraceFlags & traceCmdRouting)</P>
<P align=justify>TRACE1("Routing command id 0x%04X to app.\n", nID);</P>
<P align=justify>#endif</P>
<P align=justify>if (pThread->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))</P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>if (afxTraceFlags & traceCmdRouting)</P>
<P align=justify>{</P>
<P align=justify>TRACE2("IGNORING command id 0x%04X sent to %hs dialog.\n", nID,</P>
<P align=justify>GetRuntimeClass()->m_lpszClassName);</P>
<P align=justify>}</P>
<P align=justify>#endif</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>从上述实现可以看出,CDialog处理命令消息遵循如下顺序:</P>
<P align=justify>对话框自身→父窗口→线程对象</P>
<P align=justify>例如,模式对话框产生的WM_ENTERIDLE消息就发送给父窗口处理。</P>
<P align=justify>从实现中还看到,MFC根据TRACE过滤标识afxTraceFlags的值,把有关命令消息的派发显示到调试窗口。</P>
<P align=justify>CDialog::OnCmdMsg不仅适用于模式对话框,也适用于无模式对话框。</P>
<P align=justify></P></LI></OL></DIV></LI> |
|