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的方法(或者其扩展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); }或者
BOOL CProg1Dlg::OnInitDialog() { CDialog::OnInitDialog(); m_edit.SubclassDlgItem(IDC_EDIT1, this); return TRUE; }通过这种方式,可以动态改变一个控件的消息处理流程,使得CsuperEdit中重载的消息可以被执行。如果不使用子类型化,则无法执行。
在自绘窗口的时候,子类化是MFC最常用的窗体技术之一。什么是子类化?窗口子类化就是创建一个新的窗口函数代替原来的窗口函数。
Subclass(子类化)是MFC中最常用的窗体技术之一。子类化完成两个工作:一是把窗体类对象attach到一个windows窗体实体中(即把一个窗体的hwnd赋给该类)。另外就是把该类对象的消息加入到消息路由中,使得该类可以捕获消息。
而通常我们会碰到DDX_Control、SubclassWindow、SubclassDlgItem等,不同的子类化方法。首先先看下面的代码:
void AFXAPI DDX_Control(CDataExchange* pDX, int nIDC, CWnd& rControl) { if ((rControl.m_hWnd == NULL) && (rControl.GetControlUnknown() == NULL)) // not subclassed yet { ASSERT(!pDX->m_bSaveAndValidate); pDX->PrepareCtrl(nIDC); HWND hWndCtrl; pDX->m_pDlgWnd->GetDlgItem(nIDC, &hWndCtrl); if ((hWndCtrl != NULL) && !rControl.SubclassWindow(hWndCtrl)) { ASSERT(FALSE); // possibly trying to subclass twice? AfxThrowNotSupportedException(); } #ifndef _AFX_NO_OCC_SUPPORT else { if (hWndCtrl == NULL) { if (pDX->m_pDlgWnd->GetOleControlSite(nIDC) != NULL) { rControl.AttachControlSite(pDX->m_pDlgWnd, nIDC); } } else { // If the control has reparented itself (e.g., invisible control), // make sure that the CWnd gets properly wired to its control site. if (pDX->m_pDlgWnd->m_hWnd != ::GetParent(rControl.m_hWnd)) rControl.AttachControlSite(pDX->m_pDlgWnd); } } #endif //!_AFX_NO_OCC_SUPPORT } }
我们发现 DDX_Control()函数中调用了SubclassWindow(),再看SubclassWindow()里写了什么:
// From VS Install PathVC98MFCSRCWINCORE.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()); if (*lplpfn == NULL) *lplpfn = oldWndProc; // the first control of that type created #ifdef _DEBUG else if (*lplpfn != oldWndProc) { ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)oldWndProc); } #endif return TRUE; }
很显然,SubclassWindow()中调用了Attach()函数和PreSubclassWindow()函数,由于SubclassWindow()函数是不可重载的,而PreSubclassWindow()函数是可重载的,所以我们经常重载PreSubclassWindow()函数,以致于 窗口被子类化之前进行其它的必要的子类化,看下它原来的声明:
CWnd::PreSubclassWindow
virtual void PreSubclassWindow( );
而SubclassWindow又与SubclassDlgItem有什么区别?前者用于一切具有HWND的窗体,后者只限定于对话框控件
用法:在OnInitDialog中调用SubclassDlgItem将派生类的控件对象与对话框中的基类控件相连接,则这个基类控件对象变成了派生控件对象