MFC浅析(7) CWnd类虚函数的调用时机、缺省实现 .

简介: 1. Create 2. PreCreateWindow 3. PreSubclassWindow 4. PreTranslateMessage 5. WindowProc 6. OnCommand 7. OnNotify 8. OnChildNotify 9. DefWindowProc 10. DestroyWindow 11. PostNcDestroy CWnd作为MFC中最基本的与窗口打交道的类,完成了大部分窗口管理任务。

1. Create
2. PreCreateWindow
3. PreSubclassWindow
4. PreTranslateMessage
5. WindowProc
6. OnCommand
7. OnNotify
8. OnChildNotify
9. DefWindowProc
10. DestroyWindow
11. PostNcDestroy

CWnd作为MFC中最基本的与窗口打交道的类,完成了大部分窗口管理任务。同时提供了很多虚拟函数,这些虚拟函数在适当的地方提供了供派生类参与管理的接口。

一直以来,对这些虚拟函数的来龙去脉有所糊涂,无法明确的判断他们在什么时候调用,又缺省完成了些什么。重载时哪些是要注意的...等等。

抽时间查看了MFC的原码,想看其究竟。

总结如下:


1. Create

virtual BOOL Create( LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext = NULL);
调用时机:

窗口建立时

作为主窗口,大多在InitInstance()中将直接或间接调用Create

作为子窗口,大多再父窗口建立后发出WM_CREATE消息,对其进行处理时OnCreate()中调用。

功能:

控制建立细节

CWnd实现:

 .......
 //注册窗口类,调用API建立窗口
 // allow modification of several common create parameters
 CREATESTRUCT cs;
 cs.dwExStyle = dwExStyle;
 cs.lpszClass = lpszClassName;
 cs.lpszName = lpszWindowName;
 cs.style = dwStyle;
 cs.x = x;
 cs.y = y;
 cs.cx = nWidth;
 cs.cy = nHeight;
 cs.hwndParent = hWndParent;
 cs.hMenu = nIDorHMenu;
 cs.hInstance = AfxGetInstanceHandle();
 cs.lpCreateParams = lpParam;
 //在此调用虚拟函数PreCreateWindow,允许在实际建立之前“篡改”建立参数。
 if (!PreCreateWindow(cs))
 {
  PostNcDestroy();
  return FALSE;
 }
 AfxHookWindowCreate(this);
 HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
   cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
   cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
#ifdef _DEBUG
 if (hWnd == NULL)
 {
  TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X/n",
   GetLastError());
 }
#endif
 if (!AfxUnhookWindowCreate())
  PostNcDestroy();        // cleanup if CreateWindowEx fails too soon
 if (hWnd == NULL)
  return FALSE;
 ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
 return TRUE;
}

2. PreCreateWindow
调用时机:
参见上段,在Create()中,设置好窗口建立数据cs后,在实际建立窗口之前,将cs“暴露”给派生类,允许派生类在此时改变窗口建立参数。
功能:
控制建立参数 (在Create()中可以设置建立信息,但Create有时是框架结构隐含调用的,故在PreCreateWindow时,再提供一个修订窗口建立参数的机会)。
CWnd实现:
BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
{
 //如果在派生类中用户没有定制类名,没有制定窗口类名,使用MFC默认注册类
 if (cs.lpszClass == NULL)
 {
  // make sure the default window class is registered
  VERIFY(AfxDeferRegisterClass(AFX_WND_REG));
  // no WNDCLASS provided - use child window default
  ASSERT(cs.style & WS_CHILD);
  cs.lpszClass = _afxWnd;
 }
 return TRUE;
}

如果需要,使用自定的窗口类,应该在派生类的PreCreateWindow中注册,并得到并指定类名。

3. PreSubclassWindow
调用时机:
建立窗口的同时将C++Wnd对象附着在窗口上
 CWnd::Create()中:
 ...
 AfxHookWindowCreate(this);
 HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
   cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
   cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
 AfxUnhookWindowCreate();
 ...

建立窗口时,系统建立WH_CBT(训练)钩子(截获窗口动作),在钩子函数中完成CWnd对象对窗口的"包裹".
file://操作很多,主要有
   //pWndInit为传入的参数,应该就是CWnd对象指针了
   //对象连接到窗口句柄
   pWndInit->Attach(hWnd);
   ...
   //调用虚拟函数PreSubclassWindow,给用户一个定义相关操作的机会,例如,子控件的附着
   pWndInit->PreSubclassWindow();
   ...
   //设置消息处理函数等等。
   WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();
   ...
   WNDPROC afxWndProc = AfxGetAfxWndProc();
   oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,
     (DWORD)afxWndProc);
   ...

CWnd实现:
CWnd类中,在此虚拟函数中没有缺省动作。

4. PreTranslateMessage
调用时机:
进程的消息队列处理循环中,在将窗口的消息分发到窗口的消息处理函数之前将调用CWinApp虚拟函数PreTranslateMessage,允许再窗口派生类中对即将发送的消息进行处理。
而CWinApp::PreTranslateMessage将有可能调用到窗口的PreTranslateMessage。
先来看以下CWinThread::PumpMessage中对消息的分发过程
 ...
 ::GetMessage(&m_msgCur, NULL, NULL, NULL)
 ...
 //CWinThread的PreTranslateMessage虚拟函数被调用
 
 if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
 {
  ::TranslateMessage(&m_msgCur);
  ::DispatchMessage(&m_msgCur);
 }

BOOL CWinThread::PreTranslateMessage(MSG* pMsg)
{
 ....
 CWnd* pMainWnd = AfxGetMainWnd();
 //依此调用从命令发出窗口到主窗口间各级窗口的PreTranslateMessage();
 //参见下面的WalkPreTranslateTree原码
 if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))
  return TRUE;
 // in case of modeless dialogs, last chance route through main
 //   window's accelerator table
 if (pMainWnd != NULL)
 {
   CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
   if (pWnd->GetTopLevelParent() != pMainWnd)
   return pMainWnd->PreTranslateMessage(pMsg);
 }
 return FALSE;   // no special processing
}
BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)
{
 ....
 //依次调用各级窗口的PreTranslateMessage
 for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))
 {
  CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
  if (pWnd != NULL)
  {
   if (pWnd->PreTranslateMessage(pMsg))
    return TRUE; // trapped by target window (eg: accelerators)
  }
  // got to hWndStop window without interest
  if (hWnd == hWndStop)
   break;
 }
 return FALSE;       // no special processing
}


5. WindowProc
调用时机:
窗口建立后,将进入消息循环。在此期间,WindowPro被调用以处理各消息。
在窗口建立时,消息处理函数被制定,一般是AfxWndProc,其将调用AfxCallWndProc,而AfxCallWndProc最终将调用到虚拟函数WindowProc。
功能:
允许派生类在消息处理前,添加处理。
CWnd实现:
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
 
 LRESULT lResult = 0;
 file://主要是由OnWndMsg完成消息的分类,分解处理。
 if (!OnWndMsg(message, wParam, lParam, &lResult))
 file://剩余部分交由缺省命令处理函数处理。
  lResult = DefWindowProc(message, wParam, lParam);
 return lResult;
}
附:OnWndMsg流程

在OnWndMsg中,将根据消息的性质,归类成命令消息、通知消息、普通消息

分别由OnCommand、OnNotify...处理


BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
 //如果是WM_COMMAND消息,由虚拟函数OnCommand处理,
 //WM_COMMAND由菜单、工具条等发出,表示特定的命令,与窗口消息由所不同。
 if (message == WM_COMMAND)
 {
  ....
  OnCommand(wParam, lParam))
  ....
 }
 //如果消息是WM_NOTIFY,即通知消息,由虚拟函数OnNotify处理,
 if (message == WM_NOTIFY)
 {
  .....
  OnNotify(wParam, lParam, &lResult))
  .....
 }
 
 //对特殊消息的处理:
 WM_ACTIVATE...
 WM_SETCURSOR...
 
 //普通消息
 .......
 //在类消息映射中查找消息对应的消息处理函数。
 //参数转换等等...
 .......
 //找到后,调用该函数。
 mmf.pfn = lpEntry->pfn;
 lResult = (this->*mmf.pfn_lwl)(wParam, lParam);
 .......
}

6. OnCommand

调用时机:

在OnWndMsg中,如果消息是WM_COMMAND,即命令消息,将调用OnCommand;

在OnCommand中可以对命令处理进行操作。

CWnd实现: file://参见对命令更新机制的分析

BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
  .....
  //试探性的调用OnCmdMsg(,,CN_UPDATE_COMMAND_UI..)看当前命令项是否有效
  CTestCmdUI state;
  state.m_nID = nID;
  OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL);
  if(...)//命令有效时设置标志为命令标志:CN_COMMAND
  nCode = CN_COMMAND;
  ....
  //如果是子窗口的通知消息,则反射给子窗口 ????,若子窗口有相应处理,则返回。若未处理,还是作为命令处理。
  //有一部分通知消息是通过WM_COMMAND发送的
 
  if(ReflectLastMsg(hWndCtrl))
   return TRUE;    // eaten by child
  //命令消息经过整理后,调用虚拟函数OnCmdMsg !!!!!!
  return OnCmdMsg(nID, nCode, NULL, NULL);
}
对命令处理的具体流程,参见文章相关文章。


7. OnOnNotify

BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
调用时机:

OnWndMsg中,若处理的消息是WM_NOTIFY,将调用OnNotify对该通知消息具体处理。

虚函数OnNotify提供了在派生类中管理通知消息的接口。

CWnd实现: BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult) { ..... file://将通知消息反射到发出通知的窗口,由其处理,若该窗口未处理,在由OnCmdMSg处理。 if (ReflectLastMsg(hWndCtrl, pResult)) return TRUE; // eaten by child ..... file://交由OnCmdMsg处理。对应的消息映射宏为 return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY), ¬ify, NULL); }

关于消息反射:

控件通常将自己的变化情况以通知消息的形式告知父窗口。由父窗口响应处理。

MFC的反射机制可将通知消息传回到原窗口,在原窗口的消息映射体系中得到对事件的处理。这便于窗口功能的封装。

若子窗口没有对该通知的反射处理函数,则该通知消息还是由父窗口处理。


在ReflectLastMsg中,将调用pWnd->SendChildNotifyLastMsg,(pWnd是指向子窗口的指针)

SendChildNotifyLastMsg中将调用虚拟函数OnChildNotify。

在子窗口的OnChildNotify中可以在接收到反射消息,处理之前添加处理。


8. OnChildNotify

BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)

调用时机:

窗口向父窗口发出通知后,被反射回来时,将先调用OnChildNotify。

可以在此函数中监测处理由父窗口传来的通知消息。

CWnd实现:

调用CWnd成员ReflectChildNotify

通知消息的处理实际上还是由原有消息、命令处理流程完成的。不同的是,他们的消息、命令数值被调整以区别窗口自己的消息、命令。

在类的消息映射项中,反射消息处理宏完成对应的通知消息与处理函数的关联。

BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
 //针对返回的消息分类处理成特定消息格式
 switch (uMsg)
 {
 //普通消息
 //WM_HSCROLL,WM_VSCROLL:.........
 .....
  //转换成反射消息号,由命令处理函数。(在消息映射中,反射消息的序号为WM_REFLECT_BASE+uMsg)
  return CWnd::OnWndMsg(WM_REFLECT_BASE+uMsg, wParam, lParam, pResult);
 //如果是WM_COMMAND
 case WM_COMMAND:
  {
   .....
   //直接交给窗口的OnCmdMsg,同时命令的序号被相应改变,以与窗口自己收到的同样命令相区别,并有不同的消息映射项。
   CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_COMMAND), NULL, NULL))
   .....
  }
 //如果是 WM_NOTIFY通知
 case WM_NOTIFY:
  {
   //.......
   //交由OnCmdMsg处理。更改命令序号。
   CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY), ?ify, NULL);
   .....
  }
  //颜色类
  if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC)
  {
   ....
   CWnd::OnWndMsg(WM_REFLECT_BASE+WM_CTLCOLOR, 0, (LPARAM)&ctl, pResult);
   ....
  }
  ......
}

9. DefWindowProc

调用时机:

WindowProc中消息经由OnWndMsg后,未找到对应的处理函数,将交由DefWindowProc处理。

在DefWindowProc中,可以针对这些未处理的消息增加相应操作。

CWnd实现

LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
{
 if (m_pfnSuper != NULL)
  return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);
 WNDPROC pfnWndProc;
 if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)
  return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);
 else
  return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);
}

10. DestroyWindow

先来看CWnd对DestroyWindow的实现。

BOOL CWnd::DestroyWindow()
{
  ....
  //销毁窗口
  if (m_pCtrlSite == NULL)
   bResult = ::DestroyWindow(m_hWnd);
  else
   bResult = m_pCtrlSite->DestroyControl();
  ....
  //C++窗口对象与窗口脱离
  Detach();
}
在此实现中,调用API:BOOL DestroyWindow(HWND hWnd);

API的DestroyWindow将向窗口发送WM_DESTROY和WM_NCDESTROY消息,


调用时机:

①.对于主窗口:(CFrameWnd)

当窗口接收到关闭消息时,将调用DestroyWindow

关闭消息的发送:

在菜单上选择退出,将给一个ID_APP_EXIT命令,该命令在CWinApp::OnAppExit中有缺省实现:向主窗口发送WM_CLOSE;

另,按下窗口关闭钮,将给窗口一个WM_CLOSE消息。

在WM_CLOSE的缺省处理OnClose()中

void CFrameWnd::OnClose()
{
 ....
 DestroyWindow();
 ....
}
②.对于其他窗口

主窗口销毁时将调用::DestroyWindow,此API将向窗口发送WM_DESTROY和WM_NCDESTROY消息。

并自动完成完成子窗口的销毁。

在MFC中,子窗口的DestroyWindow虚拟函数并未被调用,但需要的时候可以重载后自己调用。控制子窗口的销毁。


11. PostNcDestroy

调用时机:

窗口销毁后,在WM_NCDESTROY的处理函数OnNcDestroy()中调用。

在PostNcDestroy中一般将完成C++的窗口对象的删除等收尾工作。

CWnd实现:

CFrameWnd实现

delete this;(删除窗口对象)


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/FMD/archive/2001/06/16/5529.aspx

目录
相关文章
|
6月前
|
存储 编译器 C语言
【C++】类和对象②(类的默认成员函数:构造函数 | 析构函数)
C++类的六大默认成员函数包括构造函数、析构函数、拷贝构造、赋值运算符、取地址重载及const取址。构造函数用于对象初始化,无返回值,名称与类名相同,可重载。若未定义,编译器提供默认无参构造。析构函数负责对象销毁,名字前加`~`,无参数无返回,自动调用以释放资源。一个类只有一个析构函数。两者确保对象生命周期中正确初始化和清理。
|
7月前
|
消息中间件 SQL 安全
[MFC] CWnd类总结
[MFC] CWnd类总结
136 0
|
C++
同样一句代码,在类内调用,跟类外调用结果不同?
同样一句代码,在类内调用,跟类外调用结果不同?
80 0
|
C#
禁止在构造函数里调用虚函数
禁止在构造函数里调用虚函数
147 0
什么情况下会调用拷贝构造函数?
什么情况下会调用拷贝构造函数?
265 0
|
.NET
vc应用CPictureEx类(重载CStatic类)加载gif动画
1.PictureEx.h文件: //////////////////////////////////////////////////////////////////////// PictureEx.
1450 0