换肤程序的设计
目前换肤的软件很多,但是大都没有源代码。最近经过一段时间的学习和摸索,我终于实现了一段简单的程序。
现在我就举一个简单的例子并解释,教你如何利用这个技术给一个按钮控件换肤?举一反三,你就懂得如何对其
它控件及整个界面进行换肤。
以前我们一直利用重载一个类的办法来实现丰富多彩的个性化控件,如GuiToolkit、CJ60LIB,都是这样的工具,
使用起来还是要在程序中插入大量的语句,这样做既增加了程序的复杂性,又给程序的调试增加了难度。当然现
在也有像SkinMagic、EasySkin这样的工具,只要在你的程序里添加两行代码就可以实现对常用控件的换肤,但
这些工具都没有源代码,对于想学习开发的人来说实在没什么帮助。为了让大家都了解这项技术,我开发一个这
样的程序,并公布源程序,可供有兴趣的朋友参考。
首先,要给一个程序换肤,我们必须给程序挂钩消息。下面一段代码就实现了挂钩功能。
//设置
BOOL CSkin::SetDialog()
{
BOOL bReturn=FALSE;
// 安装窗口消息 HOOK
m_hPrevHook = SetWindowsHookEx(WH_CALLWNDPROC, HookProc, 0, GetCurrentThreadId());
bReturn = (BOOL) m_hPrevHook;
return bReturn;
}
这和SkinMagic一类工具的初始化函数差不多。当然在退出时也是要释放钩子的。
UnhookWindowsHookEx( m_hPrevHook ); //去除钩子消息
接下来,就是IRCallWndProc这个回调函数的编写,这是至关重要的一个环节,这个函数是对所要换肤的类对象进行了监视,
并改变其消息处理函数,实现换肤的目的。
// 窗口消息 HOOK 回调函数
LRESULT CALLBACK CSkin::HookProc(int iCode, WPARAM wParam, LPARAM lParam)
{
LPCWPSTRUCT pCwp = (LPCWPSTRUCT) lParam;
// 设置新建的窗口
if( (pCwp->message == WM_CREATE) && (iCode >= 0))
{
//判断是否为按钮
if( GetHwndType(pCwp->hwnd)=="Button")
{
WNDPROC WndProc;
//取得原处理过程
WndProc = (WNDPROC) GetWindowLong( pCwp->hwnd, GWL_WNDPROC );
if( CSkinButton::m_cWndProc != NULL && WndProc != CSkinButton::m_cWndProc )
{
return CallNextHookEx(m_hPrevHook, iCode, wParam, lParam);
}
if( WndProc != (WNDPROC) CSkinButton::DefWindowProc ) {
//换肤了,设为新的处理过程
WndProc = (WNDPROC) SetWindowLong( pCwp->hwnd, GWL_WNDPROC, (LONG) CSkinButton::DefWindowProc );
CSkinButton::m_cWndProc = WndProc; //保存原过程
}
}
}
return CallNextHookEx(m_hPrevHook, iCode, wParam, lParam); //消息轮询
}
//SkinButton.h文件
#ifndef _LIBRARY_HEADER
#define _LIBRARY_HEADER
//按钮的几种状态
#define STATUS_BUTTON_NORMAL 0x00000000
#define STATUS_BUTTON_HOVER 0x00000001
#define STATUS_BUTTON_DOWN 0x00000002
class CSkinButton
{
public:
CSkinButton() {}
~CSkinButton() {}
static UINT m_nStatus; //按钮状态
static WNDPROC m_cWndProc; //消息处理过程
//各种消息处理函数
static LRESULT DefWindowProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
static LRESULT OnLButtonDown( CWnd *pWnd, UINT nFlags, CPoint point ) ;
static LRESULT OnLButtonUp( CWnd *pWnd, UINT nFlags, CPoint point ) ;
static LRESULT OnLButtonDblClk( CWnd *pWnd, UINT nFlags, CPoint point ) ;
static LRESULT onMouseMove( CWnd *pWnd, UINT nFlags, CPoint point ) ;
static LRESULT OnPaint( CWnd *pWnd ) ;
};
#endif
//SkinButton.cpp文件
#include "stdafx.h"
#include "SkinButton.h"
//消息处理过程
LRESULT CSkinButton::DefWindowProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
CWnd *pWnd = NULL;
CPoint point;
pWnd = CWnd::FromHandle( hWnd );
switch( message )
{
case WM_PAINT: //重绘
return OnPaint( pWnd );
break;
case WM_LBUTTONDOWN: //左键按下
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
return OnLButtonDown( pWnd, 0, point );
break;
case WM_LBUTTONUP: //左键释放,别小看,这可是必须要的!
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
return OnLButtonUp( pWnd, 0, point );
break;
case WM_LBUTTONDBLCLK: //双击
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
return OnLButtonDblClk( pWnd, 0, point );
break;
case WM_MOUSEMOVE: //鼠标移入,有好戏的地方
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
return onMouseMove( pWnd, 0, point );
break;
default:
break;
}
//默认的消息处理过程
return CallWindowProc( m_cWndProc, hWnd, message, wParam, lParam );
}
//左键按下处理函数
LRESULT CSkinButton::OnLButtonDown( CWnd *pWnd, UINT nFlags, CPoint point )
{
m_nStatus = STATUS_BUTTON_DOWN; //正常状态
pWnd->Invalidate();
pWnd->UpdateWindow();
return TRUE;
}
//左键释放处理函数
LRESULT CSkinButton::OnLButtonUp( CWnd *pWnd, UINT nFlags, CPoint point )
{
if( m_nStatus != STATUS_BUTTON_NORMAL ) {
m_nStatus = STATUS_BUTTON_NORMAL;
pWnd->Invalidate();
pWnd->UpdateWindow();
//看此句,发送命令消息
SendMessage( pWnd->GetParent()->m_hWnd, WM_COMMAND,
pWnd->GetDlgCtrlID(), (LPARAM) (pWnd->m_hWnd) );
}
return TRUE;
}
//在此,不处理双击消息
LRESULT CSkinButton::OnLButtonDblClk( CWnd *pWnd, UINT nFlags, CPoint point )
{
return TRUE;
}
//鼠标移入
LRESULT CSkinButton::onMouseMove( CWnd *pWnd, UINT nFlags, CPoint point )
{
//取得按钮的范围
HRGN hRgn = CreateRectRgn( 0, 0, 0, 0 );
pWnd->GetWindowRgn( hRgn );
//判断鼠标是否在按钮之内
BOOL bIn = PtInRegion( hRgn, point.x, point.y );
if( bIn ) {
if( m_nStatus == STATUS_BUTTON_DOWN ) return TRUE;
if( m_nStatus != STATUS_BUTTON_HOVER ) {
m_nStatus = STATUS_BUTTON_HOVER;
pWnd->Invalidate();
pWnd->UpdateWindow();
pWnd->SetCapture(); //捕获鼠标
}
}
else {
if ( m_nStatus == STATUS_BUTTON_HOVER )
{
m_nStatus = STATUS_BUTTON_NORMAL;
pWnd->Invalidate();
pWnd->UpdateWindow();
ReleaseCapture(); //释放鼠标处理
}
}
DeleteObject( hRgn ); //释放资源
return TRUE;
}
//所有的换肤工作在此函数完成
LRESULT CSkinButton::OnPaint( CWnd *pWnd )
{
CPaintDC dc(pWnd);
CString cs; RECT rc;
CFont Font; //字体
CFont *pOldFont;
CBrush Brush; CBrush *pOldBrush;
CPen Pen; CPen *pOldPen;
POINT pt; pt.x = 2; pt.y = 2;
dc.SetBkMode( TRANSPARENT ); //透明文字
//设置字体
Font.CreateFont( 12, 0, 0, 0, FW_HEAVY, 0, 0, 0, GB2312_CHARSET, \
OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
VARIABLE_PITCH | FF_SWISS, "宋体" );
//选入字体
pOldFont = dc.SelectObject( &Font );
//为画皮肤做准备工作
if( m_nStatus == STATUS_BUTTON_DOWN ) {
Brush.CreateSolidBrush( RGB( 160, 160, 160 ) );
Pen.CreatePen( PS_SOLID, 1, RGB( 100, 100, 100 ) );
dc.SetTextColor( RGB( 50, 50, 250 ) ); //设置文字颜色
} else if( m_nStatus == STATUS_BUTTON_HOVER ) { //鼠标移入
Brush.CreateSolidBrush( RGB( 60, 60, 180 ) );
Pen.CreatePen( PS_SOLID, 1, RGB( 0, 0, 0 ) );
dc.SetTextColor( RGB( 250, 250, 50 ) );
} else if( m_nStatus == STATUS_BUTTON_NORMAL ) { //正常按钮
Brush.CreateSolidBrush( RGB( 240, 240, 240 ) );
Pen.CreatePen( PS_SOLID, 1, RGB( 120, 120, 120 ) );
dc.SetTextColor( RGB( 100, 200, 100 ) ); //文字颜色
}
pOldBrush = dc.SelectObject( &Brush );
pOldPen = dc.SelectObject( &Pen );
pWnd->GetClientRect( &rc );
//画皮肤了!
dc.RoundRect( &rc, pt );
//确定按钮的范围,可不能少
HRGN hRgn = CreateRectRgn( rc.left, rc.top, rc.right, rc.bottom );
pWnd->SetWindowRgn( hRgn, TRUE );
DeleteObject( hRgn );
//画文本
pWnd->GetWindowText( cs );
dc.DrawText( cs, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
//选取到按钮
dc.SelectObject( pOldFont );
dc.SelectObject( pOldBrush );
dc.SelectObject( pOldPen );
return TRUE;
}
程序结构还是很清楚的,现在来做一些简单的说明:m_nStatus用来标志按钮的状态,m_cWndProc用来保存系统的消息处理函数地址。
最后,就是如何在程序中使用的问题了。调用方法其实很简单,在CApp类的InitInstance()函数中加入这样的一句话:
SetDialog(); //就这一句就完成所有的工作了
这样就实现了对按钮的换肤,是不是很简单啊,连颜色对话框、字体对话框、消息框的按钮都换了皮肤,其它控件以此类推
目前换肤的软件很多,但是大都没有源代码。最近经过一段时间的学习和摸索,我终于实现了一段简单的程序。
现在我就举一个简单的例子并解释,教你如何利用这个技术给一个按钮控件换肤?举一反三,你就懂得如何对其
它控件及整个界面进行换肤。
以前我们一直利用重载一个类的办法来实现丰富多彩的个性化控件,如GuiToolkit、CJ60LIB,都是这样的工具,
使用起来还是要在程序中插入大量的语句,这样做既增加了程序的复杂性,又给程序的调试增加了难度。当然现
在也有像SkinMagic、EasySkin这样的工具,只要在你的程序里添加两行代码就可以实现对常用控件的换肤,但
这些工具都没有源代码,对于想学习开发的人来说实在没什么帮助。为了让大家都了解这项技术,我开发一个这
样的程序,并公布源程序,可供有兴趣的朋友参考。
首先,要给一个程序换肤,我们必须给程序挂钩消息。下面一段代码就实现了挂钩功能。
//设置
BOOL CSkin::SetDialog()
{
BOOL bReturn=FALSE;
// 安装窗口消息 HOOK
m_hPrevHook = SetWindowsHookEx(WH_CALLWNDPROC, HookProc, 0, GetCurrentThreadId());
bReturn = (BOOL) m_hPrevHook;
return bReturn;
}
这和SkinMagic一类工具的初始化函数差不多。当然在退出时也是要释放钩子的。
UnhookWindowsHookEx( m_hPrevHook ); //去除钩子消息
接下来,就是IRCallWndProc这个回调函数的编写,这是至关重要的一个环节,这个函数是对所要换肤的类对象进行了监视,
并改变其消息处理函数,实现换肤的目的。
// 窗口消息 HOOK 回调函数
LRESULT CALLBACK CSkin::HookProc(int iCode, WPARAM wParam, LPARAM lParam)
{
LPCWPSTRUCT pCwp = (LPCWPSTRUCT) lParam;
// 设置新建的窗口
if( (pCwp->message == WM_CREATE) && (iCode >= 0))
{
//判断是否为按钮
if( GetHwndType(pCwp->hwnd)=="Button")
{
WNDPROC WndProc;
//取得原处理过程
WndProc = (WNDPROC) GetWindowLong( pCwp->hwnd, GWL_WNDPROC );
if( CSkinButton::m_cWndProc != NULL && WndProc != CSkinButton::m_cWndProc )
{
return CallNextHookEx(m_hPrevHook, iCode, wParam, lParam);
}
if( WndProc != (WNDPROC) CSkinButton::DefWindowProc ) {
//换肤了,设为新的处理过程
WndProc = (WNDPROC) SetWindowLong( pCwp->hwnd, GWL_WNDPROC, (LONG) CSkinButton::DefWindowProc );
CSkinButton::m_cWndProc = WndProc; //保存原过程
}
}
}
return CallNextHookEx(m_hPrevHook, iCode, wParam, lParam); //消息轮询
}
//SkinButton.h文件
#ifndef _LIBRARY_HEADER
#define _LIBRARY_HEADER
//按钮的几种状态
#define STATUS_BUTTON_NORMAL 0x00000000
#define STATUS_BUTTON_HOVER 0x00000001
#define STATUS_BUTTON_DOWN 0x00000002
class CSkinButton
{
public:
CSkinButton() {}
~CSkinButton() {}
static UINT m_nStatus; //按钮状态
static WNDPROC m_cWndProc; //消息处理过程
//各种消息处理函数
static LRESULT DefWindowProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
static LRESULT OnLButtonDown( CWnd *pWnd, UINT nFlags, CPoint point ) ;
static LRESULT OnLButtonUp( CWnd *pWnd, UINT nFlags, CPoint point ) ;
static LRESULT OnLButtonDblClk( CWnd *pWnd, UINT nFlags, CPoint point ) ;
static LRESULT onMouseMove( CWnd *pWnd, UINT nFlags, CPoint point ) ;
static LRESULT OnPaint( CWnd *pWnd ) ;
};
#endif
//SkinButton.cpp文件
#include "stdafx.h"
#include "SkinButton.h"
//消息处理过程
LRESULT CSkinButton::DefWindowProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
CWnd *pWnd = NULL;
CPoint point;
pWnd = CWnd::FromHandle( hWnd );
switch( message )
{
case WM_PAINT: //重绘
return OnPaint( pWnd );
break;
case WM_LBUTTONDOWN: //左键按下
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
return OnLButtonDown( pWnd, 0, point );
break;
case WM_LBUTTONUP: //左键释放,别小看,这可是必须要的!
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
return OnLButtonUp( pWnd, 0, point );
break;
case WM_LBUTTONDBLCLK: //双击
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
return OnLButtonDblClk( pWnd, 0, point );
break;
case WM_MOUSEMOVE: //鼠标移入,有好戏的地方
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
return onMouseMove( pWnd, 0, point );
break;
default:
break;
}
//默认的消息处理过程
return CallWindowProc( m_cWndProc, hWnd, message, wParam, lParam );
}
//左键按下处理函数
LRESULT CSkinButton::OnLButtonDown( CWnd *pWnd, UINT nFlags, CPoint point )
{
m_nStatus = STATUS_BUTTON_DOWN; //正常状态
pWnd->Invalidate();
pWnd->UpdateWindow();
return TRUE;
}
//左键释放处理函数
LRESULT CSkinButton::OnLButtonUp( CWnd *pWnd, UINT nFlags, CPoint point )
{
if( m_nStatus != STATUS_BUTTON_NORMAL ) {
m_nStatus = STATUS_BUTTON_NORMAL;
pWnd->Invalidate();
pWnd->UpdateWindow();
//看此句,发送命令消息
SendMessage( pWnd->GetParent()->m_hWnd, WM_COMMAND,
pWnd->GetDlgCtrlID(), (LPARAM) (pWnd->m_hWnd) );
}
return TRUE;
}
//在此,不处理双击消息
LRESULT CSkinButton::OnLButtonDblClk( CWnd *pWnd, UINT nFlags, CPoint point )
{
return TRUE;
}
//鼠标移入
LRESULT CSkinButton::onMouseMove( CWnd *pWnd, UINT nFlags, CPoint point )
{
//取得按钮的范围
HRGN hRgn = CreateRectRgn( 0, 0, 0, 0 );
pWnd->GetWindowRgn( hRgn );
//判断鼠标是否在按钮之内
BOOL bIn = PtInRegion( hRgn, point.x, point.y );
if( bIn ) {
if( m_nStatus == STATUS_BUTTON_DOWN ) return TRUE;
if( m_nStatus != STATUS_BUTTON_HOVER ) {
m_nStatus = STATUS_BUTTON_HOVER;
pWnd->Invalidate();
pWnd->UpdateWindow();
pWnd->SetCapture(); //捕获鼠标
}
}
else {
if ( m_nStatus == STATUS_BUTTON_HOVER )
{
m_nStatus = STATUS_BUTTON_NORMAL;
pWnd->Invalidate();
pWnd->UpdateWindow();
ReleaseCapture(); //释放鼠标处理
}
}
DeleteObject( hRgn ); //释放资源
return TRUE;
}
//所有的换肤工作在此函数完成
LRESULT CSkinButton::OnPaint( CWnd *pWnd )
{
CPaintDC dc(pWnd);
CString cs; RECT rc;
CFont Font; //字体
CFont *pOldFont;
CBrush Brush; CBrush *pOldBrush;
CPen Pen; CPen *pOldPen;
POINT pt; pt.x = 2; pt.y = 2;
dc.SetBkMode( TRANSPARENT ); //透明文字
//设置字体
Font.CreateFont( 12, 0, 0, 0, FW_HEAVY, 0, 0, 0, GB2312_CHARSET, \
OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
VARIABLE_PITCH | FF_SWISS, "宋体" );
//选入字体
pOldFont = dc.SelectObject( &Font );
//为画皮肤做准备工作
if( m_nStatus == STATUS_BUTTON_DOWN ) {
Brush.CreateSolidBrush( RGB( 160, 160, 160 ) );
Pen.CreatePen( PS_SOLID, 1, RGB( 100, 100, 100 ) );
dc.SetTextColor( RGB( 50, 50, 250 ) ); //设置文字颜色
} else if( m_nStatus == STATUS_BUTTON_HOVER ) { //鼠标移入
Brush.CreateSolidBrush( RGB( 60, 60, 180 ) );
Pen.CreatePen( PS_SOLID, 1, RGB( 0, 0, 0 ) );
dc.SetTextColor( RGB( 250, 250, 50 ) );
} else if( m_nStatus == STATUS_BUTTON_NORMAL ) { //正常按钮
Brush.CreateSolidBrush( RGB( 240, 240, 240 ) );
Pen.CreatePen( PS_SOLID, 1, RGB( 120, 120, 120 ) );
dc.SetTextColor( RGB( 100, 200, 100 ) ); //文字颜色
}
pOldBrush = dc.SelectObject( &Brush );
pOldPen = dc.SelectObject( &Pen );
pWnd->GetClientRect( &rc );
//画皮肤了!
dc.RoundRect( &rc, pt );
//确定按钮的范围,可不能少
HRGN hRgn = CreateRectRgn( rc.left, rc.top, rc.right, rc.bottom );
pWnd->SetWindowRgn( hRgn, TRUE );
DeleteObject( hRgn );
//画文本
pWnd->GetWindowText( cs );
dc.DrawText( cs, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
//选取到按钮
dc.SelectObject( pOldFont );
dc.SelectObject( pOldBrush );
dc.SelectObject( pOldPen );
return TRUE;
}
程序结构还是很清楚的,现在来做一些简单的说明:m_nStatus用来标志按钮的状态,m_cWndProc用来保存系统的消息处理函数地址。
最后,就是如何在程序中使用的问题了。调用方法其实很简单,在CApp类的InitInstance()函数中加入这样的一句话:
SetDialog(); //就这一句就完成所有的工作了
这样就实现了对按钮的换肤,是不是很简单啊,连颜色对话框、字体对话框、消息框的按钮都换了皮肤,其它控件以此类推
回复Comments
作者:
{commentrecontent}