模板类 CWindowImplBaseT 提供一个数据成员 WNDPROC m_pfnSuperWindowProc 并且初始化为 ::DefWindowProc 。然而在窗口超类化处理时它存储了已注册窗口类的窗口过程,在窗口子类化时它保存窗口实例句柄原有的窗口过程,所有设置了 bHandled = false 的消息都由该数据成员处理。
template <class TBase = CWindow, class TWinTraits = CControlWinTraits>
class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot< TBase >
{
public:
WNDPROC m_pfnSuperWindowProc;
CWindowImplBaseT() : m_pfnSuperWindowProc(::DefWindowProc)
{}
···
}
子类化 Subclass
子类化是在窗口实例创建之后,把窗口实例的窗口过程用另一个用户定义窗口类的窗口过程函数替换,从而改变其窗口行为。而窗口实例的原窗口过程则保存在数据成员 m_pfnSuperWindowProc 中。
template <class TBase, class TWinTraits>
BOOL CWindowImplBaseT< TBase, TWinTraits >::SubclassWindow(HWND hWnd)
{
BOOL result;
ATLASSUME(m_hWnd == NULL);
ATLASSERT(::IsWindow(hWnd));
// Allocate the thunk structure here, where we can fail gracefully.
result = m_thunk.Init(GetWindowProc(), this);
if (result == FALSE)
{
return FALSE;
}
WNDPROC pProc = m_thunk.GetWNDPROC();
WNDPROC pfnWndProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);
if(pfnWndProc == NULL)
return FALSE;
m_pfnSuperWindowProc = pfnWndProc;
m_hWnd = hWnd;
return TRUE;
}
窗口子类化函数接受一个有效窗口实例句柄 hWnd ,此窗口句柄必须是一个根据某个窗口类创建的窗口(如果是对话框模板资源上的控件窗口,则必然满足此要求)。
1 )把此窗口句柄 hWnd 对应的窗口类(超类窗口 Superclass )的窗口过程设置为调用者窗口类(子类窗口 Subclass )的窗口过程函数。
2 ) hWnd 原有窗口过程函数由数据成员 m_pfnSuperWindowProc 保存,便于把未处理的消息传递到原有窗口过程处理。
3 )调用者窗口类与窗口句柄 hWnd 挂接 m_hWnd = hWnd。
窗口子类化的行为在程序运行期有效。当窗口接收到 WM_NCDESTROY 消息即将被销毁时,如果该窗口存在窗口子类化的行为,则对此窗口类进行去子类化处理,即恢复 hWnd 对应的原窗口类的原窗口过程,并且窗口标识设置 WINSTATE_DESTROYED 位。具体实现如下:
template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
···
// pass to the message map to process
LRESULT lRes;
BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
// restore saved value for the current message
ATLASSERT(pThis->m_pCurrentMsg == &msg);
// do the default processing if message was not handled
if(!bRet)
{
if(uMsg != WM_NCDESTROY)
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
else
{
// unsubclass, if needed
LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc)
::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc);
// mark window as destryed
pThis->m_dwState |= WINSTATE_DESTROYED;
}
}
···
return lRes;
}
首先将消息传递到消息映射函数处理:
ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
此函数是 CMessageLoop 接口类的唯一虚拟函数,完成具体的消息处理任务,在具体窗口类中由 BEGIN_MSG_MAP ( wndclass ) /END_MSG_MAP ()宏定义实现:
#define BEGIN_MSG_MAP(theClass) /
public: /
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) /
{ /
BOOL bHandled = TRUE; /
(hWnd); /
(uMsg); /
(wParam); /
(lParam); /
(lResult); /
(bHandled); /
switch(dwMsgMapID) /
{ /
case 0:
#define ALT_MSG_MAP(msgMapID) /
break; /
case msgMapID:
#define MESSAGE_HANDLER(msg, func) /
if(uMsg == msg) /
{ /
bHandled = TRUE; /
lResult = func(uMsg, wParam, lParam, bHandled); /
if(bHandled) /
return TRUE; /
}
#define END_MSG_MAP() /
break; /
default: /
ATLTRACE(ATL::atlTraceWindowing, 0, _T("Invalid message map ID (%i)/n"), dwMsgMapID); /
ATLASSERT(FALSE); /
break; /
} /
return FALSE; /
}
可见所有的消息映射处理函数都在 ProcessWindowMessage ()中被按照相应的消息来调用,处理结果 LRESULT 由该函数的参数返回。此函数中还定义了一个极其重要的变量 bHandled ,它决定了此函数的返回值。该变量作为参数传递给消息处理函数,因此可以在消息映射函数中被设置。在窗口过程函数中若 ProcessWindowMessage() 返回 TRUE 则此消息处理到此结束,否则调用窗口类默认窗口函数:
LRESULT DefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
#ifdef STRICT
return ::CallWindowProc(m_pfnSuperWindowProc, m_hWnd, uMsg, wParam, lParam);
#else
return ::CallWindowProc((FARPROC)m_pfnSuperWindowProc, m_hWnd, uMsg, wParam, lParam);
#endif
}
仅仅是调用数据成员 m_pfnSuperWindowProc 保存的窗口过程函数进行处理。前面提到在窗口超类化处理时它存储了已注册窗口类的窗口过程,在窗口子类化时它保存窗口实例句柄原有的窗口过程,所有 bHandled = false 的消息都由该数据成员处理,也就是说超类化时路由到已注册窗口类窗口过程继续处理,子类化时路由到窗口实例句柄原有的窗口过程继续处理,否则,调用默认窗口过程函数处理。
因此在消息映射函数中可以通过设置 bHandled 来决定是否需要继续对此消息进行处理。然后窗口销毁时进行去子类化。当然如果需要在窗口销毁前去子类化,则可直接调用如下函数去子类化:
template <class TBase, class TWinTraits>
HWND CWindowImplBaseT< TBase, TWinTraits >::UnsubclassWindow(BOOL bForce )
{
ATLASSUME(m_hWnd != NULL);
WNDPROC pOurProc = m_thunk.GetWNDPROC();
WNDPROC pActiveProc = (WNDPROC)::GetWindowLongPtr(m_hWnd, GWLP_WNDPROC);
HWND hWnd = NULL;
if (bForce || pOurProc == pActiveProc)
{
if(!::SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, (LONG_PTR)m_pfnSuperWindowProc))
return NULL;
m_pfnSuperWindowProc = ::DefWindowProc;
hWnd = m_hWnd;
m_hWnd = NULL;
}
return hWnd;
}
首先判断窗口实例句柄现有的窗口过程是否是当前窗口类的窗口过程,即是否存在子类化。若是并且强制要求去子类化,则恢复窗口实例句柄原有的窗口过程,并设置 m_pfnSuperWindowProc = ::DefWindowProc 存储默认窗口过程。最后窗口类与窗口实例句柄分离 m_hWnd = NULL。返回窗口实例句柄。
超类化 superclassing
超类化是根据已注册的窗口类来创建一个新的窗口类,这一过程是一个复制的过程,新建窗口类复制了已注册窗口类的所有内容,但是把窗口类名和窗口过程改为自己预定义的窗口类名和窗口过程。 然后注册新建窗口类, 同时设置数据成员 m_pfnSuperWindowProc 为已注册窗口类的窗口过程。
#define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) /
static ATL::CWndClassInfo& GetWndClassInfo() /
{ /
static ATL::CWndClassInfo wc = /
{ /
{ sizeof(WNDCLASSEX), 0, StartWindowProc, /
0, 0, NULL, NULL, NULL, NULL, NULL, WndClassName, NULL }, /
OrigWndClassName, NULL, NULL, TRUE, 0, _T("") /
}; /
return wc; /
}
超类化宏设置了窗口类信息结构体的成员变量 OrigWndClassName 为一个已注册窗口类的窗口类名。
CWindowImpl :: Create()函数中注册窗口类然后创建窗口实例。
ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);
注册窗口类时传递了 m_pfnSuperWindowProc 作为参数,最终调用的是如下模板函数
template <class T>
ATLINLINE ATOM AtlModuleRegisterWndClassInfoT(_ATL_BASE_MODULE* pBaseModule, _ATL_WIN_MODULE* pWinModule, typename T::_ATL_WNDCLASSINFO* p, WNDPROC* pProc, T)
{
···
if(p->m_atom == 0)
{
if (p->m_lpszOrigName != NULL)
{
ATLASSERT(pProc != NULL);
T::PCXSTR lpsz = p->m_wc.lpszClassName;
WNDPROC proc = p->m_wc.lpfnWndProc;
T::WNDCLASSEX wc;
wc.cbSize = sizeof(T::WNDCLASSEX);
// Try global class
if(!T::GetClassInfoEx(NULL, p->m_lpszOrigName, &wc))
{
// try process local
if(!T::GetClassInfoEx(pBaseModule->m_hInst, p->m_lpszOrigName, &wc))
{
ATLTRACE(atlTraceWindowing, 0, "ERROR : Could not obtain Window Class information for %s/n", p->m_lpszOrigName);
return 0;
}
}
p->m_wc = wc;
p->pWndProc = p->m_wc.lpfnWndProc;
p->m_wc.lpszClassName = lpsz;
p->m_wc.lpfnWndProc = proc;
}
···
T::WNDCLASSEX wcTemp;
wcTemp = p->m_wc;
p->m_atom = static_cast<ATOM>(T::GetClassInfoEx(p->m_wc.hInstance, p->m_wc.lpszClassName, &wcTemp));
if (p->m_atom == 0)
{
p->m_atom = T::RegisterClassEx(pWinModule, &p->m_wc);
}
}
if (p->m_lpszOrigName != NULL)
{
ATLASSERT(pProc != NULL);
ATLASSERT(p->pWndProc != NULL);
*pProc = p->pWndProc;
}
return p->m_atom;
}
函数判断如果有窗口超类化(p->m_lpszOrigName != NULL)则获取已注册窗口类的信息 wc ,然后
p->m_wc = wc;
p->pWndProc = p->m_wc.lpfnWndProc;
p->m_wc.lpszClassName = lpsz;
p->m_wc.lpfnWndProc = proc;
以上语句的作用是:
令新建窗口类的 pWndProc 变量保存已注册窗口类窗口函数地址。
新建窗口类仅仅保留窗口类名和窗口过程函数不变,其余的都用子类窗口类相关信息替换。
注册新建窗口类,令新建窗口类 m_pfnSuperWindowProc 成员数据保存已注册窗口类的窗口过程函数地址。返回注册结果。