概述
詳細
一、概述
1 引言:
應用程序界面是用戶與應用程序之間的交互的橋梁和媒介,用戶界面是應用程序中最重要的組成部分,也是最為直觀的視覺體現。對用戶而言,界面就是應用程序,界面設計的好壞,會直接影響應用程序的可用性,從而影響用戶的體驗。
在軟件開發過程中,對界面的設計椅子都是一項很重要的技術,如今的應用軟件界面可謂是“豐富多彩、美麗絕倫”,如大家熟悉的360安全衛士、騰訊QQ聊天軟件、Visual C++ 編程詞典軟件等,都是非常不同於普通的對話框應用程序,因為他們的界面都是重新繪制過的,從而實現了漂亮、易用的用戶體驗。鑒於VC++編程技術,本文將通過對對話框的重新繪制來達到自定義的界面效果。
2 方案概述:
本文的主要是實現對話框界面的重新自定義繪制,主要包括標題欄的重繪、對話框邊框的重繪、對話框背景重繪、以及最小化按鈕、最小化按鈕和關閉按鈕等的重繪實現。
二、編程平台技術實現原理方案設計及過程
1 編程平台與技術:
本文實現的編程平台是基於Microsoft Visual Studio 2008 集成開發環境,編程技術采用Visual C++ 編程技術,以及相關的開發軟件如Photoshop CS5等。
2 方案分析:
在對話框重繪中,使用的主要技術有兩個,一個是繪制對話框的背景位圖,在對話框大學改變時能夠輸出位圖,使位圖能夠適應對話框的大小。另一個是在對話框的指定區域輸出位圖。
2.1繪制對話框的背景位圖
繪制對話框背景位圖本文采用的是處理對話框的WM_PAINT消息,該消息初始化時候對對話框進行繪制,從而繪制背景位圖。繪制背景位圖的主要代碼如下:
CRect rect;
CPaintDC dc(this);
GetClientRect(&rect); //獲取客戶區
//設置對話框背景顏色
dc.FillSolidRect(rect,RGB(14,94,157)); //設置為窗口背景
2.2在指定的區域中輸出位圖
為了能夠在指定的區域中輸出位圖,需要使用設備上下文CDC類的StretchBlt方法。由於我們需要在窗口的非客戶區域繪制位圖,因此需要使用CWindowDC類的StretchBlt方法, CWindowDC類派生與CDC類,它提供了在窗口非客戶區域繪制位圖的功能。該方法數從源矩形中復制一個位圖到目標矩形,必要時按目前目標設備設置的模式進行圖像的拉伸或壓縮。輸出位圖的主要實現代碼如下:
CRect winRC;
CDC* pDC=GetWindowDC();//獲取窗口設備上下文
CDC memDC;
memDC.CreateCompatibleDC(pDC);//創建兼容內存位圖
BITMAPINFO bmpInfo;
CBitmap bmp; //定義位圖對象
GetWindowRect(&winRC);
bmp.LoadBitmap(nID);//加載位圖
bmp.GetObject(sizeof(BITMAPINFO),&bmpInfo);//獲取位圖信息
int nBmpCX = bmpInfo.bmiHeader.biWidth;//獲取位圖寬度
int nBmpCY = bmpInfo.bmiHeader.biHeight;//獲取位圖高度
memDC.SelectObject(bmp);//選中位圖對象
pDC->StretchBlt(x,y,w,h,
&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY);//在窗口中繪制位圖
bmp.DeleteObject();//釋放位圖對象
ReleaseDC(pDC);//釋放DC
3 方案設計與實現:
3.1方案的整體設計
對界面的整體重繪包括兩部分,一部分是對話框自身的重繪,二是對話框控件的重繪,本文主要介紹按鈕控件的重繪。
3.2對話框繪制的實現
在對話框重繪設計與實現過程中,一般需要繪制的對話框區域主要有標題部分、邊框部分和客戶區部分。具體的區域划分如圖1所示。
圖1 對話框繪制區域圖
既然要對多個區域進行位圖顯示輸出,所以我們先封裝一個bmp位圖顯示輸出函數如下:
void CCTestDlg::ShowBmp(int x,int y,int w,int h,int nID)
{//nID 表示位圖資源的ID
CRect winRC;
CDC* pDC=GetWindowDC();
CDC memDC;
memDC.CreateCompatibleDC(pDC);
BITMAPINFO bmpInfo;
CBitmap bmp;
GetWindowRect(&winRC);
bmp.LoadBitmap(nID);
bmp.GetObject(sizeof(BITMAPINFO),&bmpInfo);
int nBmpCX = bmpInfo.bmiHeader.biWidth;
int nBmpCY = bmpInfo.bmiHeader.biHeight;
memDC.SelectObject(bmp);
pDC->StretchBlt(x,y,w,h,
&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY);//在窗口中繪制位圖
bmp.DeleteObject();
ReleaseDC(pDC);
}
(1)對各個區域進行位圖輸出重繪。由於標題欄以及邊框主要都是非客戶區域繪制,因此應該在WM_NCPAINT 消息中繪制。當然得先通過添加資源的方式將所用到的bmp位圖資源導入到項目中。
首先定義一些常量值,表示對話框各個組成區域部分。代碼如下:
#define LEFTTITLE 1//左標題
#define MIDTITLE 1//中間標題
#define RIGHTTITLE 1//右標題
#define MINBUTTON 1//右標題
#define MAXBUTTON 1//右標題
#define CLOSEBUTTON 1//右標題
#define APPICON 1 //程序icon圖標
#define LEFTBAR 1 //左邊框
#define RIGHTBAR 1 //右邊邊框
#define BOTTOMBAR 1 //底邊框
在 WM_NCPAINT消息對於的 方法OnNcPaint()中調用對話框繪制方法SetFace()。該方法的功能就是繪制對話框各個區域的位圖。主要代碼如下:
void CCTestDlg::SetFact()
{
// TODO: 在此添加控件通知處理程序代碼
int nFrameCY = GetSystemMetrics(SM_CYFIXEDFRAME);//獲取對話框邊框的高度
int nFrameCX = GetSystemMetrics(SM_CXDLGFRAME);//獲取對話邊框的寬度
int m_nBorderCY;
int m_nBorderCX;
int m_nTitleBarCY ;
int m_nTitleBarCX;
if(GetStyle()&WS_BORDER)//獲取對話框是否有邊框
{
m_nBorderCY = GetSystemMetrics(SM_CYBORDER) + nFrameCY;
m_nBorderCX = GetSystemMetrics(SM_CXBORDER) +nFrameCX;
}
else
{
m_nBorderCX = nFrameCX;
m_nBorderCY = nFrameCY;
}
m_nTitleBarCY = GetSystemMetrics(SM_CYCAPTION) + m_nBorderCY;//計算標題欄高度
m_nTitleBarCX =m_nBorderCX;
CRect winRect,factRect;
GetWindowRect(&winRect); //獲取窗口區域
factRect.CopyRect(CRect(0,0,winRect.Width(),winRect.Height()));
CWindowDC windowsDC(this);//獲取窗口設備上下文
//獲取整個MFC窗口的高度和寬度
int winCX = winRect.Width();
int winCY = winRect.Height();
if(LEFTTITLE)
{//繪制對話框左標題欄位圖
ShowBmp(0,0,100,m_nTitleBarCY,IDB_RIGHTTITLE);
}
if(RIGHTTITLE)
{//繪制對話框右標題欄位圖
ShowBmp(winCX-100,0,100,m_nTitleBarCY,IDB_RIGHTTITLE);
}
if(MIDTITLE)
{//繪制對話框中標題欄位圖
ShowBmp(100,0,winCX-200,m_nTitleBarCY,IDB_MIDTITLE);
}
if(LEFTBAR)
{//繪制對話框左邊框位圖 ShowBmp(0,m_nTitleBarCY,m_nBorderCX,factRect.Height()-m_nBorderCY,IDB_LEFTBAR);
}
if(BOTTOMBAR)
{//繪制對話框底邊框位圖 ShowBmp(m_nBorderCX,winCY-m_nBorderCX,winCX-2*m_nBorderCX,m_nBorderCX,IDB_BOTTOMBAR);
}
if(RIGHTBAR)
{//繪制對話框左邊框位圖 ShowBmp(winCX-m_nBorderCX,m_nTitleBarCY,m_nBorderCX,factRect.Height()-m_nBorderCY,IDB_RIGHTBAR);
}
if(MINBUTTON)
{//給對話框繪制最小化按鈕
ShowBmp(winCX-3-24-3-24-3-24,1,24,24,IDB_MINBUTTON);
}
if(MAXBUTTON)
{//給對話框繪制最大化按鈕
ShowBmp(winCX-3-24-3-24,1,24,24,IDB_MAXBUTTON);
}
if(CLOSEBUTTON)
{//給對話框繪制關閉按鈕
ShowBmp(winCX-3-24,1,24,24,IDB_CLOSEBUTTON);
}
ReleaseDC(&windowsDC);
DrawTitleBarText();//輸出標題欄文本
}
上面代碼中最后的繪制對話框標題文本的方法DrawTitleBarText()的主要代碼如下:
CString strTitle ="自繪窗口標題欄和邊框";
CDC* pDC= GetWindowDC();//獲取窗口設備上下文
pDC->SetBkMode(TRANSPARENT);//設置透明的背景模式
pDC->SetTextColor(RGB(255,255,255));//設置文本顏色
pDC->SetTextAlign(TA_CENTER);//設置文本對齊方式
CRect rect;
GetClientRect(&rect);//獲取窗口客戶區域
CSize szText = pDC->GetTextExtent(strTitle);//獲取文本高度
pDC->TextOut(rect.Width()/2,3,strTitle,20);//在窗口中輸出文本
ReleaseDC(pDC);//釋放窗口設備上下文
繪制后的效果圖如圖2所示。
圖2 對話框繪制效果圖
在完成對話框相應區域的位圖后,並沒有完成任務,還需要處理標題欄按鈕的熱點效果,以及按鈕的單擊事件。首先得處理鼠標在非客戶區域移動時的事件,即WM_NCMOUSEMOVE消息,在其消息處理函數中判斷當前的鼠標點是否位於標題欄的按鈕區域,如果是則設置按鈕的熱點效果,並且記錄當前的按鈕狀態,及鼠標點在哪個按鈕上。同樣的,處理對話框非客戶區域的單擊事件,即WM_NCLBUTTONDOWN消息,在其消息處理函數中完成單擊事件操作。主要代碼如下:
void CCTestDlg::OnNcMouseMove(UINT nHitTest, CPoint point)
{
// TODO: 在此添加消息處理程序代碼和/或調用默認值
CRect minRC,maxRC,closeRC,winRC;
GetWindowRect(&winRC);
closeRC.CopyRect(CRect(winRC.Width()-27,1,winRC.Width()-27+24,1+24));
maxRC.CopyRect(CRect(winRC.Width()-27*2,1,winRC.Width()-27*2+24,1+24));
minRC.CopyRect(CRect(winRC.Width()-27*3,1,winRC.Width()-27*3+24,1+24));
point.Offset(-winRC.left,-winRC.top);//由於point為屏幕坐標,這里將其轉換為窗口坐標
if(closeRC.PtInRect(point)) //判斷鼠標是否在關閉按鈕區域上
{
ShowBmp(winRC.Width()-3-24,1,24,24,IDB_CLOSEBUTTON2);
}
else if(maxRC.PtInRect(point)) //判斷鼠標是否在最大化按鈕區域上
{
ShowBmp(winRC.Width()-27*2,1,24,24,IDB_MAXBUTTON2);
}
else if(minRC.PtInRect(point)) //判斷鼠標是否在最小化按鈕區域上
{
ShowBmp(winRC.Width()-27*3,1,24,24,IDB_MINBUTTON2);
}
else//鼠標沒有在標題欄的按鈕區域上
{
ShowBmp(winRC.Width()-3-24-54,1,24,24,IDB_MINBUTTON);
ShowBmp(winRC.Width()-3-24-27,1,24,24,IDB_MAXBUTTON);
ShowBmp(winRC.Width()-3-24,1,24,24,IDB_CLOSEBUTTON);
}
}
添加熱點效果后的效果如圖3所示。
圖3 繪制對話框之熱點效果圖
3.3按鈕控件重繪的實現
在MFC下編程,很多時候對於標准的按鈕控件不是很滿意,想要弄的美觀些。這就需要按鈕重繪。重繪按鈕一般的實現方法就是重寫CButton類。
首先給工程添加一個自繪按鈕類MyDrawButton,基類為CButton。要想讓按鈕具備自繪功能,就要為按鈕添加BS_OWNERDRAW屬性。為類CButton重載PreSubclassWindow虛函數。在該函數中添加如下一行代碼:
SetButtonStyle(GetButtonStyle() | BS_OWNERDRAW);
當按鈕控件具有了自繪功能之后,每次控件狀態改變都會觸發DrawItem函數,在該函數中來繪制按鈕的形態外觀,所以第二步就要重載DrawItem虛函數。在這個函數中就可以自由發揮了,比如繪制背景,底色,按鈕標題,繪制文本字體樣式等等。
一般都會為按鈕定義幾種不同狀態時的外觀,比如光標滑過時的狀態,按鈕按下時的狀態,按鈕禁用時的狀態,以及按鈕的正常狀態等等。這就要為新的按鈕添加幾種重要的消息響應。比如WM_MOUSELEAVE消息,WM_MOUSEHOVER消息和WM_MOUSEMOVE消息等等,值得一提的是前兩個消息的響應函數需要自己手動添加,微軟提供了一個TrackMouseEvent函數在光標離開一個窗口時投遞WM_MOUSELEAVE消息,光標滑過窗口時投遞WM_MOUSEHOVER消息。一般來說可以在WM_MOUSEMOVE消息響應函數中調用TrackMouseEvent函數來投遞WM_MOUSELEAVE消息和WM_MOUSEHOVER消息。然后在WM_MOUSELEAVE消息的響應函數中標記“光標已經離開按鈕”,然后調用InvalidateRect函數讓按鈕重繪。在WM_MOUSEHOVER消息的響應函數中標記“光標正在按鈕上方”,並調用InvalidateRect函數讓按鈕重繪。
在本文中,重繪按鈕分為3個部分。
(1)繪制按鈕背景樣式,即繪制背景bmp位圖,使得按鈕具有自定義的樣式,同時在繪制按鈕背景的輸出位圖時采用TransparentBlt()函數,該函數的作用是使窗體上顯示位圖的背景與窗體背景色融為一體,不僅可以顯示按鈕bmp位圖樣式,而且還可以使背景透明。
(2)就是繪制按鈕上的文本。主要繪制按鈕上文本的樣式,包括字體大小,字體樣式,字體顏色等屬性。
(3)實現不同狀態下的按鈕的外觀樣式,主要包括WM_MOUSEMOVE和WM_MOUSELEAVE兩個消息的消息處理函數。分別實現鼠標在按鈕區域上和不在按鈕區域上的狀態。為了標記鼠標移動到按鈕區域內停留,需要用到一個定時器來標記鼠標是否還在按鈕區域內停留。在WM_MOUSEMOVE內啟動定時器,觸發WM_MOUSELEAVE消息時結束定時器即銷毀定時器。定時器的主要代碼如下:
void MyDrawButton::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息處理程序代碼和/或調用默認值
if(nIDEvent != 24)
return;
CPoint point;
CRect rect;
GetWindowRect(&rect);
GetCursorPos(&point);
// 如果鼠標離開按鈕區域,重繪按鈕
if (!rect.PtInRect(point) && m_bMove)
{
KillTimer (24);
m_DrawState=ST_MOVEOUT;
m_bMove=FALSE;
Draw();
}
CButton::OnTimer(nIDEvent);
}
重繪按鈕類MyDrawButton的主要實現代碼如下:
消息處理函數和定義的函數:
void MyDrawButton::PreSubclassWindow()
{
SetButtonStyle(GetButtonStyle() | BS_OWNERDRAW);
}
void MyDrawButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
Draw();//繪制按鈕
}
void MyDrawButton::Draw()//繪制按鈕
{
DrawBackground();//繪制按鈕bmp位圖,並使背景透明化
DrawText();//繪制按鈕上的文本
}
void MyDrawButton::DrawText()
{//繪制按鈕上的文本的字體大小、樣式等
CString itemString;
CRect clientRect;
CClientDC dc(this);
GetClientRect(&clientRect);
GetWindowText(itemString);
if(itemString)
{
CSize size=dc.GetTextExtent (itemString);//獲得所選字體中指定字符串的高度和寬度
int rectwidth=clientRect.Width();
int rectheight=clientRect.Height();
int textwidth=size.cx ;
int textheight=size.cy ;
int x,y; // 文本的位置
// 計算文本的輸出位置
x=(rectwidth-textwidth)/2;//水平居中
y=(rectheight-textheight)/2;//垂直居中
switch(m_DrawState)
{
case ST_MOVEIN://鼠標進入按鈕區域
m_clText=m_clActiveText;
break;
case ST_MOVEOUT://鼠標離開按鈕區域
m_clText=m_clNormalText;
break;
default:
m_clText=m_clNormalText;
break;
}
dc.SetTextColor(m_clText);
dc.SetBkMode(TRANSPARENT);
CFont *font ;
font =new CFont();
int fontSize = 14; font->CreateFont(fontSize,0,0,0,FW_BOLD,FALSE,FALSE,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_SWISS,_T("宋體"));
dc.SelectObject(font);
dc.TextOut (x,y,itemString);
}
}
void MyDrawButton::SetBkBmp(int nBmpID)
{//設置按鈕bmp位圖樣式
m_nBmpID = nBmpID;
}
void MyDrawButton::DrawBackground()
{//繪制按鈕bmp位圖,並使背景透明化
CRect winRC;
CDC* pDC=GetWindowDC();
CDC memDC;
memDC.CreateCompatibleDC(pDC);
BITMAPINFO bmpInfo;
CBitmap bmp;
GetWindowRect(&winRC);
bmp.LoadBitmap(m_nBmpID);
bmp.GetObject(sizeof(BITMAPINFO),&bmpInfo);
int nBmpCX = bmpInfo.bmiHeader.biWidth;
int nBmpCY = bmpInfo.bmiHeader.biHeight;
memDC.SelectObject(bmp);
pDC->TransparentBlt(0,0,nBmpCX,nBmpCY,&memDC,0,0,
nBmpCX,nBmpCY,RGB(14,94,157));//在窗口中繪制位圖,RGB(14,94,157)是透明色
bmp.DeleteObject();
ReleaseDC(pDC);
}
到此,按鈕的自定義重繪完成了,接下來就可以使用自己重繪的按鈕類MyDrawButton了。首先往對話框中添加一個按鈕控件,假設它的ID值為IDC_TEST。進入類向導(Class Wizard)的成員變量屬性頁,為IDC_ TEST添加一個變量m_ testButton。如下:
MyDrawButton m_testButton;
然后就可以調用MyDrawButton的方法來設置按鈕的樣式了。如下:
m_testButton.SetBkBmp(IDB_TEST);//IDB_TEST為所設置的bmp位圖資源ID。
到現在為止,按鈕類的重繪完成了,可以隨意定義自己喜歡的樣式的按鈕了。帶有自定義按鈕的對話框重繪效果圖如下:
三、總結
通過本文涉及到知識和技術的分析、設計與實現,首先我們了解了VC++應用程序尤其是MFC應用系統設計與開發的流程和解決方案。
其次,我們掌握了VC++編程技術和面向對象技術以及bug的調試技術和解決bug的能力,重點掌握了對話框應用程序界面重繪和控件重繪的知識和技術,尤其是重中之重的位圖顯示技術,同時也學會了如何設計並實現VC++應用程序主界面的設計與美化。在設計界面過程中,漸漸的學會了如何設計漂亮、美觀、友好的用戶界面。
最后,最為重要的是通過這次設計與開發,使自己懂得如何在困難重重中一步一步細心的發現問題,解決問題。另外,提高了對編程認知與總結,不斷加強編程基本功,不斷總結經驗,學習他人的優秀成果,並提高了獨立思考和解決問題的能力。知道了在軟件設計開發中對用戶界面的方向的把握和用戶心理的把握,從而開發出滿足用戶最為滿意的軟件程序。
四、項目代碼目錄結構圖
