皮肤技术[实现原理]

作者:Sanbrother
来源:www.sanbrother.com

要实现换肤,就是在恰当的时候处理恰当的消息。比如WM_NCPAINT消息:当程序的框架(Frame)需要重新绘制的时候,系统发送此消息给此程 序。程序接收到此消息,如果没有被处理的话,则进行默认的处理(绘制边框、标题栏按钮等,XP下普通窗口的蓝色边框就是默认的处理)。因此,我们可以抓住 这个时机来绘制皮肤。
void CSkinDlgDlg::OnNcPaint()
{
CDC * pDC = GetWindowDC();
DrawBorders(pDC,m_bActive);
DrawCorners(pDC,m_bActive);
ReleaseDC(pDC);
}
在代码中使用了GetWindowDC,而不是GetDC,因为我们要对非客户区(non-client)进行操作,GetDC只能得到Client区的DC。
DrawBorders函数用来绘制四条边框:
void CSkinDlgDlg::DrawBorders(CDC * pDC, BOOL bActive)
{
CRect rcWindow;
GetWindowRect(&rcWindow);
CDC * memDC = new CDC;
memDC->CreateCompatibleDC(pDC);
CBitmap * pOld = bActive ? memDC->SelectObject(&m_bmpTop_1) :
memDC->SelectObject(&m_bmpTop_2);

int a = int((rcWindow.Width() – BORDER_LEFT_WIDTH – BORDER_RIGHT_WIDTH) /
BORDER_TOP_WIDTH);
if(((rcWindow.Width() – BORDER_LEFT_WIDTH – BORDER_RIGHT_WIDTH) %
BORDER_TOP_WIDTH) != 0) a++;
for( int b = 0; b < a; b++)
{
pDC->BitBlt(BORDER_LEFT_WIDTH + BORDER_TOP_WIDTH *
b,0,BORDER_TOP_WIDTH,BORDER_TOP_HEIGHT,memDC,0,0,SRCCOPY);
}
memDC->SelectObject(pOld);
//以上代码完成绘制上侧边框(标题栏),其它三个是类似的,在此省略
delete memDC;
}
其中的bActive参数下文有解释,BORDER_LEFT_WIDTH …… 等,这些是在程序开始处定义的常量,代表边框的宽度、高度值。
DrawCorners函数用来绘制四个角,此函数用于非矩形窗口。由于非矩形窗口的四个角要考虑到透明的问题,所以编写了这个函数:
void CSkinDlgDlg::DrawCorners(CDC * pDC, BOOL bActive)
{
CRect rcWindow;
GetWindowRect(&rcWindow);
CDC * memDC = new CDC;
memDC->CreateCompatibleDC(pDC);
CBitmap * pOld = bActive ? memDC->SelectObject(&m_bmpTopLeft_1) :
memDC->SelectObject(&m_bmpTopLeft_2);
pDC->BitBlt(0,0,BORDER_LEFT_WIDTH,BORDER_TOP_HEIGHT,memDC,0,0,SRCCOPY);
memDC->SelectObject(pOld);
//以上代码完成绘制左上角,其它三个是类似的,在此省略
delete memDC;
}
有了上面两个函数,并且正确处理了OnNcPaint函数后,编译运行,就能看到基本的效果了。

但是,当此窗口失去焦点的时候,又恢复成原来的样子。因为我们没有处理当程序状态变化时系统发送的消息(WM_NCACTIVATE),系统做了默认 的处理。当系统发送此消息的时候,附带一个bActive参数,指示当前窗口是活动状态(真值)还是非活动状态(假值)。这样,上面两个函数可以根据此参 数值来选择相应的位图(皮肤)文件,来表示此窗口的状态。

BOOL CSkinDlgDlg::OnNcActivate(BOOL bActive)
{
m_bActive = bActive;
CDC * pDC = GetWindowDC();
DrawBorders(pDC,m_bActive);
DrawCorners(pDC,m_bActive);
ReleaseDC(pDC);
return TRUE;
}
函数返回TRUE,代表处理了此消息,系统就不会进行默认的处理了。
再次编译,效果比较好了。只是边框被Client区盖住了一部份。通常,程序的边框的粗细是一定的。而这些特定的值可以通过 GetSystemMetrics函数来获得。比如我们知道了边框的粗细是4像素(水平与竖直边框不一定相等),而我们要绘制的边框是14像素的,这就要 考虑把Client区缩小一点。好像标准函数库没有设置Client区大小的函数,虽然有获取其大小的函数。

要解决这个问题,我们可以处理这个消息:WM_NCCALCSIZE
void CSkinDlgDlg::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
RECT r;
memcpy(&r,&lpncsp->rgrc[0],sizeof(RECT));
r.top += (BORDER_TOP_HEIGHT – GetSystemMetrics(SM_CYSMCAPTION) -
GetSystemMetrics(SM_CXSIZEFRAME));
r.bottom -= (BORDER_BOTTOM_HEIGHT – GetSystemMetrics(SM_CXSIZEFRAME));
r.left += (BORDER_LEFT_WIDTH – GetSystemMetrics(SM_CXSIZEFRAME));
r.right -= BORDER_RIGHT_WIDTH – GetSystemMetrics(SM_CXSIZEFRAME);
memcpy(&lpncsp->rgrc[0],&r,sizeof(RECT));
InvalidateRect(NULL);
CDialog::OnNcCalcSize(bCalcValidRects, lpncsp);
}
r.left += X(X>0),表示Client被向右移了X个像素,其它同理。现在,边框就不会被Client区盖住了。如果不想把客户区缩小,可以考虑先将整个窗口适当增大,这样客户区就能保持原来的大小了。

网友评论(共2条评论)

  1. lonkil

     2008年12月1日 13:21 pm

    圆弧也一样的,目前有两种方法实现一是通道,二是关键色。

    透明需要程序支持。


  2. flight

     2008年12月1日 13:02 pm

    挺有用的东东,如果能说说四个角圆弧如何做透明处理就更好了


发表评论





XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>