MFC
(VC6.0
)的CWnd
及其子类
中,有如下三个函数
:
//从`VS`安装路径`VC98MFCIncludeAFXWIN.H`
class CWnd : public CCmdTarget
{
...
public:
...
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
virtual void PreSubclassWindow();
BOOL SubclassWindow(HWND hWnd);
...
} ;
让人很不容易区分
,不知道它们究竟干了些什么,在何时要重写哪个函数
?
想知道重写函数
?
根据有无虚
关键字,在排除了SubclassWindow
后,也就知道可重写预创建窗口
和预子类化窗口
函数.
先看看对这三个函数,MSDN
给的解释:
预创建窗口
:
在创建窗口
并附加
到本
指针所指的CWnd
对象前,由框架
调用.
预子类化窗口
:
在子类化窗口
前框架
调用它,用来允许其它必要
的子类化
.
要理解附加
,必须要知道一个C++
的CWnd
对象和窗口(窗口
)的区别:窗口
就是实在的窗口,而CWnd
就是MFC
用类对窗口
所C++
封装.
附加
,就是在CWnd
对象上附加窗口
.附加(附加
)完成后,CWnd
对象才和窗口
有了关联
.窗口的子类化
是指修改窗口过程
,而不是面向对象
中的继承子类
.
在创建窗口
前,框架
调用预创建窗口
,框架
在子类化
窗口前,调用预子类化窗口
.
来看看MFC
中的这三个函数
的实现!
//从`VSInstallPathVC98MFCSRCWINCORE.CPP`
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
//允许修改几个常见的`创建`参数
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = lpParam;
if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this);
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
...
return TRUE;
}
//对子窗口
BOOL CWnd::PreCreateWindow(CREATESTRUCT & cs)
{
if (cs.lpszClass == NULL)
{
//确保已注册默认`窗口`类
VERIFY(AfxDeferRegisterClass(AFX_WND_REG));
//未提供`窗口类`,使用子窗口默认值
ASSERT(cs.style & WS_CHILD);
cs.lpszClass = _afxWnd;
}
return TRUE;
}
CWnd::CreateEx
先设置cs(CREATESTRUCT)
,在调用真正的创建窗口函数::CreateWindowEx
前,并按引用
传递cs
参数,来调用了CWnd::PreCreateWindow
函数.
而CWnd
的预创建窗口
函数,也只是给cs.lpszClass
赋值而已.毕竟,创建窗口函数CWnd::CreateEx
的诸多参数
中,并没有哪个指定
了要创建窗口的窗口类
.
所以修改窗口的大小,风格,窗口所属的窗口类
等cs
成员变量时,要重写预创建窗口
函数.
//从`VSInstallPathVC98MFCSRCWINCORE.CPP`
BOOL CWnd::SubclassWindow(HWND hWnd)
{
if (!Attach(hWnd))
return FALSE;
//允许其他子类化
PreSubclassWindow();
//现在勾挂到`AFXWndProc`
WNDPROC* lplpfn = GetSuperWndProcAddr();
WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)AfxGetAfxWndProc());
ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());
if (*lplpfn == NULL)
*lplpfn = oldWndProc;
//创建该类型的第一个控件
#ifdef _DEBUG
else if (*lplpfn != oldWndProc)
{
...
::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)oldWndProc);
}
#endif
return TRUE;
}
void CWnd::PreSubclassWindow()
{
//无默认处理
}
CWnd::SubclassWindow
先调用Attach(hWnd)
函数,来关联CWnd
对象和窗柄
所指的窗口.接着再用::SetWindowLong
修改窗口过程
(子类化
)前,调用了PreSubclassWindow
.
CWnd::PreSubclassWindow
则是闲着.
在实现CWnd
中,除了CWnd::SubclassWindow
会调用预子类化窗口
外,还有一处
.上面所列CreateEx
函数的代码,其中调用
了一个AfxHookWindowCreate
函数,见下面代码:
//从`VSInstallPathVC98MFCSRCWINCORE.CPP`
BOOL CWnd::CreateEx(...)
{
//允许修改几个常见的`创建`参数
...
if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this);
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
...
return TRUE;
}
接着查看AfxHookWindowCreate
的代码:
//从`VSInstallPathVC98MFCSRCWINCORE.CPP`
void AFXAPI AfxHookWindowCreate(CWnd * pWnd)
{
...
if (pThreadState->m_hHookOldCbtFilter == NULL)
{
pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
if (pThreadState->m_hHookOldCbtFilter == NULL)
AfxThrowMemoryException();
}
...
}
起主要作用的::SetWindowsHookEx
函数,用来设置一个勾挂函数
(勾挂
函数)_AfxCbtFilterHook
,每当窗口
产生一个窗口
时,就会调用
你设置的勾挂
函数.
这样设置完成
后,回到CWnd::CreateEx
函数中,执行::CreateWindowEx
创建窗口,窗口
一产生,就会调用上面设置的勾挂
函数_AfxCbtFilterHook
.
而正是在_AfxCbtFilterHook
中,第二次调用预子类化窗口
函数.见如下代码
:
//来自`VSInstallPathVC98MFCSRCWINCORE.CPP/Window`创建勾挂
LRESULT CALLBACK
_AfxCbtFilterHook( int code, WPARAM wParam, LPARAM lParam)
{
...
...
//连接`窗柄`到`pWndInit...`
pWndInit->Attach(hWnd);
//允许首先其他子类化
pWndInit->PreSubclassWindow();
...
{
//用标准`AfxWndProc`,子类化窗口
oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc);
ASSERT(oldWndProc != NULL);
*pOldWndProc = oldWndProc;
}
...
}
也在调用SetWindowLong
函数,子类化
窗口,前调用了预子类化窗口
.