SubClassWindow 的使用

转载 一篇很好的文章:

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

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

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

        SDK中的实现方法是调用函数SetWindowLong

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

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

      我们先用MFC实现我上面提到的要求:一个只能输入ABCEDIT控件。启动时界面如下:输入时就只能输入ABC,并且只允许输入一个字母。

         实现方法:先派生一个自己的类CsuperEditCtrl + 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("<请输入ABC>");

 并处理EDITDIALOG发送的通知消息:EN_SETFOCUS

void CProg1Dlg::OnSetfocusEdit1()

{

       // TODO: Add your controlnotification handler code here

       m_edit.SetWindowText("");m_edit.SetFocus();

}

OK,一切搞定!和SDK的子类化方法比起来,这是多么的容易!我们看看MFC背着我们到底做了什么!这里主要解决两个容易让初学者比较疑惑的问题:

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

2 CSuperEdit类为什么可以处理WM_CHAR消息?大家都知道,控制Windows窗口、控件、资源……都是通过它们的句柄来实现,如HHANDLEHWNDHDC都是句柄,它表现为一个32位长整形数据,存放于Windows中的特定区域,我们可以把它理解为指向我们想控制的窗口、控件、资源的索引,有了它,我们就可以控制我们想要控制的对象。这里你可以想到为什么多数API函数都有一个参数HWND hwnd了吧!

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

我们的C++变量m_edit要想控制IDC_EDIT1,也要通过它的句柄,但这又是如何实现的呢?您可能注意到了m_edit.SubclassDlgItem(IDC_EDIT1,this);一句,对了,这就是关键所在!在此处F9设置断点,F5之后,程序到达此处,F11跟入SubclassDlgItem函数: BOOLCWnd::SubclassDlgItem(UINT nID, CWnd* pParent)

{

ASSERT(pParent != NULL);

ASSERT(::IsWindow(pParent->m_hWnd)); // check for normal dialogcontrol 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 toits 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 thattype created
#ifdef _DEBUG
 else if (*lplpfn != oldWndProc) 
 { 
  TRACE(traceAppMsg, 0, "Error: Trying to use SubclassWindowwith 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 inpermanent 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 AFXWndProc 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 AFXAPIAfxGetAfxWndProc() { #ifdef _AFXDLL returnAfxGetModuleState()->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, UINTnFlags) 至于是如何流窜的、怎么到达的请参考《深入浅出 MFC [ 如果您的书是繁体电子版,请从 566 页读起 ] 终于,我们走出了 MFC 子类化的迷宫。 CSDN 烤鸡翅膀 2002-12-3[FINISH] PS :本人才疏学浅,如果说得有什么让人 笑得露齿 的地方,一定要通知我呦。


你可能感兴趣的:(SubClassWindow 的使用)