子类化,通俗来讲就是用自己的窗口处理函数来处理特定消息,并将自己其他消息还给标准(默认)窗口处理函数。在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代码,让它来告诉我们:

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

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

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

消息的流动

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

仔细分析上面的代码,发现消息的路由关键在于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. 嵌入式设备web服务器

    操作系统:ubuntu10.04 前言:    为了提高对设备的易操作性,很多设备中提供pc机直接通过浏览器操作设备的功能.这就需要在设备中实现web服务器.    现在在嵌入式设备中所使用的web服 ...

  2. 【转】android 电池(三):android电池系统

    关键词:android电池系统电池系统架构 uevent power_supply驱动 平台信息: 内核:linux2.6/linux3.0系统:android/android4.0 平台:S5PV3 ...

  3. Mac 下纯lua(三)

    文件处理 直接使用io调用 io.close();文件流关闭 io.flush():如果文件流以bufferd缓存模式处理,输入不会立即存入文件,需要调用本函数 io.input(file):输入 i ...

  4. linux下php扩展curl的安装

    方法一 安装cURL wget http://curl.haxx.se/download/curl-7.17.1.tar.gz tar -zxf curl-7.17.1.tar.gz ./config ...

  5. top命令的解释

    top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器.下面详细介绍它的使用方法. top - 01:06:48 up  1:22,   ...

  6. 学习asp.net Identity 心得体会(连接oracle)

    asp.net Identity具体功能暂不在此细说,下面主要介绍几点连接oracle注意的事项, 1.首先下载连接oracle驱动Oracle.ManagedDataAccess.dll和Oracl ...

  7. 浅谈JavaScript DOM编程艺术读后感和一点总结

    最近工作不是很忙就想想想JavaScript的内部组成和一些要点,就是从这本书开始的.对新手来说还好,简单易懂. 简单终结下我重书中学到的一些要点. 下面都是个人学习的要点提取: 1.给自己预留退路, ...

  8. ASP.net+SQL server2008简单的数据库增删改查 VS2012

    工具:VS2012 数据库:SQL server 简单说明:根据老师上课给的代码,进行了简单的改正适用于VS2012环境,包括注册.登录.查询.修改.删除功能,多数参考了网上的代码 百度云源代码连接t ...

  9. Javascript DOM编程艺术JS代码

    //com function addLoadEvent (func) { var oldonload = window.onload; if (typeof window.onload != 'fun ...

  10. setlocal enabledelayedexpansion

    http://www.jb51.net/article/29323.htm 例1: 代码如下: @echo off set a=4 set a=5&echo %a% pause  结果:4 解 ...