先说一下,我学到的方法,后面再说问题

原理:利用钩子截获窗口消息,再用子类化技术,使用自己的窗口类,实现换肤

方法:

1、在App类中安装钩子:

1
2
3
4
5
 	hWndHook = SetWindowsHookEx(WH_CALLWNDPROC, 
 		CallWndProc, 
 		NULL, 
 		GetCurrentThreadId());
CallWndProc是系统的回调函数,它可以截获窗口消息

2、CallWndProc的具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if(nCode < 0)
        return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);
 
    switch(nCode)
    {
	case HC_ACTION:
		switch(((CWPSTRUCT*)lParam)->message)
		{
		case WM_CREATE:
			BeginSubclassing(((CWPSTRUCT*)lParam)->hwnd);
			break;
 
		case WM_DESTROY:
			EndSubclassing(((CWPSTRUCT*)lParam)->hwnd);
			break;
		}
		break;
	}
 
	return CallNextHookEx(hWndHook, nCode, wParam, lParam);
}

别忘了在窗口销毁的时候进行取消子类化!BeginSubclassing和EndSubclassing都不是API,具体实现请参见文后的源码

3、最后在ExitInstance中UnhookWindowsHookEx就行了

运行的效果如图:

maindlg

可能大家觉得我基本上没怎么讲解这个代码。其实我的重点不是在这个方法上,因为这个方法比较有缺陷。

我也是从vckbase的一篇文章上学的,但是我还没看完就急着自己去做了,过了好久才发现,我遇到的问题,同那上面提到的完全一样!

问题一:重复子类化问题

比如在InitDlg中添加了用CButton类动态产生按钮的代码,就不能运行了。因为它已经Attach了一遍,后来又在BeginSubclassing中Attach一遍,所以会出去问题。那作者给的解决办法是,把换肤的代码弄到dll中。

但是这样,还是有问题

问题二:

MessageBox的按钮、CFileDialog的按钮都消失了!

msgbox opd 

子类化是成功了的,只是不能显示而已

问题三:

除了按钮外,其它控件也有问题,各种各样,不可预见的问题!

现在该说说,更好的办法了!

这个办法的思路就是,实现各个窗口类的时候,不是从CWnd、CButton直接派生来自绘控件,而是从头实现,自己写窗口过程,自己处理n多消息。这对MFC、Windows底层机制必须要特别熟悉才做得下去了!然后,后面的步骤同以上的方法是一致的

列几篇参考的VckBase的文章:

关于如何换肤、子类化的解决方案

为你的程序换个皮肤

测试源码下载:

HookSubclsChgSkin.rar

用窗口过程实现换肤的代码下载:

TestSkin