走出MFC子类化的迷宫

走出MFC子类化的迷宫

KEY WORDS:子类化 SUBCLASSWINDOW  MFC消息机制

 

许多Windows程序员都是跳过SDK直接进行RAD开发工具[或VC,我想VC应不属于RAD]的学习,有些人可能对子类化机制比较陌生。

我们先看看什么是Windows的子类化。Windows给我们或是说给它自己定义了许多丰富的通用控件,如:Edit、ComboBox 、ListBox……等,这些控件功能丰富,能为我们开发工作带来极大方面,试想:我们单单是自己实现一个EDIT控件是多么的艰难!但是,在实际开发中还是有些情况这些标准控件也无能为力,比如:在我们的应用中要求一个EDIT得到老师对学生的评价A、B、C[不要对我说你想用ComboBox实现J],这时,要求在Edit中禁止对其它字母、数字的输入操作,怎么办?EDIT控件本身没有提供这种机制,我们就可以采用子类化很好的解决这类问题。

我们知道,每一个Windows窗口[这里是EDIT]都有一个窗口处理函数负责对消息处理,子类化的办法就是用我们自己的消息处理函数来替代窗口原有的、标准的处理函数。当然我们自己的窗口处理函数只是关心那些特定的消息[在这里当然是WM_CHAR了],而其它消息,再发给原来的窗口函数处理。在SDK中的实现方法是调用函数SetWindowLong :

WNDPROC * oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,(DWORD)AfxGetAfxWndProc());

其中AfxGetAfxWndProc()是我们自己的窗口处理函数,在其中处理过我们感兴趣的消息后就可能通过返回的原窗口处理函数指针oldWndProc来把其它消息按标准方法处理掉,具体做法请查阅相关资料。

但到了MFC“时代”,一切都被包装起来了,原来的窗口类注册、窗口函数都不见了[或是说隐身了],我想对于那些“刨根问底”的程序员有兴趣了解在MFC中的子类化机制,本人就自己做的一点“探索”作出总结,希望能给大家点启示。

我们先用MFC实现我上面提到的要求:一个只能输入A,B,C的EDIT控件。

启动时界面如下:

输入时就只能输入A、B、C,并且只允许输入一个字母。

实现方法:

先派生一个自己的类CsuperEdit,Ctrl + W后,在其中处理WM_CHAR,然后再编辑这个消息处理函数:

 

void CSuperEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

     // TODO: Add your message handler code here and/or call default

     TCHAR ch[20];

     GetWindowText(ch,20);

     if (strlen(ch) == 1 && (nChar <= 'C' && nChar >= 'A'))

            return;

     if (nChar != 'A'

            && nChar != 'B'

            && nChar != 'C'

            )

            return;

    

     CEdit::OnChar(nChar, nRepCnt, nFlags);

}

 

然后再给我们Cprog1Dlg类中加入一个数据成员CsuperEdit m_edit,在CProg1Dlg::OnInitDialog()中加入:

m_edit.SubclassDlgItem(IDC_EDIT1,this);

     m_edit.SetWindowText("<请输入A、B、C>");

并处理EDIT向DIALOG发送的通知消息:EN_SETFOCUS:

void CProg1Dlg::OnSetfocusEdit1()

{

     // TODO: Add your control notification handler code here

     m_edit.SetWindowText("");

     m_edit.SetFocus();

}

 

OK,一切搞定!和SDK的子类化方法比起来,这是多么的容易!

我们看看MFC背着我们到底做了什么!这里主要解决两个容易让初学者比较疑惑的问题:

1、    m_edit只是我们定义的一个C++类对象,为什么通过它调用其成员函数SetWindowText便可以控制我们程序中资源编号为:IDC_EDIT1的控件?

2、    CSuperEdit类为什么可以处理WM_CHAR消息?

 

大家都知道,控制Windows窗口、控件、资源……都是通过它们的句柄来实现,如
HHANDLE、HWND、HDC都是句柄,它表现为一个32位长整形数据,存放于Windows中的特定区域,我们可以把它理解为指向我们想控制的窗口、控件、资源的索引,有了它,我们就可以控制我们想要控制的对象。

这里你可以想到为什么多数API函数都有一个参数HWND hwnd了吧!

BOOL SetWindowText(
 HWND hWnd,         // handle to window or control
 LPCTSTR lpString   // title or text
);

我们的C++变量m_edit要想控制IDC_EDIT1,也要通过它的句柄,但这又是如何实现的呢?您可能注意到了m_edit.SubclassDlgItem(IDC_EDIT1,this);一句,对了,这就是关键所在!

在此处F9设置断点,F5之后,程序到达此处,F11跟入SubclassDlgItem函数:

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);

 

#ifndef _AFX_NO_OCC_SUPPORT

     if (pParent->m_pCtrlCont != NULL)

     {

            // normal dialog control not found

            COleControlSite* pSite = pParent->m_pCtrlCont->FindItem(nID);

            if (pSite != NULL)

            {

                   ASSERT(pSite->m_hWnd != NULL);

                   VERIFY(SubclassWindow(pSite->m_hWnd));

 

#ifndef _AFX_NO_OCC_SUPPORT

                   // If the control has reparented itself (e.g., invisible control),

                   // make sure that the CWnd gets properly wired to its control site.

                   if (pParent->m_hWnd != ::GetParent(pSite->m_hWnd))

                          AttachControlSite(pParent);

#endif //!_AFX_NO_OCC_SUPPORT

 

                   return TRUE;

            }

     }

#endif

 

     return FALSE;   // control not found

}

代码开始时对传入的父窗口做些检查,然后就是

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

     if (hWndControl != NULL)

            return SubclassWindow(hWndControl);

这是关键的代码,先用hWndControl得到我们IDC_EDIT1控件的句柄,然后调用

SubclassWindow函数,这个函数是实现的关键,我们来看一下它做了什么:

 

 

 

BOOL CWnd::SubclassWindow(HWND hWnd)

{

     if (!Attach(hWnd))

            return FALSE;

 

     // allow any other subclassing to occur

     PreSubclassWindow();

 

     // now hook into the AFX WndProc

     WNDPROC* lplpfn = GetSuperWndProcAddr();

     WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,     (DWORD)AfxGetAfxWndProc());

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

 

     if (*lplpfn == NULL)

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

#ifdef _DEBUG

     else if (*lplpfn != oldWndProc)

     {

            TRACE0("Error: Trying to use SubclassWindow with incorrect CWnd/n");

            TRACE0("/tderived class./n");

            TRACE3("/thWnd = $%04X (nIDC=$%04X) is not a %hs./n", (UINT)hWnd,

                   _AfxGetDlgCtrlID(hWnd), GetRuntimeClass()->m_lpszClassName);

            ASSERT(FALSE);

            // undo the subclassing if continuing after assert

            ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)oldWndProc);

     }

#endif

 

     return TRUE;

}

 

函数Attach内部如下:

BOOL CWnd::Attach(HWND hWndNew)

{

       ASSERT(m_hWnd == NULL);     // only attach once, detach on destroy

       ASSERT(FromHandlePermanent(hWndNew) == NULL);

              // must not already be in permanent map

 

       if (hWndNew == NULL)

              return FALSE;

 

       CHandleMap* pMap = afxMapHWND(TRUE); // create map if not exist

       ASSERT(pMap != NULL);

 

       pMap->SetPermanent(m_hWnd = hWndNew, this);

 

#ifndef _AFX_NO_OCC_SUPPORT

       AttachControlSite(pMap);

#endif

 

       return TRUE;

}

 

这里要说明的是pMap->SetPermanent(m_hWnd = hWndNew, this);一句,它把我们IDC_EDIT1的句柄赋值给类CsuperEdit的数据成员m_hWnd [别忘了我们的CsuperEdit类是派生于Cedit的],大家可能现在已经隐约的明白了些什么,不错,在m_edit.SetWindowText("<请输入A、B、C>");中正是通过这个数据成员m_hWnd实现对IDC_EDIT1控制的:

void CWnd::SetWindowText(LPCTSTR lpszString)

{

       ASSERT(::IsWindow(m_hWnd));

 

       if (m_pCtrlSite == NULL)

              ::SetWindowText(m_hWnd, lpszString);

       else

              m_pCtrlSite->SetWindowText(lpszString);

}

其它CEdit类的函数也都是围绕 “m_hWnd + API函数” 进行包装的。

而我们常用的DDX_Control方法说到底也是调用SubclassWindow。

 

怎么样?第一个问题的来龙去脉搞明白了吧?

 

现在看看第二个问题:CSuperEdit类为什么可以处理WM_CHAR消息?

可能有的朋友现在疑惑,虽然通过句柄实现了m_edit对IDC_EDIT的控制,但发送给它的消息照样跑到EDIT的标准处理函数中,对WM_CHAR的处理是如何实现的呢?

如果消息照样跑到EDIT的标准处理函数中,那当然是不能处理了!不知您有没有看到在上面的SubclassWindow函数中有这么一小段我加了重点标示:

// now hook into the AFX WndProc

     WNDPROC* lplpfn = GetSuperWndProcAddr();

     WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,     (DWORD)AfxGetAfxWndProc());

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

 

     if (*lplpfn == NULL)

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

再和我们开始讲到的SDK中子类化机制联系起来,明白了吧?MFC在这里神不知鬼不觉的搞起偷天换日的勾当!

这个AfxGetAfxWndProc()函数是这样的:

 

 

WNDPROC AFXAPI AfxGetAfxWndProc()

{

#ifdef _AFXDLL

       return AfxGetModuleState()->m_pfnAfxWndProc;

#else

       return &AfxWndProc;

#endif

}

读过侯捷先生《深入浅出MFC》的朋友不知还是否记得MFC的命令路由机制正是以这个函数为起点的!

这样当程序收到发给Edit的WM_CHAR时,本应调用EDIT标准窗口处理函数,现在被改为调用LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)了,然后WM_CHAR消息进行一系列的流窜,最终成功到达我们的处理函数CSuperEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags),至于是如何流窜的、怎么到达的请参考《深入浅出MFC》[如果您的书是繁体电子版,请从566页读起]。

 

终于,我们走出了FMC子类化的迷宫。

 

 

                                                                             CSDN 烤鸡翅膀

                                                                             2002-12-3

[FINISH]

 

PS:本人才疏学浅,如果说得有什么让人“笑得露齿”的地方,一定要通知我呦。

 

from:http://blog.csdn.net/mahongxi/article/details/6357

时间: 2024-08-31 00:01:46

走出MFC子类化的迷宫的相关文章

深入理解MFC子类化

子类化,通俗来讲就是用自己的窗口处理函数来处理特定消息,并将自己其他消息还给标准(默认)窗口处理函数.在SDK中,通过SetWindowLong来指定一个自定义窗口处理函数:SetWindowLong(hwnd, GWL_WNDPROC, (LONG)UserWndProc);.可是到了MFC中,大部分基础的东西都被封装起来了,那么,这是该怎么实现子类化呢?       先来看一个例子:       要求:定义一个Edit控件,让它能够对输入进行特定的处理输入进行处理-----只能输入英文字母,

【浙大脑机接口实验室探秘】人类与AI控制大鼠走出迷宫,C10背负的混合智能未来(视频)

(文/杨静 )我对浙江大学的脑机接口研究长久以来抱有朝圣般的浓厚兴趣,主要是受浙江大学计算机学院潘纲教授的影响.他在一次演讲里展示了脑控大鼠在一名研究人员的脑电波控制下,快速走迷宫的神奇现场,这只大鼠是在中央电视台的节目里表演走迷宫的.当时现场一位嘉宾跟我交流说,他去浙江大学的实验室参观过这只脑控大鼠,据他观察这只大鼠受人类操控走迷宫时,他感觉动作比较机械,更像一个机器而不是一个动物.他的话更引发了我的好奇,一定要想办法去看看这只大鼠,到底是不是已经演化成"机器"了. 浙大的超级明星

走出迷宫, 你适合什么职业?

  [走出迷宫, 你适合什么职业? ] 终点A的人适合职业: police.教练.作家. 终点B的人适合职业: 漫画家.会计.导演.设计师. 终点C的人适合职业: 领导.律师.指挥. 终点D的人适合职业: 医生.教师.歌手.记者.工人. 终点E的人适合职业: 演员.司机.商人.基层管理人员.    我走出来是 C...可是我是老师...

窗口子类化-实例应用

所谓窗口子类化:改变一个已经存在的窗口实例的性质:消息处理与其他实例属性.   通常在SDK中所谓的窗口子类化就是改变一个窗口函数(如GetWindowLong()和SetWindowLong())通过这两个函数来设置窗口的属性等:   而今天我们主要内容是介绍MFC中的子类化,它跟SDK中的子类化不太一样: 所有MFC窗口有相同的窗口函数,由该窗口函数根据窗口句柄查找窗口实例,在把消息映射到该窗口类(class)得消息处理函数上.为了利用MFC的消息映射机制,不宜改变窗口函数(名),MFC也把

自绘控件的子类化方法

  1.       新建一个基于对话框的MFC程序. 2.       在工程中添加一个新类CMyButton,基类选择CButton. 3.       然后转至新生成的MyButton.h头文件中,将鼠标放在类名CMyButton上点击一下,打开"属性"窗口,在"消息"按钮下为此类添加 WM_LBUTTONDOWN,WM_LBUTTONUP消息,并重写DrawItem虚函数(这个函数是重绘按钮时要调用的,在这里面可以自定义一些绘制按钮的操作,必须设置控件属性为

C++ 中超类化和子类化及他们的区别

C++ 中超类化和子类化 超类化和子类化没有具体的代码,其实是一种编程技巧,在MFC和WTL中可以有不同的实现方法. 窗口子类化: 原理就是改变一个已创建窗口类的窗口过程函数.通过截获已创建窗口的消息,从而实现监视或修改已创建窗口类的行为属性.可以用来改变或者扩展一个已存在的窗口的行为,而不用重新开发.比如要获得那些预定义控件窗口类(按钮控件.编辑控件.列表控件.下 拉列表控件.静态控件和滚动条控件)的功能而又要修改它们的某些行为. 子类化的优点主要体现在以下两个方面:首先,它不需要创建新的窗口

如何走出人才网站经营的困境

网上求职与招聘是互联网上一种信息化人才交流形式,网络招聘不受时间.地域.空间的限制,避免了人群大范围集中和对场地的依赖,给用人单位和求职者提供远在天边近在咫尺的交流平台.因此,网上求职与招聘受到了越来越多用人单位和毕业生青睐,成为招聘行业的"新宠".正是基于这样一个庞大而可看得见的市场蛋糕使不少企业和个人蜂拥而上,刷新着中国人才网站的数量. 其实目前国内数量众多的人才招聘网站并不像人们想象中的那么乐观,技术落后,收费低廉,服务水平参次不齐,已经使中国的网络人才服务市场陷入了一个恶性循环

走出WEB应用防火墙认识误区——WAF是强不是墙

在<走出Web应用防火墙认识误区>系列文章(一)中,我们分析讨论了谁能保护Web应用,在本文中我们将重点介绍WAF的特点和应用. 早在2004年,国外一些安全厂商就提出了Web应用防火墙(Web Application Firewall,简称WAF)的概念,并开始了逐步的尝试(例如梭子鱼网络有限公司将Netcontinuum公司纳入旗下,当时的Netcontinuum就是这一领域的先行者,其解决方案包含网站的网络应用安全.通信管理和SSL加速等).支付卡行业安全标准委员会也发布了支付卡行业数据

解说Win32的窗口子类化

也许你需要一个特殊的Edit来限制浮点数的输入,但是现有的Edit却并不能完成这项工作--因为它只能够单纯的限制大小写或者纯数字.当你在论坛上求救的时候,某个网友告诉你:"用子类化."你也许会在看到一线曙光的同时多出了一连串的问题:何为子类化?子类化的原理是什么?如何实现子类化?下面就让我从一个简单的C++程序开始,一步步解开你的疑团吧. 首先,我为你列出以下这个C++程序: #include <iostream> using namespace std; class Pa