金山毒霸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下調試通過。