子类化,通俗来说就是用本身的窗口处理函数来处理特定消息,并将本身其余消息还给标准(默认)窗口处理函数。在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::On
就将m_edit这个c++对象和IDC_EDIT1窗口关联起来了,而后咱们只须要在CSuperEdit::On
原理探讨web
追溯的目标:在整个程序中的哪一个位置改变了m_edit关联窗口的消息处理函数。算法
首先,来探讨一下m_edit和窗口关联实现:m_edit.SetclassDlgItem(IDC_EDIT1,this); 咱们进入该函数中看看:编程
BOOL CWnd::SubclassDlgItem(UINT nID, CWnd* pParent)windows
{函数
ASSERT(pParent != NULL);this
ASSERT(::IsWindow(pParent->m_hWnd));spa
// 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)这个函数的执行结果。
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
// 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++;
}
……
}
仔细分析上面的代码,发现消息的路由关键在于On
这样咱们就找到了消息的处理函数了。
回顾完这些知识,咱们回到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,再经过On
3) 固然CSuperEdit只定义了一部分本身的消息处理函数,大部分仍是要由原来的函数(CWnd)完成,因此要保存原来函数地址,这句代码就完成此功能。
至此,咱们关于子类化的前因后果就搞清楚了。
补充:咱们在用ClassWizard将一个控件与一个自定义的类型关联起来后,咱们并无添加像xxx.SubclassDlgItem(xxx,this);这样的代码,可是也实现了以上的功能?缘由是ClassWizard已经为咱们实现了上面的关联,其原理是同样的。