SubclassWindow和SubclassDlgItem

通常如果在对话框中将一个控件映射到一个变量,有三种方法:

  1. DDX的方法
  2. GetDlgItem的方法,例如CEdit pEdt = (CEdit *)GetDlgItem(IDC_EDIT1);
  3. SubclassWindow的方法(或者其扩展SubclassDlgItem),例如CEdit m_edit;m_edit.SubclassDlgItem(IDC_EDIT1);

SubclassWindow

CWnd::SubclassWindow(HWND hWnd)中调用两个主要操作:Attach(hWnd)和WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)AfxGetAfxWndProc());

前者的作用是把CWnd中的m_hWnd设置为hWnd,后者的作用是改变该窗口的窗口函数为AfxGetAfxWndProc()的返回.

AfxGetAfxWndProc返回了AfxWndProc的函数指针,即窗口函数的指针,AfxWndProc包裹了AfxCallWndProc,后者又调用了pWnd->WindowProc(nMsg, wParam, lParam);。

可见SubclassWindow完成了两项功能:

  1. 我们对该窗体实例调用成员函数将会直接改变相关窗体句柄对应的窗体(Attach)
  2. 系统传给相关窗体句柄的消息会先经过该窗体实例的消息映射(SetWindowLong)。

SubclassDlgItem

调用了SubclassWindow,但之前调用了::GetDlgItem获取一个控件ID对应的窗口句柄。

GetDlgItem

只是调用::GetDlgItem获得控件ID对应的窗口句柄,然后使用FromHandle将句柄转换为CWnd指针。

SubclassDlgItem和GetDlgItem二者的区别

如果只是想调用一个控件对应类的方法,差别不大。只是前者会生成一个类对象,而后者得到指向对象的指针。

但如果跟消息有关,则前者会相应消息。例如:

比如我自己写了一个类叫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);

通过这种方式,可以动态改变一个控件的消息处理流程,使得CsuperEdit中重载的消息可以被执行。如果不使用子类型化,则无法执行。

例子:::

许多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)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (INT_PTR)AfxGetAfxWndProc());
 ASSERT(oldWndProc != AfxGetAfxWndProc());
 if (*lplpfn == NULL) *lplpfn = oldWndProc; // the first control of that type created
#ifdef _DEBUG
 else if (*lplpfn != oldWndProc)
 {
  TRACE(traceAppMsg, 0, "Error: Trying to use SubclassWindow with incorrect CWnd/n");
  TRACE(traceAppMsg, 0, "/tderived class./n");
  TRACE(traceAppMsg, 0, "/thWnd = $%08X (nIDC=$%08X) is not a %hs./n",
   (UINT)(UINT_PTR)hWnd, _AfxGetDlgCtrlID(hWnd), GetRuntimeClass()->m_lpszClassName);
  ASSERT(FALSE); // undo the subclassing if continuing after assert
  ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (INT_PTR)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页读起]。 终于,我们走出了MFC子类化的迷宫。

时间: 2024-10-01 03:54:35

SubclassWindow和SubclassDlgItem的相关文章

DDX_Control、SubclassWindow和SubclassDlgItem

文章参考地址:http://blog.sina.com.cn/s/blog_86fe5b440101au88.html:http://www.cnblogs.com/riskyer/p/3424278.html 问题缘起 通常如果在对话框中将一个控件映射到一个变量,有三种方法: 1. DDX的方法 2. GetDlgItem的方法,例如CEdit pEdt = (CEdit *)GetDlgItem(IDC_EDIT1); 3. SubclassWindow的方法(或者其扩展SubclassDl

日志文件目录

一.Visio分类 以编程方式使用 Microsoft Office Visio 2003 ActiveX 控件 visio应用程序相关设置-选项-视图 visio应用程序相关设置-选项-常规 visio中相关设置-菜单视图 Visio中设置自定义属性的值 Visio中的Undo和Redo Visio中ShapeAdded和SelectionAdded 二.位分类 补码.原码.反码 不同进制之间的转换 位操作基础 常用位操作小技巧 位操作的趣味应用 位操作与空间压缩 位操作基础篇之位操作全面总结

CWnd中PreCreateWindow、PreSubclassWindow、SubclassWindow的区别

http://blog.csdn.net/swimmer2000/archive/2007/10/30/1856213.aspx MFC(VC6.0)的CWnd及其子类中,有如下三个函数:     // From VS Install PathVC98MFCIncludeAFXWIN.H    class CWnd : public CCmdTarget    {        ...    public:        ...        virtual BOOL PreCreateWind

关于SubclassWindow

#define SubclassWindow(hwnd, lpfn) \ ((WNDPROC)SetWindowLongPtr((hwnd), GWLP_WNDPROC, (LPARAM)(WNDPROC)(lpfn))) 从该宏可以看出是调用SetWindowLongPtr 具体的详解:http://blog.csdn.net/sunliangyuan/article/details/6028425 由于照着上面的链接敲的时候遇到了两个问题: 1.OnChar无法响应 2.当按回车键的时候整个

WTL自定义控件:SubclassWindow的实现

自定义了一个edit类如下: 1 class CCheckEditEx : public CWindowImpl< CCheckEditEx, CEdit > 其SubclassWindow函数实现如下: 1 BOOL CCheckEditEx::SubclassWindow(HWND hwnd) 2 { 3 ATLASSERT(NULL == ::GetWindow(hwnd, GW_CHILD)); 4 if (CWindowImpl< CCheckEditEx, CEdit >

MFC自绘控件学习总结

前言:从这学期开始就一直在学习自绘控件(mfc),目标是做出一款播放器界面,主要是为了打好基础,因为我基础实在是很烂....说说我自己心得体会以及自绘控件的方法吧,算是吐槽吧,说的不对和不全的地方,或者有更好的方法,请不吝赐教.我的机器环境是:Windows7旗舰版 Service Pack 1,Visual studio 20051).重绘某个控件时,强烈推荐使用子类化方法,比如想自绘Button控件, 首先添加自己的类CMYButton 继承自 CButton ,声明一个CMYButton

VC常用小知识

(1) 如何通过代码获得应用程序主窗口的 指针?主窗口的 指针保存在CWinThread::m_pMainWnd中,调用AfxGetMainWnd实现.AfxGetMainWnd() ->ShowWindow(SW_SHOWMAXMIZED)//使程序最大化. (2) 确定应用程序的路径Use GetModuleFileName 获得应用程序的路径,然后去掉可执行文件名.Example:TCHARexeFullPath[MAX_PATH] // MAX_PATH在API中定义了吧,好象是128G

mfc 按钮自绘

MFC  按钮自绘 :songyanwu 如果你是大神就没必要看这个文章了! 说明 源码下载:mfc 按钮自绘 先说说自己的一些想法:我就想把按钮封装成一个类,每次在使用的时候会很方便,当然在自己的类中去重载也可以! 此文章可借鉴学习:MFC基础,MFC自绘控件学习总结. (我也主要研究了自绘控件的子类化方法  ),看完前面推荐的文章,你似乎有何种感觉呢? 先实际操作吧;原理在后面介绍: 1 新建一个对画框 应用程序 2 新添加一个CMyButton继承CButton 3 为你自己添加的类 添加

窗口的子类化与超类化

1. 子类化 改变一个已经存在的窗口实例的性质:消息处理与其他实例属性.在SDK编程范畴内,子类化就是改变一个窗口实例的窗口函数(通过GetWindowLong()和SetWindowLong()),子类化所要做的就是为某窗口实例编写新的窗口函数.其操作是在实例级别上进行的.在MFC中子类化的情况有所不同:所有MFC窗口有相同的窗口函数,由该窗口函数根据窗口句柄查找窗口实例,在把消息映射到该窗口类(class)得消息处理函数上.为了利用MFC的消息映射机制,不宜改变窗口函数(名),MFC也把子类