📄 skindialog.cpp
字号:
#include "stdafx.h"
#include "SkinDialog.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
static HHOOK hSkinDialogHook=NULL;
CSkinDialog *g_pGlobalSkinDialogObj=NULL;
//------------------------------------------------------------------------
// 枚举子窗口所用的回调函数,用来让CSkinButtonSubClass当中的所有Button
// 顺便看看有没有Icon类型的Static,如果有,那么把它和背景融合一下.
BOOL CALLBACK EnumSkinWindowProc(HWND hwnd,LPARAM lParam)
{
TCHAR szBuffer[512]={NULL};
CSkinButton *pSkinButton=(CSkinButton*)lParam;
GetClassName(hwnd,szBuffer,512);
ASSERT(g_pGlobalSkinDialogObj != NULL); // 必须确保全局的CSkinDialog被赋值.
if (lstrcmpi(szBuffer,"Button") == 0)
{
DWORD dwOldStyle=GetWindowLong(hwnd,GWL_STYLE);
if (((dwOldStyle & BS_RADIOBUTTON) != BS_RADIOBUTTON) && ((dwOldStyle & BS_AUTORADIOBUTTON) != BS_AUTORADIOBUTTON)
&& ((dwOldStyle & BS_CHECKBOX) != BS_CHECKBOX))
{
if (GetProp(hwnd,"SkinButtonHandled") == NULL)
{
dwOldStyle |= BS_OWNERDRAW; // 增加BS_OWNERDRAW属性以便获得WM_DRAWITEM消息.
SetWindowLong(hwnd,GWL_STYLE,dwOldStyle);
pSkinButton->SubClassWindow(hwnd); // CSkinButton 类只对真正的按钮感兴趣.
}
}
}
else if (lstrcmpi(szBuffer,"Static") == 0)
{
ICONINFO IconInfo={NULL};
HICON hIcon=(HICON)SendMessage(hwnd,STM_GETICON,0,0);
if (hIcon != NULL)
{
// 增加SS_BITMAP标记,同时去掉SS_ICON标记。
DWORD dwOldStyle=GetWindowLong(hwnd,GWL_STYLE);
dwOldStyle &= ~SS_ICON;
dwOldStyle |= SS_BITMAP;
SetWindowLong(hwnd,GWL_STYLE,dwOldStyle);
// 由于图标的颜色位数和颜色表的关系,渐进色的背景无法正确的和Icon进行融合。
// (通过构造刷子实现的结果是背景色被强制转换成最接近的颜色)
//
// 这里采用的方法是把Icon转换成增加了背景后的Bitmap然后显示。
//
// 好麻烦的东西。:)
CRect rect;
CBitmap Bitmap;
CDC BitmapDC;
CDC ClientDC;
CBitmap ClientBitmap;
CDC PrevDC;
CRect rect2;
CBrush Brush;
HWND hwndParent=GetParent(hwnd); // 因为这是在EuumChildWindows中调用的,所以hwnd一定是一个子窗口的hWnd
CBitmap *pOldClientBitmap=NULL;
CBitmap *pOldBitmap=NULL;
PrevDC.Attach(GetDC(hwnd));
GetClientRect(GetParent(hwnd),&rect);
GetWindowRect(hwnd,&rect2);
::ScreenToClient(GetParent(hwnd), (LPPOINT)&rect2);
::ScreenToClient(GetParent(hwnd), ((LPPOINT)&rect2)+1);
ClientDC.CreateCompatibleDC(&PrevDC);
BitmapDC.CreateCompatibleDC(&PrevDC);
ClientBitmap.CreateCompatibleBitmap(&PrevDC,rect.Width(),rect.Height()); // 整个窗体背景的那个Bitmap
Bitmap.CreateCompatibleBitmap(&PrevDC,rect2.Width(),rect2.Height()); // 用来画图标的那个小的Bitmap
pOldClientBitmap=ClientDC.SelectObject(&ClientBitmap); // 选中对象
pOldBitmap=BitmapDC.SelectObject(&Bitmap);
g_pGlobalSkinDialogObj->DoGradientFill(&ClientDC,rect); // 填充渐进色背景
// 然后从背景中把这个空间所在的位置对应的图象“抠”出来
BitmapDC.BitBlt(0,0,rect2.Width(),rect2.Height(),&ClientDC,rect2.left,rect2.top,SRCCOPY);
// 画原来的图标
VERIFY(DrawIcon(BitmapDC,0,0,hIcon));
// 更新控件的图象为位图
SendMessage(hwnd,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)(HBITMAP)Bitmap);
ClientDC.SelectObject(pOldClientBitmap);
BitmapDC.SelectObject(pOldBitmap);
PrevDC.DeleteDC();
Bitmap.Detach(); // 如果让析构函数销毁这个位图的话,我们就什么也得不到了。
}
}
// 继续枚举当前窗口的子窗口,把按钮类型的Style增加上OwnerDraw标记。
// 通常都是没什么用的,因为很多子窗口还没开始构造出来呢. ^_^
EnumChildWindows(hwnd,EnumSkinWindowProc,lParam);
return TRUE;
}
//----------------------------------------------------------------------------------
// CSkinDialog Subclass后的WndProc过程
LRESULT CALLBACK SkinWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
WNDPROC OldWndProc=NULL;
OldWndProc=(WNDPROC)GetProp(hwnd,"SkinWindowOldWndProc");
ASSERT(OldWndProc != NULL);
if (uMsg == WM_DRAWITEM)
{
// 将收到的WM_DRAWITEM发送到对应的控件中。做到真正的“属主画”->“自画”
LPDRAWITEMSTRUCT pDrawInfo=(LPDRAWITEMSTRUCT)lParam;
if ((pDrawInfo->CtlType = ODT_BUTTON))
{
// 如果这个按钮被CSkinButton Subclass或这个按钮是由CSkinButton控制的话,我们才会把消息转发.
// 否则在打开公用对话框中会被重复调用的.
// 默认情况下,WM_DRAWITEM只会被发送给窗体,而不是对应的控件.
if ((GetProp(pDrawInfo->hwndItem,"SkinButtonObj") != NULL) // 这是通过CSkinButton subclass后的按钮.
|| (GetProp(pDrawInfo->hwndItem,"SkinButtonHandled") != NULL)) // 这是使用MFC方法调用的CSkinButton
{
return SendMessage(pDrawInfo->hwndItem,uMsg,wParam,lParam);
}
}
else
{
return CallWindowProc(OldWndProc,hwnd,uMsg,wParam,lParam);
}
}
else if (uMsg == WM_DESTROY)
{
SetWindowLong(hwnd,GWL_WNDPROC,(LONG)OldWndProc);
RemoveProp(hwnd,"SkinWindowOldWndProc");
return CallWindowProc(OldWndProc,hwnd,uMsg,wParam,lParam);
}
else if (uMsg == WM_ERASEBKGND)
{
LONG nWindowStyle=GetWindowLong(hwnd,GWL_STYLE);
if ((((nWindowStyle & WS_DLGFRAME) == WS_DLGFRAME) // 只针对对话框类型的窗体或者标记为使用CSKinDialog的
|| (GetProp(hwnd,"SkinWindowHandled") != NULL))
&& ((nWindowStyle & WS_MINIMIZEBOX) == 0))
{ // 否则,在使用BCG的时候会把弹出的菜单也绘制上的.
TCHAR szBuffer[128];
GetWindowText(hwnd,szBuffer,128);
TRACE("SkinDialog receive WM_ERASEBKGND message for window %s.\n",szBuffer);
CDC dc;
CRect rect;
dc.Attach((HDC)wParam);
::GetClientRect(hwnd,&rect);
// 我们自己画一遍
g_pGlobalSkinDialogObj->DoGradientFill(&dc,rect);
dc.Detach();
// 不交给以前的代码画,默认的MFC的代码会把我们的渐变背景擦掉的。
// CallWindowProc(OldWndProc,hwnd,uMsg,wParam,lParam);
// 我们已经自己绘制了,不劳系统费心了 :)。
return TRUE;
}
}
else if (uMsg == WM_CTLCOLORSTATIC)
{
TCHAR szBuffer[512]={NULL};
GetClassName((HWND)lParam,szBuffer,512);
if (lstrcmpi(szBuffer,"Edit") == 0) return (long)(HBRUSH)GetStockObject(WHITE_BRUSH);
GetWindowText((HWND)lParam,szBuffer,512);
if (lstrcmpi(szBuffer,"") != 0) // 对于标题不为空的Static,我们把背景去掉.
{ // TODO: 对于Frame,这样会把后面的线条漏出来,下一版想办法解决.
if ((g_pGlobalSkinDialogObj == NULL)
|| ((g_pGlobalSkinDialogObj->m_dwFlags & SKINDIALOG_FLAGS_FORCE_STATIC_BACKGROUND_BRUSH) != SKINDIALOG_FLAGS_FORCE_STATIC_BACKGROUND_BRUSH))
{
SetBkMode((HDC)wParam,TRANSPARENT);
HBRUSH hbrush=CreateSolidBrush(RGB(100,100,255));
return (long)hbrush;
}
else
{
// 原来处理图标背景的方法,创建一个特殊的刷子来绘制背景,但最后发现不行,颜色数的问题。
// 没想到的是,居然可以用这种方法对付那些"顽固"的静态标签,效果当然还是差一点了.
// 考虑一下要不要也换成位图.
CDC DC;
CRect rect;
CBitmap Bitmap;
CDC BitmapDC;
CDC ClientDC;
CBitmap ClientBitmap;
CDC PrevDC;
CRect rect2;
CBrush Brush;
HBRUSH hbr;
CBitmap *pOldClientBitmap=NULL;
CBitmap *pOldBitmap=NULL;
PrevDC.Attach(GetDC(hwnd));
DC.Attach((HDC)wParam);
DC.SetBkMode(TRANSPARENT);
GetClientRect(hwnd,&rect);
GetWindowRect((HWND)lParam,&rect2);
::ScreenToClient(hwnd, (LPPOINT)&rect2);
::ScreenToClient(hwnd, ((LPPOINT)&rect2)+1);
ClientDC.CreateCompatibleDC(&PrevDC);
BitmapDC.CreateCompatibleDC(&PrevDC);
ClientBitmap.CreateCompatibleBitmap(&PrevDC,rect.Width(),rect.Height());
Bitmap.CreateCompatibleBitmap(&PrevDC,rect2.Width(),rect2.Height());
pOldClientBitmap=ClientDC.SelectObject(&ClientBitmap);
pOldBitmap=BitmapDC.SelectObject(&Bitmap);
g_pGlobalSkinDialogObj->DoGradientFill(&ClientDC,rect);
BitmapDC.BitBlt(0,0,rect2.Width(),rect2.Height(),&ClientDC,rect2.left,rect2.top,SRCCOPY); // Copy the content to the Icon DC.
// NOTE: 由于有些图标是16色的,而背景是32位真彩色的,所以有的图标背景会出现不完全符合的情况。
// TODO: 通过转换图标到32位真彩色来解决这个问题. -- FAILED 没有找到对应的转换函数. :(
Brush.CreatePatternBrush(&Bitmap);
ClientDC.SelectObject(pOldClientBitmap);
BitmapDC.SelectObject(pOldBitmap);
PrevDC.DeleteDC(); // 这个是我们通过GetDC生成的一个新的,我们必须负责销毁。
DC.Detach(); // 这个DC不是我们构造的,所以我们不能销毁它。
hbr=Brush;
Brush.Detach();
return (long)(HBRUSH)hbr;
}
}
}
return CallWindowProc(OldWndProc,hwnd,uMsg,wParam,lParam);
}
//----------------------------------------------------------------------------
// Hook 所用的回调函数
LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if(nCode < 0)
{
return CallNextHookEx(hSkinDialogHook, nCode, wParam, lParam);
}
switch(nCode)
{
case HCBT_ACTIVATE:
HWND hwnd = (HWND)wParam;
CSkinDialog SkinDialog;
SkinDialog.SubClassDialog(hwnd); // 让SubClassDialog去判断是不是重复Hook了.
return 0;
}
// 调用下一个Hook
return CallNextHookEx(hSkinDialogHook, nCode, wParam, lParam);
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CSkinDialog::CSkinDialog()
{
}
CSkinDialog::~CSkinDialog()
{
}
BOOL CSkinDialog::SubClassDialog(HWND hWnd)
{
if (g_pGlobalSkinDialogObj == NULL)
{
g_pGlobalSkinDialogObj=this;
}
if (hSkinDialogHook == NULL)
{
// 没有安装过Hook?我们安装它。
hSkinDialogHook = SetWindowsHookEx(
WH_CBT,
CBTProc,
NULL,
GetCurrentThreadId() // 只Hook当前线程!!!
);
}
// 首先,枚举所有的子窗口,把按钮类型的Style增加上OwnerDraw标记。
EnumChildWindows(hWnd,EnumSkinWindowProc,(LPARAM)&m_bSkinButtonsTemplate);
if (GetProp(hWnd,"SkinWindowOldWndProc") == NULL)
{
// 获取以前的WndProc
WNDPROC OldWndProc=(WNDPROC)GetWindowLong(hWnd,GWL_WNDPROC);
// 保存下来。
SetProp(hWnd,"SkinWindowOldWndProc",(HANDLE)OldWndProc);
// 设置新的WndProc
SetWindowLong(hWnd,GWL_WNDPROC,(LONG)SkinWindowProc);
SetProp(hWnd,"SkinWindowHandled",(HANDLE)1L);
}
else
{
// 只允许SubClass一次。
return FALSE;
}
return FALSE;
}
BOOL CSkinDialog::RemoveSubClass()
{
BOOL bResult=FALSE;
if (hSkinDialogHook != NULL)
{
bResult=UnhookWindowsHookEx(hSkinDialogHook);
hSkinDialogHook=NULL;
}
return bResult;
}
void CSkinDialog::DoGradientFill(CDC *pDC, CRect rect)
{
CBrush pBrush[64];
int nWidth = (rect.right) - (rect.left);
int nHeight = (rect.bottom) - (rect.top);
CRect rct;
BOOL m_bOverControl=FALSE;
BOOL Focus=FALSE;
for (int i=0; i<64; i++)
{
//pBrush[i].CreateSolidBrush(RGB(255-(i/1.3), 255-(i/1.4), 255-(i/1.6)));
pBrush[i].CreateSolidBrush(RGB(100,100,255));
//pBrush[i].CreateSolidBrush(RGB(90+2*i,192+i,104-i));
}
for (i=rect.top; i<nHeight+2; i++)
{
rct.SetRect (rect.left, i, nWidth+2, i + 1);
pDC->FillRect (&rct, &pBrush[((i * 63) / nHeight)]);
}
for (i=0; i<64; i++)
{
pBrush[i].DeleteObject();
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -