所谓窗口子类化:改变一个已经存在的窗口实例的性质:消息处理与其他实例属性。
通常在SDK中所谓的窗口子类化就是改变一个窗口函数(如GetWindowLong()和SetWindowLong())通过这两个函数来设置窗口的属性等;
而今天我们主要内容是介绍MFC中的子类化,它跟SDK中的子类化不太一样:
所有MFC窗口有相同的窗口函数,由该窗口函数根据窗口句柄查找窗口实例,在把消息映射到该窗口类(class)得消息处理函数上。为了利用MFC的消息映射机制,不宜改变窗口函数(名),MFC也把子类化封装在函数SubclassWindow()中。
#include "Subclass.h"
class CSubclassWnd : public CObject {
public:
DECLARE_DYNAMIC(CSubclassWnd);
CSubclassWnd();
~CSubclassWnd();
// Subclass a window. Hook(NULL) to unhook (automatic on WM_NCDESTROY)
virtual BOOL HookWindow(HWND hwnd);
virtual BOOL HookWindow(CWnd* pWnd) { return HookWindow(pWnd->GetSafeHwnd()); }
virtual BOOL IsHooked() { return m_hWnd!=NULL; }
friend LRESULT CALLBACK HookWndProc(HWND, UINT, WPARAM, LPARAM);
friend class CSubclassWndMap;
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
HWND m_hWnd; // the window hooked
WNDPROC m_pOldWndProc; // ..and original window proc
CSubclassWnd* m_pNext; // next in chain of hooks for this window
// Override this to handle messages in specific handlers
virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
virtual LRESULT Default(); // call this at the end of handler fns
};
#include "Subclass.cpp"
CSubclassWnd::CSubclassWnd()
{
m_pNext = NULL;
m_pOldWndProc = NULL;
m_hWnd = NULL;
}
CSubclassWnd::~CSubclassWnd()
{
if (m_hWnd)
HookWindow((HWND)NULL); // unhook window
}
//////////////////
// Hook a window.
// This installs a new window proc that directs messages to the CSubclassWnd.
// pWnd=NULL to remove.
//
BOOL CSubclassWnd::HookWindow(HWND hwnd)
{
ASSERT_VALID(this);
if (hwnd) {
// Hook the window
ASSERT(m_hWnd==NULL);
ASSERT(::IsWindow(hwnd));
theHookMap.Add(hwnd, this); // Add to map of hooks
} else if (m_hWnd) {
// Unhook the window
theHookMap.Remove(this); // Remove from map
m_pOldWndProc = NULL;
}
m_hWnd = hwnd;
return TRUE;
}
//////////////////
// Window proc-like virtual function which specific CSubclassWnds will
// override to do stuff. Default passes the message to the next hook;
// the last hook passes the message to the original window.
// You MUST call this at the end of your WindowProc if you want the real
// window to get the message. This is just like CWnd::WindowProc, except that
// a CSubclassWnd is not a window.
//
LRESULT CSubclassWnd::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
// ASSERT_VALID(this); // removed for speed
ASSERT(m_pOldWndProc);
return m_pNext ? m_pNext->WindowProc(msg, wp, lp) :
::CallWindowProc(m_pOldWndProc, m_hWnd, msg, wp, lp);
}
//////////////////
// Like calling base class WindowProc, but with no args, so individual
// message handlers can do the default thing. Like CWnd::Default
//
LRESULT CSubclassWnd::Default()
{
// MFC stores current MSG in thread state
MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
// Note: must explicitly call CSubclassWnd::WindowProc to avoid infinte
// recursion on virtual function
return CSubclassWnd::WindowProc(curMsg.message, curMsg.wParam, curMsg.lParam);
}
#ifdef _DEBUG
void CSubclassWnd::AssertValid() const
{
CObject::AssertValid();
ASSERT(m_hWnd==NULL || ::IsWindow(m_hWnd));
if (m_hWnd) {
for (CSubclassWnd* p = theHookMap.Lookup(m_hWnd); p; p=p->m_pNext) {
if (p==this)
break;
}
ASSERT(p); // should have found it!
}
}
void CSubclassWnd::Dump(CDumpContext& dc) const
{
CObject::Dump(dc);
}
#endif
//////////////////
// Subclassed window proc for message hooks. Replaces AfxWndProc (or whatever
// else was there before.)
//
LRESULT CALLBACK
HookWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
#ifdef _USRDLL
// If this is a DLL, need to set up MFC state
AFX_MANAGE_STATE(AfxGetStaticModuleState());
#endif
// Set up MFC message state just in case anyone wants it
// This is just like AfxCallWindowProc, but we can't use that because
// a CSubclassWnd is not a CWnd.
//
MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
MSG oldMsg = curMsg; // save for nesting
curMsg.hwnd = hwnd;
curMsg.message = msg;
curMsg.wParam = wp;
curMsg.lParam = lp;
// Get hook object for this window. Get from hook map
CSubclassWnd* pSubclassWnd = theHookMap.Lookup(hwnd);
ASSERT(pSubclassWnd);
LRESULT lr;
if (msg==WM_NCDESTROY) {
// Window is being destroyed: unhook all hooks (for this window)
// and pass msg to orginal window proc
//
WNDPROC wndproc = pSubclassWnd->m_pOldWndProc;
theHookMap.RemoveAll(hwnd);
lr = ::CallWindowProc(wndproc, hwnd, msg, wp, lp);
} else {
// pass to msg hook
lr = pSubclassWnd->WindowProc(msg, wp, lp);
}
curMsg = oldMsg; // pop state
return lr;
}
这边只是介绍SubclassWindow() 内部实现方式。
CSubclassWnd类的作用是不用派生新类便能在MFC中子类化窗口。
现在我们这边只提CSubclass Wnd 类的一个作用,其实CSubclass Wnd 也经常 被说成是万能类,可以用在很多的界面和控件上。
具体实例:
以美图秀秀的弹出对话框自定义皮肤为例:
创建一个自定义皮肤类:
class CDlgSkin : public CSubclassWnd
{
public:
自定义变量
CDlgSkin();
CDlgSkin(LPCTSTR skinfile)
{
CDlgSkin();
LoadSkin(skinfile);
}
Virtual ~CDlgSkin();
BOOL InitSkinWin(CString strPath,CWnd *wnd);
virtual LRESULT WindowProc(UINT msg, WPARAM wparam,LPARAM lparam);
……
……
}
//初始化
BOOL CDlgSkinWin::InstallSkin(CWnd *wnd)
{
if(!wnd || !m_bInit)
return FALSE;
//开始捕获此窗口的消息
// HookWindow((HWND)NULL);
int r = HookWindow(wnd);
return r;
}
//消息处理函数
LRESULT CDlgSkin::WindowProc(UINT msg, WPARAM wp/*wparam*/,LPARAM lp/*lparam*/)
{
if ( !IsWindow(m_hWnd) )
return 0;
if ( !m_bInit )
return Default();
switch ( msg )
{
case WM_SHOWWINDOW:
//call setwindowpos to force OnNcCalcSize when hWnd is a dialog
if ( wp )
SetWindowPos( m_hWnd, 0, 0, 0, 400, 400, SWP_NOSIZE|SWP_NOMOVE|SWP_FRAMECHANGED );
Default();
m_downHitTest = 0;
m_moveHitTest = 0;
return 0;
break;
//case WM_ERASEBKGND:
case WM_INITMENUPOPUP:
Default();
return 0;
case WM_SYSCOMMAND:
OnSysCommand( wp, lp );
return 0;
case WM_SETTEXT:
return OnSetText( wp, lp );
case WM_NCPAINT:
OnNcPaint( (HRGN)wp );
return 0;
case WM_NCCALCSIZE:
OnNcCalcSize( (BOOL)wp, (NCCALCSIZE_PARAMS *)lp );
return 0;
case WM_SIZE:
OnSize( wp, LOWORD(lp), HIWORD(lp) );
return 0;
case WM_NCACTIVATE:
return OnNcActivate( (BOOL)wp );
case WM_NCHITTEST:
return OnNcHitTest(CPoint(LOWORD(lp), HIWORD(lp)));
case WM_NCLBUTTONUP:
OnNcLButtonUp(wp, CPoint(LOWORD(lp), HIWORD(lp)));
return 0;
case WM_NCLBUTTONDOWN:
OnNcLButtonDown(wp, CPoint(LOWORD(lp), HIWORD(lp)));
return 0;
case WM_NCLBUTTONDBLCLK:
OnNcLButtonDblClk(wp, CPoint(LOWORD(lp), HIWORD(lp)));
return 0;
case WM_NCRBUTTONUP:
OnNcRButtonUp(wp, CPoint(LOWORD(lp), HIWORD(lp)));
return 0;
case WM_NCRBUTTONDOWN:
OnNcRButtonDown(wp, CPoint(LOWORD(lp), HIWORD(lp)));
return 0;
case WM_NCMOUSEMOVE:
OnNcMouseMove( wp,CPoint(LOWORD(lp), HIWORD(lp)));
return 0;
case WM_GETMINMAXINFO:
OnGetMinMaxInfo( (MINMAXINFO *)lp );
return 0;
case WM_WINDOWPOSCHANGING:
OnWindowPosChanging((WINDOWPOS *)lp);
return 0;
case WM_SIZING:
OnSizing( wp, (LPRECT)lp );
return 0;
case WM_ACTIVATE:
OnActivate( wp, CWnd::FromHandle((HWND)lp), 0 );
return 0;
case WM_COMMAND:
if ( !HandleSysCommand( wp, lp ) )
Default();
return 0;
default:
return Default();
}
}
简单的调用方法:
Class B :public A
{
……
}
A a;
B b;
HWND ha=a.GetSafeHwnd();
b.SubclassWindow(ha); #当然A 和B 不一定是继承关系。
注意:在被子类化的窗口销毁之前,必须执行窗口的反子类化:
b.UnSubclassWindow();
子类化跟超类化的区别:
超类化:首先获得一个已存在的窗口类,然后设置窗口类,最后注册该窗口类。
如:
WNDCLASSEX wc;
wc.cbSize=sizeof(wc); //Windows用来进行版本检查的,与窗口特征无关
GetClassInfoEx(hinst,”XXXXXX”,&wc);
// hinst—定义窗口类XXXXXX的模块的句柄,如为系统定义的窗口类(如:EDIT、BUTTON)则hinst=NULL.。
wc.lpszClassName = “YYYYYYY”;//必须改变窗口类的名字
wc.hbrBackGround = CreateSolidBrush(RGB(0,0.0));//改变背景刷
wc.lpfnWndProc = NewWndProc;//改变窗口函数
……
RegisterClassEx(&wc);// 注册新窗口类
//使用窗口类
……
::CreateWindow(_T(“YYYYYYYY”,……);
所以超类化只能改变自己创建的窗口特征,而不能用于windows创建的窗口;
而子类化是实例级别上的,只要能获得窗口的句柄就可以对其子类化,这也是子类化对超类化的一个优势。
总结
(0) 子类化修改窗口过程函数, 超类化修改窗口类(新的窗口类名)
(1) 子类化是在窗口实例级别上的,超类化是在窗口类(WNDCLASS)级别上的。
(2) 超类化可以完成比子类化更复杂的功能,在SDK范畴上,可以认为子类化是超类化的子集。
(3) 子类化只能改变窗口创建后的性质,对于窗口创建期间无能为力(无法截获ON_CREATE 事件),而超类化可以实现;超类化不能用于Windows创建的窗口,子类化可以。
from:http://blog.csdn.net/lin_angle/article/details/6178351