子类化,通俗来讲就是用自己的窗口处理函数来处理特定消息,并将自己其他消息还给标准(默认)窗口处理函数。在SDK中,通过SetWindowLong来指定一个自定义窗口处理函数:SetWindowLong(hwnd, GWL_WNDPROC, (LONG)UserWndProc);。可是到了MFC中,大部分基础的东西都被封装起来了,那么,这是该怎么实现子类化呢?
       先来看一个例子:
       要求:定义一个Edit控件,让它能够对输入进行特定的处理输入进行处理-----只能输入英文字母,对其他输入作出提示。
       分析:1)处理输入当然是响应WM_CHAR消息了,然后对输入字符进行判断,并做相应处理。那么,我们有怎么才能让Edit自己处理输入呢?
                  2)我们知道Windows为我们设计Edit控件时,已经将常用操作通过成员函数的形式封装在CEdit类中了,直接由CEdit生成的对象自己并不能改变原有方法或是定制自己的方法(除了虚函数,但有时我们想实现的并不是虚函数啊!),那么现在想达到这些情况应该怎么办呢?这就用到本篇文章的主题-----MFC子类化。
                  3)我们可以从CEdit类派生一个新类CSuperEdit,然后通过子类化方法是Edit窗口来使用我们指定的消息处理函数。
        实现:先CSuperEdit,并为其添加WM_CHAR消息响应函数,这样CSuperEdit对象就拥有了自己WM_CHAR响应函数(这正是子类化的效果所在----面向对象----自己的方法封装在自己的类中),然后在其父窗口类(这里我们用一个基于对话框的MFC程序)中声明一个CSuperEdit类对象m_edit,当然m_edit需要和一个实际存在的窗口关联起来,因此,在CXXXDialog::OnInitDialogj中添加:m_edit.SubclassDlgItem(IDC_EDIT1,this);这样
就将m_edit这个c++对象和IDC_EDIT1窗口关联起来了,然后我们只需要在CSuperEdit::OnChar()中添加相应的操作就OK了。

原理探讨

追溯的目标:在整个程序中的哪个位置改变了m_edit关联窗口的消息处理函数。

首先,来探讨一下m_edit和窗口关联实现:m_edit.SetclassDlgItem(IDC_EDIT1,this); 我们进入该函数中看看:

BOOL CWnd::SubclassDlgItem(UINT nID, CWnd* pParent)

{

ASSERT(pParent != NULL);

ASSERT(::IsWindow(pParent->m_hWnd));

// check for normal dialog control first

HWND hWndControl = ::GetDlgItem(pParent->m_hWnd, nID);

if (hWndControl != NULL)

   return SubclassWindow(hWndControl);

     // 省略无关代码

… …

return FALSE;   // control not found

}

查看MSDN:

CWnd::SubclassDlgItem

This method dynamically subclasses a control created from a dialog box template, and attach it to this CWnd object. When a control is dynamically subclassed, windows messages will route through the CWnd message map and call message handlers in the CWnd class first. Messages that are passed to the base class will be passed to the default message handler in the control.

This method attaches the Windows control to a CWnd object and replaces the WndProc and AfxWndProc functions of the control. The function stores the old WndProc in the location returned by the CWnd::GetSuperWndProcAddrmethod.

翻译:

该方法动态子类化一个从对话框模板创建的控件,然后将它与一个CWnd对象(记为A)关联。当一个控件被动态子类化后,Windows消息将会根据CWnd消息地图路由并首先响应CWnd对象A的消息响应函数。被路由到基类的消息将会被该控件的默认消息处理函数处理。

该方法将一个Windows控件和一个CWnd对象相关联,并替换了这个控件原来的WndProc和AfxWndProc函数。这个函数储存了原先的WndProc的地址,该地址由CWnd::GetSuperWndProcAddr返回。

好!那么,该函数是怎样替换掉这个控件的原先WndProc和AfxWndProc函数的呢?在SubclassDlgItem函数中我们发现它返回的是SubclassWindow(hWndControl)这个函数的执行结果。

继续查看MSDN:

This method dynamically subclasses a window and attach it to this CWnd object. When a window is dynamically subclassed, windows messages will route through the CWnd message map and call message handlers in theCWnd class first. Messages that are passed to the base class will be passed to the default message handler in the window.

This method attaches the Windows CE control to a CWnd object and replaces the WndProc and AfxWndProc functions of the window.

The function stores a pointer to the old WndProc in the CWnd object.

发现:SubclassWindow与SubclassDlgItem的MSDN说明惊人的相似。可见,SubclassDlgItem函数功能的实现是通过SubclassWindow实现的。那么,对于上面的问题等于没有任何发现。现在查看SubclassWindow源代码:

BOOL CWnd::SubclassWindow(HWND hWnd)

{

if (!Attach(hWnd))

return FALSE;

// allow any other subclassing to occur

PreSubclassWindow();

// now hook into the AFX WndProc

1) WNDPROC* lplpfn = GetSuperWndProcAddr();

2) WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,

      (DWORD)AfxGetAfxWndProc());

ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());

if (*lplpfn == NULL)

3)    *lplpfn = oldWndProc;   // the first control of that type created

// 省略无关代码

… …

return TRUE;

}

在K这段代码之前,先回顾一下“MFC消息的起点”

MFC消息起点和流动:

Windows消息怎样从产生到响应函数收到该消息?

消息的起点

不管MFC是什么机理,其本质还是对Windows编程进行了整合封装,仅此而已!对Windows系统来说都是一样的,它都是不断地用GetMessage(或其他)从消息队列中取出消息,然后用DispatchMessage将消息发送到窗口函数中去。在“窗口类的诞生”中知道,MFC将所有的窗口处理函数都注册成DefWndProc,那么,是不是MFC将所有的消息都发送到DefWndProc中去了呢?答案是“不是”,而是都发送到AfxWndProc函数中去了(您可以回想一下前面我们查看MSDN是提到的AfxWndProc)。那么,MFC为什么要这样做呢?那就查看MFC代码,让它来告诉我们:

  1. BOOL CWnd::CreateEx(……)
  1. {
  1. ……
  1. PreCreateWindow(cs);
  1. AfxHookWindowCreate(this);
  1. HWND hWnd = ::CreateWindowEx(……);
  1. ……
  1. }
  1. void AFXAPI AfxHookWindowCreate(CWnd *pWnd)
  1. {
  1. ……
  1. pThreadState->m_hHookOldCbtFilter =
  1. ::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook,NULL,::GetCurrentThreadId());
  1. ……
  1. }
  1. _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
  1. {
  1. ……
  1. if(!afxData.bWin31)
  1. {
  1. _AfxStandardSubclass((HWND)wParam);
  1. }
  1. ……
  1. }
  1. void AFXAPI _AfxStandardSubclass(HWND hWnd)
  1. {
  1. ……
  1. oldWndProc =
  1. (WNDPROC)SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)AfxGetAfxWndProc());
  1. }
  1. WNDPROC AFXAPI AfxGetAfxWndProc()
  1. {
  1. ……
  1. return &AfxWndProc;
  1. }

仔细分析上面的代码,发现MFC在创建窗口之前,通过AfxHookWindowCreate设置了钩子(这样有消息满足所设置的消息时,系统就发送给你设置的函数,在这里就是_AfxCbtFilterHook),这样每次创建窗口时,该函数就将窗口函数修改成AfxWndProc。至于为什么要这样做?那是因为包含3D控件和兼容MFC2.5。

消息的起点都是AfxWndProc,所有消息都被发送到AfxWndProc中,然后在从AfxWndProc流向各自的消息响应函数。AfxWndProc的作用就和车站的作用是一样的,人们都要先到车站来,然后流向各种的目的地。那么自己的目的地在哪,只有自己才会知道。当然对于消息,也只有它自己才会知道要去哪!而“这种只有自己知道”在MFC中反映为“MFC根据不同类型的消息设置不同的消息路由路径,然后不同类型的消息走自己的路就OK了(计算机嘛!自己当然不会知道,要用算法嘛!)”。

消息的流动

  1. LRESULT CALLBACK AfxWndProc(…….)
  1. {
  1. ……
  1. return AfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);
  1. }
  1. LRESULT AFXAPI AfxCallWndProc(……)
  1. {
  1. ……
  1. lResult = pWnd->WindowProc(nMsg,wParam,lParam);
  1. ……
  1. }
  1. LRESULT CWnd::WindowProc(……)
  1. {
  1. ……
  1. if(!OnWndMsg(message,wParam,lParam,&lResult))
  1.       lResult = DefWindowProc(message,wParam,lParam);
  1. ……
  1. }
  1. BOOL CWnd::OnWndMsg(……)
  1. //该函数原来太过庞大,为了只表达意思,将其改造如下
  1. {
  1.     ……
  1.     if(message == WM_COMMAND)
  1.          OnCommand(wParam,lParam);
  1.     if(message == WM_NOTIFY)
  1.          OnNotify(wParam,lParam,&lResult);
  1.  
  1.     //每个CWnd类都有它自己的消息地图
  1. pMessage = GetMessageMap();
  1. //在消息地图中查找当前消息的消息处理函数
  1. for(; pMessageMap!=NULL; pMessageMap = pMessageMap->pBaseMap)
  1. {
  1.         if((lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries,
  1.                     message,0,0))!=NULL)
  1.          break;
  1. }
  1.    
  1. (this->*(lpEntry->pnf))(……);//调用消息响应函数
  1. }
  1. AFX_MSGMAP_ENTRY AfxFindMessageEntry(……)
  1. {
  1. ……
  1. while(lpEntry->nSign!=AfxSig_end)
  1. {
  1.        if(lpEntry->nMessage==nMsg&&lpEntry->nCode==nCode&&nID>=lpEntry->nID
  1.               &&nID<=lpEntry->nLastID)
  1.        {
  1.              return lpEntry;
  1.        }
  1.        lpEntry++;
  1. }
  1. ……
  1. }

仔细分析上面的代码,发现消息的路由关键在于OnCmdMsg函数。OnCmdMsg或者WM_COMMAND消息调用OnCommand(),或者为WM_NOTIFY消息调用OnNotify()。它将没有被处理的消息都是为窗口消息。OnCmdMsg()搜索类的消息映射,以便找到一个能处理窗口消息的处理函数。

这样我们就找到了消息的处理函数了。

回顾完这些知识,我们回到SubclassWindow的实现代码中。

我们发现了三个关键的语句:

1) WNDPROC* lplpfn = GetSuperWndProcAddr();

2) WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,

      (DWORD)AfxGetAfxWndProc());

3)                *lplpfn = oldWndProc;

对这三个语句进行分析:

1)            获取原窗口处理过程地址,随便说一下是怎么获得的:窗口通过CreateEx创建,在调用CreateEx中又调用了CreateWindowEx,调用该函数后将原来的窗口处理函数地址保存在了窗口类的成员函数m_pfnSuper中了。

2)            看到了没:用SetWindowLong将窗口处理函数改为AfxGetWndProc,根据前面的分析,它会调用AfxWndProc,再通过OnCmdMsg进行消息的路由。结合本例,CSuperEdit有它自己的消息地图,WM_CHAR消息路由时就会找了CSuperEdit类它自己的OnChar()函数,这样子类化的目的----封装自己的消息处理函数----就达到了。

3)            当然CSuperEdit只定义了一部分自己的消息处理函数,大部分还是要由原来的函数(CWnd)完成,所以要保存原来函数地址,这句代码就完成此功能。

至此,我们关于子类化的来龙去脉就搞清楚了。

补充:我们在用ClassWizard将一个控件与一个自定义的类型关联起来后,我们并没有添加像xxx.SubclassDlgItem(xxx,this);这样的代码,但是也实现了以上的功能?原因是ClassWizard已经为我们实现了上面的关联,其原理是一样的。

深入理解MFC子类化的更多相关文章

  1. 走出MFC子类化的迷宫

    走出MFC子类化的迷宫 KEY WORDS:子类化 SUBCLASSWINDOW  MFC消息机制 许多Windows程序员都是跳过SDK直接进行RAD开发工具[或VC,我想VC应不属于RAD]的学习 ...

  2. C++ 中超类化和子类化常用API

    在windows平台上,使用C++实现子类化和超类化常用的API并不多,由于这些API函数的详解和使用方法,网上一大把.本文仅作为笔记,简单的记录一下. 子类化:SetWindowLong,GetWi ...

  3. C++ 中超类化和子类化

    超类化和子类化没有具体的代码,其实是一种编程技巧,在MFC和WTL中可以有不同的实现方法. 窗口子类化: 原理就是改变一个已创建窗口类的窗口过程函数.通过截获已创建窗口的消息,从而实现监视或修改已创建 ...

  4. 窗口的子类化与超类化——子类化是窗口实例级别的,超类化是在窗口类(WNDCLASS)级别的

    1. 子类化 理论:子类化是这样一种技术,它允许一个应用程序截获发往另一个窗口的消息.一个应用程序通过截获属于另一个窗口的消息,从而实现增加.监视或者修改那个窗口的缺省行为.子类化是用来改变或者扩展一 ...

  5. Win32中安全的子类化(翻译)

    关于子类化的话题虽然有些旧,但它至今仍然不失为一种开发Windows的强有力技术,在MFC的内核.甚至.NET的内核中都离不开它,希望本连载能对Windows开发的爱好者有所帮助. 原文标题:Safe ...

  6. 动态子类化CComboBox以得到子控件EDIT及LISTBOX

    动态子类化CComboBox以得到子控件EDIT及LISTBOX Joise.LI写于2004-4-6 ComboBox是比较常用的一个控件,有三种样式:CBS_SIMPLE(简单),CBS_DROP ...

  7. 眼见为实(2):介绍Windows的窗口、消息、子类化和超类化

    眼见为实(2):介绍Windows的窗口.消息.子类化和超类化 这篇文章本来只是想介绍一下子类化和超类化这两个比较“生僻”的名词.为了叙述的完整性而讨论了Windows的窗口和消息,也简要讨论了进程和 ...

  8. Delphi的子类化控件消息, 消息子类化

    所谓的子类化,网上有很多说明,我就说我个人的随意理解,可能有误,请列位看官斟酌理解. 所谓子类化,个人理解就是拦截某个控件的消息以及样式,来进行自己的特定处理以达到特殊的功能需求.这个子类化,可以有子 ...

  9. 子类化GetOpenFileName/GetSaveFileName, 以及钩子函数OFNHookProc的使用的简要说明

    昨天, 群里面有一个人问起: 要怎么让"文件打开对话框"居中显示, 有人说子类. 而我告诉他的方法是用钩子函数OFNHookProc, 不知道这是不是所谓的子类?相信看了我今天这篇 ...

随机推荐

  1. USB系列之七:ASPI介绍及命令测试

    在以前的一篇博文<关于构建DOS下编程平台的总结>中曾经介绍了一种在DOS下驱动U盘的方法,我们大致回顾一下.在config.sys中加入两个驱动程序,就可以驱动U盘:device = a ...

  2. ./configure : /bin/sh^M : bad interpreter

    用命令行来编译Qt的时候发生标题尚的错误. 原因是文件中带有DOS行结束符,必须把它转换成UNix结束符 references: http://stackoverflow.com/questions/ ...

  3. Vitamio视频播放

    activity代码 package com.hck.player.ui; import io.vov.utils.StringUtils; import io.vov.vitamio.LibsChe ...

  4. ubuntu下hadoop完全分布式部署

    三台机器分别命名为: hadoop-master ip:192.168.0.25 hadoop-slave1 ip:192.168.0.26 hadoop-slave2 ip:192.168.0.27 ...

  5. Linux转发性能评估与优化-转发瓶颈分析与解决方式(补遗)

    补遗 关于网络接收的软中断负载均衡,已经有了成熟的方案,可是该方案并不特别适合数据包转发,它对server的小包处理非常好.这就是RPS.我针对RPS做了一个patch.提升了其转发效率. 下面是我转 ...

  6. Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/7932268 在前面一篇文章中,我们分析了And ...

  7. dom4j解析XML的CURD操作

    在开发JavaWeb项目中通常是利用XML配置文件来提高应用程序的访问效率,而在配置XML的同时,更多时候是对XML进行解析. 一.解析XML配置文件有两种机制: DOM和SAX的机制: DOM是将整 ...

  8. 解决在Linux下安装Oracle时的中文乱码问题

    本帖最后由 TsengYia 于 2012-2-22 17:06 编辑 解决在Linux下安装Oracle时的中文乱码问题 操作系统:Red Hat Enterprise Linux 6.1数据库:O ...

  9. VS插件集

    Unit Test Generator  很好用的测试插件 注:在VS2015中,改名为Test generator Nunit extension了. ReSharperPlatformVs11   ...

  10. 使用react-native做一个简单的应用-02项目搭建与运行

    下面我们开始着手去做这一个项目,因为初学不久就开始边学边做,所以有些地方设计不太合理.请大家多多包涵.0.0 下面来介绍截图中的三个文件夹, GuoKuApp:是我开发app的文件夹. GuoKuDB ...