现在的位置: 首页 > 综合 > 正文

VC++中的对话框(2)

2014年10月04日 ⁄ 综合 ⁄ 共 6539字 ⁄ 字号 评论关闭

本小节讨论对话框的窗口过程。


AfxWndProc是所有的MFC窗口类使用的窗口过程,它取代了模式对话框原来的窗口过程(Windows提供),那么,MFC如何完成Win32下对话框窗口的功能呢?

考查模式对话框的创建过程。CDialog::DoModal用来创建模式对话框窗口并执行有关任务,和DoModal相关的是MFC内部使用的成员函数CDialog::PreModal和CDialog::PostModal。下面分别讨论它们的实现。

HWND CDialog::PreModal()

{

// cannot call DoModal on a dialog already constructed as modeless

ASSERT(m_hWnd == NULL);

 

// allow OLE servers to disable themselves

AfxGetApp()->EnableModeless(FALSE);

 

// 得到父窗口

CWnd* pWnd = CWnd::GetSafeOwner(m_pParentWnd, &m_hWndTop);

 

// 如同CWnd处理其他窗口的创建,设置一个窗口创建HOOK

AfxHookWindowCreate(this);

 

//返回父窗口的句柄

return pWnd->GetSafeHwnd();

}

 

void CDialog::PostModal()

{

//取消窗口创建前链接的HOOK

AfxUnhookWindowCreate();   // just in case

//MFC对话框对象和对应的Windows对话框窗口分离

Detach();               // just in case

 

// m_hWndTop是当前对话框的父窗口或所属窗口,则恢复它

if (::IsWindow(m_hWndTop))

::EnableWindow(m_hWndTop, TRUE);

m_hWndTop = NULL;

 

AfxGetApp()->EnableModeless(TRUE);

}

 

int CDialog::DoModal()

{

// can be constructed with a resource template or InitModalIndirect

ASSERT(m_lpszTemplateName != NULL ||

m_hDialogTemplate != NULL || m_lpDialogTemplate != NULL);

 

//加载对话框资源

LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;

HGLOBAL hDialogTemplate = m_hDialogTemplate;

HINSTANCE hInst = AfxGetResourceHandle();

//查找资源(见9.5.2节),找到了就加载它

if (m_lpszTemplateName != NULL)

{

hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);

HRSRC hResource = 

::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);

hDialogTemplate = LoadResource(hInst, hResource);

}

 

//锁定加载的资源

if (hDialogTemplate != NULL)

lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);

 

// return -1 in case of failure to load the dialog template resource

if (lpDialogTemplate == NULL)

return -1;

 

//创建对话框前禁止父窗口,为此要调用PreModal得到父窗口句柄

HWND hWndParent = PreModal();

AfxUnhookWindowCreate();

CWnd* pParentWnd = CWnd::FromHandle(hWndParent);

BOOL bEnableParent = FALSE;

if (hWndParent != NULL && ::IsWindowEnabled(hWndParent))

{

::EnableWindow(hWndParent, FALSE);

bEnableParent = TRUE;

}

 

//创建对话框,注意是无模式对话框

TRY

{

//链接一个HOOK到HOOK链以处理窗口创建,

//如同4.4.1节描述的CWnd类窗口创建一样

AfxHookWindowCreate(this);

//CreateDlgIndirect间接调用::CreateDlgIndirect,

//最终调用了::CreateWindowEX来创建对话框窗口。

//HOOK过程_AfxCbtFilterHook用子类化的方法

//取代原来的窗口过程为AfxWndProc。

if (CreateDlgIndirect(lpDialogTemplate, CWnd::FromHandle(hWndParent), hInst))

{

if (m_nFlags & WF_CONTINUEMODAL)

{

// enter modal loop

DWORD dwFlags = MLF_SHOWONIDLE;

//RunModalLoop接管整个应用程序的消息处理

if (GetStyle() & DS_NOIDLEMSG)

dwFlags |= MLF_NOIDLEMSG;

VERIFY(RunModalLoop(dwFlags) == m_nModalResult);

}

 

// 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取代了原来的对话框窗口过程,这两个消息如何送给对话框过程呢?处理方法如下节所描述。

对话框的消息处理过程和其他窗口并没有什么不同。这里主要分析的是如何把一些消息传递给对话框原窗口过程处理。下面,通过解释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的处理一样。

通过覆盖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

    if (afxTraceFlags & traceCmdRouting)

        TRACE1("Routing command id 0x%04X to owner window./n", nID);

#endif

    ASSERT(pOwner != this);

    if (pOwner->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

        return TRUE;

}

 

// 最后,给当前线程对象处理

CWinThread* pThread = AfxGetThread();

if (pThread != NULL)

{

#ifdef _DEBUG

    if (afxTraceFlags & traceCmdRouting)

        TRACE1("Routing command id 0x%04X to app./n", nID);

#endif

    if (pThread->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

        return TRUE;

}

 

#ifdef _DEBUG

if (afxTraceFlags & traceCmdRouting)

{

        TRACE2("IGNORING command id 0x%04X sent to %hs dialog./n", nID,

                GetRuntimeClass()->m_lpszClassName);

    }

#endif

return FALSE;

}

从上述实现可以看出,CDialog处理命令消息遵循如下顺序:

对话框自身→父窗口→线程对象

例如,模式对话框产生的WM_ENTERIDLE消息就发送给父窗口处理。

从实现中还看到,MFC根据TRACE过滤标识afxTraceFlags的值,把有关命令消息的派发显示到调试窗口。

CDialog::OnCmdMsg不仅适用于模式对话框,也适用于无

 

抱歉!评论已关闭.