關于如何換膚、子類化的解決方案
作者:peterbing@sohu.com
對于應用程序的換膚及子類化。下面是我嘗試過一些方法,以在CAboutDlg中子類化其中的Button為例:
第一種:直接用現(xiàn)成的類
1、自己寫一個類class CButtonXP : public CButton{/*...*/}
用MessageMap處理感興趣的消息。
2、用CButtonXP代替CButton來聲明變量m_btn;
3、在void CAboutDlg:DoDataExchange(CDataExchange* pDX)中加上一句:
DDX_Control(pDX, IDB_BUTTON1, m_edit);
或者在 InitDialog() 中加上
m_btn.SubclassDlgItem(IDB_BUTTON1, this);
這兩種效果差不多的。
第二種:在 Hook 中使用現(xiàn)成的類
1、自己寫一個類 class CButtonXP : public CButton{/*...*/}
用 MessageMap 處理感興趣的消息。
2、使用 SetWindowsHookEx 安裝一個鉤子:
g_hWndProcHook = ::SetWindowsHookEx(WH_CALLWNDPROC,WndProcHook,NULL,::GetCurrentThreadId());
3、在 WndProcHook 中處理窗口創(chuàng)建和銷毀的消息:
LRESULT CALLBACK WndProcHook(int code, WPARAM wParam, LPARAM lParam){if (code == HC_ACTION){switch (((CWPSTRUCT*) lParam)->message){case WM_CREATE:BeginSubclassing(((CWPSTRUCT*) lParam)->hwnd);break;case WM_NCDESTROY:// TODO: clear subclass info.EndSubclassing(((CWPSTRUCT*) lParam)->hwnd);break;default:break;}}return CallNextHookEx(g_hWndProcHook, code, wParam, lParam);}4、在 BeginSubclassing 中用 GetClassName 得到類名,例如 "Button",然后用 CButtonXP 類進行子類化。
CButtonXP pButton = new CButtonXP;VERIFY(pButton ->SubclassWindow(hWnd));
第三種 在Hook中使用窗口過程
1、自己寫一個按鈕的窗口過程
WNDPROC oldProc;LRESULT CALLBACK ProcButton(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam){ASSERT(oldProc != 0);if (oldProc == 0) return TRUE;switch (uMsg){case WM_ERASEBKGND:break;//......default:break;}return CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);}2、同第二種
oldProc = (WNDPROC) GetWindowLong(hWnd, GWL_WNDPROC);SetWindowLong(hWnd, GWL_WNDPROC, (LONG) ProcButton);第四種:不用 Hook
hWnd=GetWindow(hDlg,GW_CHILD);hWnd=GetWindow(hWnd,GW_HWNDNEXT);
對每個子窗體進行子類化處理,處理過程同第二種與第三種。
第五種:如果是在XP下運行,可以使用manifest,也就是如下的一個XML文件<?xml version="1.0" encoding="UTF-8" standalone="yes"?><assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><assemblyIdentity name="Microsoft.Windows.XXXX"processorArchitecture="x86"version="5.1.0.0"type="win32"/><description>Windows Shell</description><dependency><dependentAssembly><assemblyIdentity type="win32"name="Microsoft.Windows.Common-Controls"version="6.0.0.0"processorArchitecture="x86"publicKeyToken="6595b64144ccf1df"language="*"/></dependentAssembly></dependency></assembly>把它存為應用程序名 .manifest,放到和應用程序?qū)哪夸浵?,或者把它作為資源類型為24的資源編譯進應用程序中。這樣程序在XP下就自動擁有了XP的風格。
http://community.csdn.net/Expert/To....asp?id=3103399
在文件對話框中我遇到一個問題,子類化過的 CStatic 的背景好像沒有重繪一樣,照理說應該由CStatic的父窗體負責背景的。
我在我的 CStaticNew 類中只重載了 OnPaint,里面只處理文字和圖標的繪制,背景的繪制留給父窗體完成。這樣的處理在 MessageBox 和自己的 AboutDlg 中都沒有問題,Static 控件的背景就是父窗口的背景,可是在 CFileDlg 中背景就沒有重繪了:
void CStaticNew::OnPaint(){CPaintDC dc(this); // device context for painting// TODO: Add your message handler code hereCRect rt;GetWindowRect(rt);// 繪制背景dc.SetBkMode(TRANSPARENT);// 繪制文字CFont *pfont, * pOldFont;pfont = GetFont();if (pfont)pOldFont = dc.SelectObject(pfont);CString szTitle;GetWindowText(szTitle);dc.DrawText(szTitle, CRect(0, 0, rt.Width(), rt.Height()), DT_LEFT | DT_WORDBREAK );if (pfont)dc.SelectObject(pOldFont);// 繪制圖標if ((GetStyle() & SS_ICON) != 0){dc.DrawIcon(0, 0, GetIcon());}// Do not call CStatic::OnPaint() for painting messages}類名的識別問題,到現(xiàn)在為止,我所使用的子類化方法都是基于GetClassName這個函數(shù)獲得窗口類名,再根據(jù)用spy++所得到的知識,如"#32770"表示對話框,"ToolbarWindow32"是工具欄,等等。但是窗口類名是可以在創(chuàng)建時任意指定的呀,而像CMainFrame的類名根本就不能夠確定,例如記事本主窗體的類名是"Notepad",寫字板主窗體的類名是"WordPadClass"。這樣的話,子類化如何去進行呢。真想知道windows是怎么做的,skinmagic又是怎么做的。目前主要就是這三個問題了。希望大家能展開討論,給出一個換膚的完善的解決方案。
CWndNew* pWnd = new CWndNew;pWnd->SubclassWindow(hWnd);
用完了,記得釋放處理:
pWnd->UnsubclassWindow();delete pWnd;
如果要進行功能擴充(繼承),就改寫那幾個虛函數(shù):
class CWndNew{public:CWndNew();virtual ~CWndNew();bool SubclassWindow(HWND hWnd);void UnsubclassWindow();protected: // virtualvirtual LRESULT WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);virtual void PresubclassWindow(){};virtual void PostunsubclassWindow(){};protected:LRESULT PrevWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);HWND m_hWnd;private:WNDPROC m_oldProc;static map關于子類化及其撤銷的順序問題,當用自己的類或者過程子類化窗口時,需要處理好與MFC類子類化的順序沖突。假設我們自己的類叫CWndNew,那么不管CWnd和CWndNew誰先子類化一個窗口,最終兩者協(xié)同工作的結(jié)果應該是該窗口的窗口過程還原到未子類化之前的狀態(tài)。首先,不要在HOOK過程中處理WM_NCDESTROY消息。理由:如果CWndNew比CWnd先子類化,由于HOOK的原因,你仍然會先處理WM_NCDESTROY,這時候如果你撤銷子類化,那么CWnd類就得不到機會清理。而如果你不撤銷子類化,CWnd沒有能力把被子類化的窗口還原到最初狀態(tài)。在HOOK過程中,不能通過調(diào)用SendMessage函數(shù)讓CWnd先行處理,然后你自己再處理,因為SendMessage后,消息又會被HOOK攔截。m_map;static LRESULT CALLBACK StaticWindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam);};//////////////////////////////////////////////////////////////////////// Construction/Destruction//////////////////////////////////////////////////////////////////////map CWndNew::m_map;CWndNew::CWndNew(){m_hWnd = NULL;}CWndNew::~CWndNew(){ASSERT(m_hWnd == NULL);}bool CWndNew::SubclassWindow(HWND hWnd){m_map[hWnd] = this;ASSERT(m_hWnd == NULL);m_hWnd = hWnd;//允許派生類在子類化之前做一些初始化.PresubclassWindow();m_oldProc = (WNDPROC) GetWindowLong(hWnd, GWL_WNDPROC);ASSERT(m_oldProc != 0);SetWindowLong(hWnd, GWL_WNDPROC, (LONG) StaticWindowProc);return true;}void CWndNew::UnsubclassWindow(){SetWindowLong(m_hWnd, GWL_WNDPROC, (LONG)m_oldProc);PostunsubclassWindow();m_map.erase(m_hWnd);m_hWnd = NULL;}LRESULT CALLBACK CWndNew::StaticWindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam){CWndNew* pWnd = m_map[hWnd];ASSERT(pWnd != NULL);return pWnd->WindowProc(uMsg, wParam, lParam);}LRESULT CWndNew::PrevWindowProc(UINT uMsg,WPARAM wParam,LPARAM lParam){return CallWindowProc(m_oldProc, m_hWnd, uMsg, wParam, lParam);}LRESULT CWndNew::WindowProc(UINT uMsg,WPARAM wParam,LPARAM lParam){return PrevWindowProc(uMsg, wParam, lParam);}
case WM_NCDESTROY:{LRESULT lret;WNDPROC wndproc;wndproc = (WNDPROC)GetWindowLong(m_hWnd, GWL_WNDPROC);if (wndproc == CWndNew::StaticWindowProc){HWND hWnd = m_hWnd;UnsubclassWindow();lret = CallWindowProc(m_oldProc,hWnd,uMsg,wParam,lParam);}else{lret = CallWindowProc(m_oldProc,m_hWnd,uMsg,wParam,lParam);if(wndproc == (WNDPROC)GetWindowLong(m_hWnd, GWL_WNDPROC))UnsubclassWindow();}delete this;return lret;}首先判斷該窗口的WNDPROC是否發(fā)生過變動,如果沒有的話是最好的,趕緊撤銷子類化,再把消息傳遞給之前窗口過程,然后功成身退,不問世事了。