分享
 
 
 

浅谈MFC中超类化技术的实现

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

// 关键词:

// 面对对象编程、超类化、子类化、Superclassing

// MFC、CWnd::SubclassWindow

// 通用控件、CMNCTRL

//

// 主题:

// 通过CWnd::SubclassWindow 函数的分析,浅谈MFC中超类化技术的实现

//

//

// 背景

// 我在2002-12月见了mahongxi (烤鸡翅膀)(色摸)在CSDN上的一个帖

// 介绍了MFC中窗体的超类化的概念,以下是对我个人回贴的总结

//

// 日志

// 修改:Panr 2002-12-15 13:30 版式整理,转帖到CSDN文档中心

// 修改:Panr 2002-12-15 13:30 勘误

// 原作:Panr 2002-12-13 12:00

//

// 关于“文档中心”

// 在那个帖子里看到njtu_shiyl(玉晶)提到了文档中心,

// 我就一直在想文档中心在哪?

// 后来再回顾那个帖时,跟着翅膀兄就来到了这儿

// 所以这篇也就顺理成章是我的第一次

// 估计我是找对了地方...

//

//

一:超类化概述

在MFC中窗体实例对某个窗体句柄超类化后,系统提供了这样两种能力:

1.我们对该窗体实例调用成员函数将会直接改变相关窗体句柄对应的窗体

2.系统传给相关窗体句柄的消息会先经过该窗体实例的消息映射

我举一个例子来说明:

比如我自己写了一个类叫CSuperEdit(父类为CEdit),在该类中我声明了void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);并在消息循环里添加了ON_WM_CHAR 一行

现在我只要在对话框CProg1Dlg 中声明CSuperEdit m_edit;然后在CProg1Dlg::OnInitDialog中,添加以下代码,就完成了“超类化”:

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

m_edit.SubclassWindow (hWndControl);

这样超类化处理以后:

当我们调用m_edit.SetWindowText("<请输入A、B、C>");,后IDC_EDIT1窗体上对应的文字就会改变为"<请输入A、B、C>"

当用户在IDC_EDIT1窗体中敲键盘时,系统会调用我自己写的CSuperEdit::OnChar函数(而不是原先的CEdit::OnChar)

二:超类化实现的概述

所有的秘密都在CWnd::SubclassWindow 中,让我们查看一下它到底做了些什么吧,以下是函数体(在WINCORE.CPP文件内):

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

return TRUE;

}

结合注释不难想到PreSubclassWindow 是非功能性的函数,所以我们只要研究两个函数就可以了解CWnd::SubclassWindow 的大概功能CWnd::Attach和 ::AfxGetAfxWndProc

两者中当中CWnd::Attach 对应于实现了功能1,即“我们对该窗体实例调用成员函数将会直接改变相关窗体句柄对应的窗体”

::AfxGetAfxWndProc函数对应于实现了功能2,即“系统传给相关窗体句柄的消息会先经过该窗体实例的消息映射”

三:功能1的实现

CWnd::Attach 的函数体如下(在WINCORE.CPP文件内):

BOOL CWnd::Attach(HWND hWndNew)

{

if (hWndNew == NULL)

return FALSE;

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

ASSERT(pMap != NULL);

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

return TRUE;

}

最关键的是m_hWnd = hWndNew 一句(接触过windows的API的朋友都知道,windows系统所有窗体操作函数都是把窗体句柄作为一个调用参数),显然只要我把窗体的句柄保存下来,那我就可以在系统中唯一地指定一个窗体,然后对该窗体进行操作

是的,思路就是这么简单。我们现在看到CWnd(别忘了CsuperEdit 是从CWnd继承的,这里的CWnd实际就是CsuperEdit )在Attach 函数中把IDC_EDIT1 的句柄保存在了成员变量m_hWnd 中,那么实现功能1,自然也就不在话下了

至于CHandleMap::SetPermanent 函数则是用来延长句柄的使用期的,与“超类化”无关,不在此处讨论,其具体实现可参考WINHAND_.H文件

四:功能2的实现

四点一:窗体句柄的GWL_WNDPROC属性

在前面的讨论中,我说过功能2是跟::AfxGetAfxWndProc 有关的,该函数的实现是这样的(也是在WINCORE.CPP文件中):

WNDPROC AFXAPI AfxGetAfxWndProc()

{

#ifdef _AFXDLL

return AfxGetModuleState()->m_pfnAfxWndProc;

#else

return &AfxWndProc;

#endif

}

这是指在DLL中调用的话返回AfxGetModuleState()->m_pfnAfxWndProc;否则返回AfxWndProc 函数的地址。于是在一般的可执行文件中CWnd::SubclassWindow 为功能2所做的事可以简化为一行::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)&AfxWndProc);

该函数的作用是把窗体句柄hWnd 的GWL_WNDPROC 属性设置为AfxWndProc 的地址,那么现在急需解决的问题是:窗体句柄的GWL_WNDPROC 属性是干什么用的?其实不用我说,大家都猜得到(因为我们是在讨论窗体的消息嘛,而且我也一直在说AfxWndProc是一个函数),它的作用是指定窗体消息的处理函数

对于该属性更准确地描述如下:对于发给窗体的所有消息,Windows操作系统将会以该消息为参数调用窗体句柄的GWL_WNDPROC属性所指定的函数

四点二:被传递到MFC环境中

(本节参考了侯捷老师《深入浅出MFC》中“消息映射与命令传递”一章的“两万五千里长征”)

于是功能2可以表述为:AfxWndProc函数是如何找到我为CSuperEdit 类所写的消息映射的?还是从函数体出发

LRESULT CALLBACKAfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

{

// special message which identifies the window as using AfxWndProc

if (nMsg == WM_QUERYAFXWNDPROC)

return 1;

// all other messages route through message map

CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);

}

如上所列::AfxWndProc 整个函数只有四行,显然它仅仅是包装了::AfxCallWndProc 函数,只是把hWnd参数包装成pWnd,然后转道::AfxCallWndProc。

::AfxCallWndProc该函数才是真正做了一些事的,但其中与消息传递有关直接关系的就一句:

LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,

WPARAM wParam = 0, LPARAM lParam = 0)

{

...

// delegate to object's WindowProc

lResult = pWnd->WindowProc(nMsg, wParam, lParam);

...

return lResult;

}

现在我们已经看到通过::AfxWndProc/::AfxCallWndProc 两个函数的接力,操作系统中消息被传递到MFC环境中的。

进一步的讨论可以把所有的目光都集中到LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam);

四点三:总结

我们看到转机了:为了实现不同的函数调用,OOP(面对对象编程)本身提供继承、虚函数之类的许多的方法。MFC正是一种面对对象的语言

现在CsuperEdit 是继承自CEdit,CEdit 又继承自CWnd,我们要让程序调用CsuperEdit::OnChar 也就没什么技术难度。比如,可以在CWnd中写一个响应键盘消息的虚函数 virtual void CWnd::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);,并在CWnd::WindowProc 中调用OnChar

那么我只要重载CsuperEdit::OnChar 函数,程序自然而然就会调用我写的函数了

微软为了减小程序文件的体积,做了一些优化工作,它未用virtual 修饰符来修饰所有的函数,而是把“要响应的消息和相应的响应函数”登记在一张MESSAGE_MAP(称,消息映射)里。

在AFXMSG_.H文件中ON_WM_CHAR 宏定义被为{WM_CHAR, 0, 0, ... &OnChar},它的作用就是把WM_CHAR和当前类(现在指CsuperEdit)的OnChar函数,填加到了消息映射的登记表中

既然有了“消息映射”这样一张的登记表,对于“让CWnd在接受到WM_CHAR 消息时调用CsuperEdit::OnChar”的算法和代码,估计你我都能在两小时内实现,我就不在此处罗嗦了,至于MFC中的相关的代码请参考“深入浅出”一书

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有