走出MFC子类化的迷宫 ---原创

mahongxi 2002-12-03 10:04:20
走出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
);
...全文
445 123 打赏 收藏 转发到动态 举报
写回复
用AI写文章
123 条回复
切换为时间正序
请发表友善的回复…
发表回复
fgmailbox 2003-02-09
  • 打赏
  • 举报
回复
rainbowinfog 2003-02-09
  • 打赏
  • 举报
回复
收藏
lysde 2003-02-09
  • 打赏
  • 举报
回复
讨论的可是真细致

太好了
zoukaix 2003-02-09
  • 打赏
  • 举报
回复
好!
  • 打赏
  • 举报
回复
mark
passren 2003-02-03
  • 打赏
  • 举报
回复
up
FAICHEN 2003-01-29
  • 打赏
  • 举报
回复
haha
LeighSword 2003-01-29
  • 打赏
  • 举报
回复
subclass不是这样用的,笨蛋,误人子弟
knights 2003-01-21
  • 打赏
  • 举报
回复
直接派生一个类,然后把变量指定为这个派生类的对象不就ok了.
调用虚函数的时候自然就会使用到派生类的了.
oiq 2003-01-20
  • 打赏
  • 举报
回复
再说我文采也不好。
oiq 2003-01-20
  • 打赏
  • 举报
回复
我不写文章了。
关闭创建窗口与子类化,见《深》,那上面有。
breakfast 2003-01-18
  • 打赏
  • 举报
回复
收藏,
mark
oiq 2003-01-17
  • 打赏
  • 举报
回复
谢谢光荣!
是高手的请看:
http://expert.csdn.net/Expert/topic/1369/1369561.xml?temp=.6943628
谁解答了它,就是高手。
它解释了窗口句柄是如何捧定到一个CWnd类中的。

谁将它解决了,我就写一篇文章,将这些问题都讲请楚!
Panr 2003-01-17
  • 打赏
  • 举报
回复
另外你可以把断点设在CField::OnCreate 的函数里,你会发现在我们整个程序的运行过程中,该函数从未被调用过(所以oiq兄在看了你第一次的留言后,断定你的AfxRegisterClass 是有问题的)



真正起作用的是 CField::RegisterWindowClass 中的这两句:
if ( !GetClassInfo( hInstance, _T( "CField" ), &wndcls ) )
.
.
.
wndcls.lpszClassName = _T( "CField" );

它们与你第二次贴的代码中的
m_pwnd1->m_Field.CreateEx( WS_EX_CLIENTEDGE, _T("CField"), ...
这句相对应,这三个名字就是“新的窗体类型”的类名,个人建议你别把操作系统中“新的窗体类型的类名”和“源代码级窗体类型的类名”用一样的词,避免误会嘛
Panr 2003-01-17
  • 打赏
  • 举报
回复
to lionpb(很郁闷!很郁闷!很郁闷...):
这不是超类的概念,这可以说是最典型的OOP过程,先用AfxRegisterClass函数注册/创建一个叫“CField”的新的窗体类型,然后在用CreateEx 来创建它的实例
只是这个窗体类型在操作系统中注册的,而不是像一般的我们说的类是在VC的源代码级的概念

在超类过程是没有“新的窗体类型”出现的,超类是通过修改“已有窗体类型”的属性改变消息传递的路径的


随便说以下,如果你只是要截获CEdit::OnChar 消息,你只要从CEdit 继承一个类,然后用正常的消息映射和DoDataExchange就可以实现了,注册一个新类是有点牛刀杀鸡了。AfxRegisterClass主要是为了自己创建“与common control差别很大的类”时用的
lionpb 2003-01-17
  • 打赏
  • 举报
回复
实际上我的方法完全可以实现楼主要实现的功能啊。在CField类中处理WM_CHAR消息,也就是修改OnChar函数就行了。我现在的一个应用就是这么做的。
实际上我看窗口子类化的说明以及其例子,其目的也是与此类似,但例子往往都是修改默认窗口处理函数。
那么我们究竟为什么要用子类化的技术呢?这种办法究竟是子类化的一种方法,还是子类化之外的一个选择?最好是能够告诉我这两种方法各自的优缺点就更好了。我在Windows程序设计( Win32API权威指导)中并没有看到任何与此有关的说明。各位大侠如果能跟我讲讲,那就再好不过了。
oiq 2003-01-17
  • 打赏
  • 举报
回复
你这种用法当然不是子类化技术,也不是我上面举的三种对话框中应用的三种方式。
你这种是用CreateEx方法来创建窗口的方法,然后再显示出来,就象显示视图一样(单文档无文档视图支持),你在其中没有替换哪个窗口的窗口函数。
lionpb 2003-01-16
  • 打赏
  • 举报
回复
我不能确定我上述方法是否使用了子类化或者超类化的技术,但是这种方法确实是完全可行的,因此向楼主请教。我怀疑是否是在AfxRegisterClass中已经隐含了子类化/超类化的操作。
lionpb 2003-01-16
  • 打赏
  • 举报
回复

下面是 CTestDrawView::OnCreate中的一段代码。
m_pwnd1->CreateEx( WS_EX_CLIENTEDGE|WS_EX_DLGMODALFRAME, _T("CSunSwin"), _T("WINDOW1"),
WS_CHILD|WS_CAPTION|WS_VISIBLE, 40,40,300,160, this->m_hWnd, (HMENU)1 );
m_pwnd1->ShowWindow( SW_SHOW );
m_pwnd1->UpdateWindow( );

m_pwnd1->m_Field.CreateEx( WS_EX_CLIENTEDGE, _T("CField"), _T( "iiww_wwPP" ), WS_CHILD|WS_BORDER|WS_VISIBLE, CRect( 70,30, 230,50 ), m_pwnd1, 1 );
其中m_pwnd1是CSunSwin类(由CWnd类派生而来)的一个窗口,m_Field是m_pwnd1的一个CField类的成员变量。

你可以自己试一试,一点问题都没有。实际上我还可以用m_Field的Create方法,效果也是一样的。之所以这么写,只是为了证明我确实是用CField类名创建的。
由于在RegisterWindowClass中
wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW);
用了窗口的背景色,所以该编辑框的北京色是灰的。
oiq 2003-01-15
  • 打赏
  • 举报
回复
To: lionpb(很郁闷!很郁闷!很郁闷...)

我不知道你是怎么使用你的这个类的,但我可以肯定,你在对话框上创建的控件的窗口类名肯定不是 "CField"

我们在对话框编辑器中添加一个EDIT后,有以下几种方式使用这个EDIT

# 用ClassView 进行映射,比如映射成一个成员变量 m_edtOne,以后你就可以使用这个变量对这个EDIT进行操作。当然不能实现楼主的功能。

# 使用 SubClassWindow()函数,比如在对话框类中定义一个 CEdit m_edtOne; 然后在OnInitDialog()中使用 m_edtOne.SubClassWindow(ID_EDIT, this),以后可以使用m_edtOne对EDIT进行操作,这种方式中只要使用CEdit的继承类,就可以实现楼主的功能。

# 不在对话框中使用变量,而是即时使用即时得到变量。CEdit * p = (CEdit*)GetDlgItem(ID_EDIT); 在当前函数中就可以操作p 来操作EDIT。这种方式不能实现楼主的功能。

以上三种方式实际上都使用了子类化技术。
子类化技术的关键目的是什么?是替换一个窗口的默认窗口函数。那我们怎么替换呢?理论上只要得到某个窗口的句柄HWND 就可以调用函数SetWindowLong()函数来替换。所以无论使用以上三种方式的哪种,最终都调用了这个函数。
加载更多回复(103)

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧