金山毒霸2002自推出以来,广受用户的喜爱。今天在这里我要谈的不是金山毒霸的功能,而是其别具一格的界面。本人早就对金山毒霸的界面垂涎三尺,如果自己的程序里能用上金山毒霸那样的界面,那该多爽!
说干就干,本着由浅入深的原则,我选择了金山毒霸的一个附带工具:Duba_GOP.exe来开刀。该工具在金山毒霸的网站上可以下载到,其
界面如图所示:
好,开始准备工具:
提取资源工具:freeRes
编程工具:VC++
还有一个Vc自带的小工具:spy++
之所以不用大家都熟悉的eXeScope,是因为Duba_GOP.exe已经被压缩过了,有些资源提取工具无法再用。
用freeRes打开Duba_GOP.exe,可看到其带有20个bmp位图,有整个窗体的背景图,右上角两个小按钮“主页”和“关闭”的三态位图,以及两个大按钮“浏览文件夹”和“开始扫描”的三态位图。看到这些图片,即可猜到该软件是没有标题栏的,而显示出来的标题栏只是背景,而且连同窗体下部的金山毒霸的标志都是属于同一幅背景图片!那么“标题栏”上的“主页”,“关闭”是怎么回事?用spy++来试一下,发现这两个是Button,而不是Bitmap。同样“浏览文件夹”和“开始扫描”也是Button,随即想到这四个Button可以用VC中的CBitmap类来实现。而窗体下部的超链接仅仅是Static静态框。把这些难点分析出来了,下面就可以开始我们的编程之旅了,不过记得把有用的图片保存下来。
一.窗体背景问题
打开vc,新建一个对话框工程,名为Interface。去掉缺省的一个Static和两个Button,在窗体上点右键,在出现的属性对话框中,去掉标题栏,再将用freeRes提取的所有图片Import进工程的资源中。修改CInterfaceDlg::OnPaint()如下:
01.
void
CInterfaceDlg::OnPaint()
02.
{
03.
if
(IsIconic())
04.
{
05.
CPaintDC dc(
this
);
// device context for painting
06.
SendMessage(WM_ICONERASEBKGND, (
WPARAM
) dc.GetSafeHdc(), 0);
07.
// Center icon in client rectangle
08.
int
cxIcon = GetSystemMetrics(SM_CXICON);
09.
int
cyIcon = GetSystemMetrics(SM_CYICON);
10.
CRect rect;
11.
GetClientRect(&rect);
12.
int
x = (rect.Width() - cxIcon + 1) / 2;
13.
int
y = (rect.Height() - cyIcon + 1) / 2;
14.
// Draw the icon
15.
dc.DrawIcon(x, y, m_hIcon);
16.
}
17.
else
18.
{
19.
// CDialog::OnPaint();//将这一句注销掉
20.
*********************************************************
21.
CPaintDC dc(
this
);
22.
CRect rect;
23.
GetClientRect(&rect);
//得到窗体的大小
24.
CDC dcMem;
25.
dcMem.CreateCompatibleDC(&dc);
26.
CBitmap bmpBackground;
27.
bmpBackground.LoadBitmap(IDB_BITMAPBACKGROUND);
//加载背景图片
28.
BITMAP bitMap;
29.
bmpBackground.GetBitmap(&bitMap);
30.
CBitmap *pbmpOld=dcMem.SelectObject(&bmpBackground);
31.
dc.StretchBlt(0,0,rect.Width(),rect.Height(),&dcMem,0,0,bitMap.bmWidth,bitMap.bmHeight,SRCCOPY);
//画窗体
32.
********************************************************
33.
}
34.
}
其中两个“*”行之间的部分为所添加部分。
这段代码的作用是将那幅背景图片画在窗体上, 其中我使用了StretchBlt来画出窗体,StretchBlt比BitBlt要慢许多,如果你想得到更好的速度,可以考虑建立与背景位图相同尺寸的窗体,然后用BitBlt来画出。CBitmap bmpBackground;最好作为CInterfaceDlg的成员变量并在CInterfaceDlg::OnInitDialog中提前加载位图,感兴趣的朋友可以试试。
编译,运行。可看到修改后的效果,由于该图片是平铺在窗体上的,如果图片和窗体尺寸不一致就会导致图片发生扭曲。因此可将窗体调整到与图片相同或相近的尺寸大小,那么运行后看起来就顺眼多了。但是窗体边框还是不大对劲,于是再进vc的资源编辑器,将窗体的Border由“Dialog Frame”该为“Thin”,重新编译,运行,搞定!
但是没过一秒钟就觉得这话说早了,标题栏没了,窗体怎么移动啊?
别急,车到山前必有路,我们不妨骗骗Windows。当我们的鼠标在窗体任意位置上拖动鼠标时,让Windows以为鼠标在标题栏上拖动,不就可以实现任意点击窗体的什么地方都可以移动窗体了吗?于是在CInterfaceDlg::OnLButtonDown(UINT nFlags, CPoint point)中添加下面一句:1.
PostMessage(WM_NCLBUTTONDOWN,HTCAPTION,MAKELPARAM(point.x, point.y));
该语句的作用是向系统发送HTCAPTION消息,让系统以为鼠标点在标题栏上。
现在编译,运行,真的搞定了!
二.按钮问题
Duba_GOP.exe中包括两类按钮,第一类就是上面所说的四个图片按钮,可以CBitmap类来实现:
在窗体上添加四个Button,设为Bitmap和OwnerDraw风格。为窗体添加4个CBitmap的成员变量,比如:
1.
CBitmapButton m_StartBtn;
并与这四个Button关联上,再在CInterfaceDlg::OnInitDialog()中添加:
1.
m_StartBtn.LoadBitmaps(IDB_BITMAPONNORMAL,IDB_BITMAPONDOWN);
//让窗体初始化时就加载按钮位图
其中IDB_BITMAPONNORMA是一般状态下的按钮的图片ID,IDB_BITMAPONDOWN是鼠标按下时的按钮的图片ID。 合理摆放这四个按钮的位置,使其与Duba_GOP.exe界面上的位置一致,现在运行一下看看,界面增色不少吧。只是有一点:“开始扫描”这个按钮比较特殊,当鼠标点击时,会变成停止的位图,当再次点击时,又变成开始的位图,因此这个按钮拥有两套,4个位图。为此,再为CInterfaceDlg添加一个成员变量:
1.
BOOL
m_bEnable;
并在CInterfaceDlg::OnInitDialog()中设其初值为True。
然后在点击“开始扫描”按钮的相应事件CInterfaceDlg::OnStartbtn()中添加:
01.
if
(m_bEnable)
02.
{
03.
m_StartBtn.LoadBitmaps(IDB_BITMAPOFFNORMAL,IDB_BITMAPOFFDOWN);
//加载位图
04.
m_bEnable=
false
;
05.
m_StartBtn.RedrawWindow();
06.
}
07.
else
08.
{
09.
m_StartBtn.LoadBitmaps(IDB_BITMAPONNORMAL,IDB_BITMAPONDOWN);
//加载另一套位图
10.
m_bEnable=
true
;
11.
m_StartBtn.RedrawWindow();
12.
}
以上代码的作用是控制两套位图的切换。
好了,现在轮到Duba_GOP.exe界面中上部的那三个CheckBox风格的Button了。首先添加三个这样的按钮,但看上去与Duba_GOP.exe的按钮不太一样,于是将我的三个按钮加上“平坦”的风格,OK,行了。
编译,运行,顿时大吃一惊,原来新添加的三个CheckBox的背景还是缺省的灰色,与白色的窗体背景极不协调,非常难看。看来需要改变该类Button的背景颜色了。于是,从CButton类派生出一个CColorButton类,其主要代码如下:
01.
// ColorButton.h : header file
02.
class
CColorButton :
public
CButton
03.
{
04.
……
//省略了无关代码
05.
public
:
06.
CBrush m_brush;
07.
void
SetBackColor(
COLORREF
BackColor);
08.
protected
:
09.
COLORREF
m_BackColor;
10.
//{{AFX_MSG(CColorButton)
11.
afx_msg
HBRUSH
CtlColor(CDC* pDC,
UINT
nCtlColor);
12.
//}}AFX_MSG
13.
DECLARE_MESSAGE_MAP()
14.
……
//省略了无关代码
15.
}
16.
17.
// ColorButton.cpp
18.
19.
BEGIN_MESSAGE_MAP(CColorButton, CButton)
20.
//{{AFX_MSG_MAP(CColorButton)
21.
ON_WM_CTLCOLOR_REFLECT()
22.
//}}AFX_MSG_MAP
23.
END_MESSAGE_MAP()
24.
25.
void
CColorButton::SetBackColor(
COLORREF
BackColor)
26.
{
27.
m_BackColor=BackColor;
//设置背景颜色
28.
m_brush.CreateSolidBrush(m_BackColor);
//创建画刷
29.
}
30.
31.
32.
HBRUSH
CColorButton::CtlColor(CDC* pDC,
UINT
nCtlColor)
33.
{
34.
pDC->SetBkMode(TRANSPARENT);
//将背景设为透明
35.
return
(
HBRUSH
)m_brush;
//返回自定义画刷
36.
// return NULL;//将缺省的处理注销掉
37.
}
好了,新类构造完成了。如何使用呢?将这三个CheckBox按钮声明为CColorButton类的三个成员变量,比如:
1.
CColorButton m_ColorBtn1;
不过要记得在CInterfaceDlg::OnInitDialog()中添加:
1.
m_ColorBtn1.SubclassDlgItem(IDC_CHECK1,
this
);
// IDC_CHECK1是其中一个CheckBox的ID
2.
m_ColorBtn1.SetBackColor(RGB(222,223,222));
// RGB(222,223,222)就是窗体背景颜色
现在编译一下,可以看到这三个按钮已经融入窗体背景之中,按钮问题已经全部解决了。
三.Static问题
这个界面上Static不少,中上部有一个Static有一种凹下去的效果,其实只需要一般的Static设置了“下沉”的风格即可。其余几个Static可算作一类,存在的问题和前面的一样,也就是背景颜色问题,为此又从CStatic中派生出CcolorStatic类,主要代码如下:
01.
// ColorStatic.h : header file
02.
class
CColorStatic :
public
CStatic
03.
{
04.
……
//省略了无关代码
05.
public
:
06.
void
SetTextColor(
COLORREF
TextColor);
07.
COLORREF
m_TextColor;
08.
void
SetBackColor(
COLORREF
BackColor);
09.
void
SetCaption(CString strCaption);
10.
void
Create(CString strCaption,
COLORREF
BackColor);
11.
COLORREF
m_BackColor;
12.
CString m_strCaption;
13.
protected
:
14.
//{{AFX_MSG(CColorStatic)
15.
afx_msg
int
OnCreate(LPCREATESTRUCT lpCreateStruct);
16.
afx_msg
void
OnPaint();
17.
//}}AFX_MSG
18.
DECLARE_MESSAGE_MAP()
19.
}
20.
21.
22.
// ColorStatic.cpp
23.
BEGIN_MESSAGE_MAP(CColorStatic, CStatic)
24.
//{{AFX_MSG_MAP(CColorStatic)
25.
ON_WM_CREATE()
26.
ON_WM_PAINT()
27.
//}}AFX_MSG_MAP
28.
END_MESSAGE_MAP()
29.
30.
void
CColorStatic::OnPaint()
//重画Static
31.
{
32.
CPaintDC dc(
this
);
// device context for painting
33.
CRect rect;
34.
GetClientRect(&rect);
35.
dc.SetBkColor(m_BackColor);
36.
dc.SetBkMode(TRANSPARENT);
37.
CFont *pFont=GetParent()->GetFont();
//得到父窗体的字体
38.
CFont *pOldFont;
39.
pOldFont=dc.SelectObject(pFont);
//选用父窗体的字体
40.
dc.SetTextColor(m_TextColor);
//设置文本颜色
41.
dc.DrawText(m_strCaption,&rect,DT_CENTER);
//将文本画在Static的中央
42.
dc.SelectObject(pOldFont);
43.
44.
// Do not call CStatic::OnPaint() for painting messages
45.
}
46.
47.
void
CColorStatic::SetCaption(CString strCaption)
48.
{
49.
m_strCaption=strCaption;
//设置Static文本
50.
}
51.
52.
void
CColorStatic::SetBackColor(
COLORREF
BackColor)
53.
{
54.
m_BackColor=BackColor;
//设置背景颜色
55.
}
56.
57.
void
CColorStatic::SetTextColor(
COLORREF
TextColor)
58.
{
59.
m_TextColor=TextColor;
//设置文字颜色
60.
}
OK,这也是个很简单的类,主要功能就是可以设置该Static的文字颜色和背景颜色,但是已经基本满足我们的需要了。有一点缺陷就是下面三个Static应该还具备超链接的功能,不过你可以通过修改这个类来实现。或者直接用一个超链接类来替代也可以。
总之,这个问题也解决了,现在我们的程序运行起来已经非常像金山毒霸的Duba_GOP.exe了,做到这一步仿制任务已经基本完成,但是低头看看系统的任务栏你会发现自己的程序还不够完美,因为在任务栏上属于自己程序的那个小方块上空空如也,太不专业了。
请注意Windows是将程序的标题栏上的Caption和图标显示在任务栏上,而我的这个程序没有标题栏,故而什么也显示不出来,怎么办呢?办法在这里:
1.恢复标题在对话框的OnInitDialog()中添加:
SetWindowText("金山毒霸专杀工具");//设置对话框的标题为金山毒霸专杀工具
2.恢复图标
在对话框上打开属性对话框,重新选上“System Menu”和“Title Bar”风格。然后在对话框的OnInitDialog()中添加:
ModifyStyle(WS_CAPTION,WS_MINIMIZEBOX,SWP_DRAWFRAME);
现在运行起来看看,瞧!系统任务栏上熟悉的图标和标题又回来了。但是这样又带来一个问题:窗体最下面的那个Static在运行后与背景图片上金山毒霸2002几个字重叠在一起,很不好看。而且由于对话框的尺寸有限制,这个Static也移不到合适的位置,于是咱们只好在程序里动动脑筋了。比如在OnInitDialog()里加上几句:
1.
CRect rect1;
2.
m_Link3.GetWindowRect(&rect1);
3.
rect1.top+=17;
4.
rect1.bottom+=17;
5.
m_Link3.MoveWindow(rect1.left,rect1.top,rect1.Width(),rect1.Height());
//往下移17
其中的m_Link3就是代表那个Static的成员变量。至于那个ListCtrl嘛,只不过在它的风格里去掉了边框而已。自此,大功告成!
该程序在VC6+Win98/2k/xp下调试通过。