MFC的界面真的是很难看,但是我们可以通过一些方法对它进行美化,看看一下的几个方法。
●重绘对话框
先看看效果图再上代码:
首先先在StdAfx.cpp的文件中加入代码:
class CBitmapEx:public CBitmap
{
protected:
BITMAP m_bmpStruct;
public:
CBitmapEx(){memset(&m_bmpStruct,0,sizeof(BITMAP));} ;
virtual~CBitmapEx(){} ;
const int GetWidth()const{return m_bmpStruct.bmWidth;} ;
const int GetHeight()const{return m_bmpStruct.bmHeight;} ;
bool LoadBitmapEx(UINT nIDResource)
{
if (CBitmap::LoadBitmap(nIDResource))
{
GetBitmap(&m_bmpStruct);
return true;
}
return false;
}
};
这是自己写的继承CBitmap的类。
然后,创建几个位图的对象。
CBitmapEx m_bmpTopLeft;
CBitmapEx m_bmpTopRepeat;
CBitmapEx m_bmpTopRight;
CBitmapEx m_bmpLeftRepeat;
CBitmapEx m_bmpRightRepeat;
CBitmapEx m_bmpBottomLeft;
CBitmapEx m_bmpBottomRepeat;
CBitmapEx m_bmpBottomRight;
在OnCreate函数中初始化这些对象,将准备好的位图和这些对象关联。
m_bmpTopLeft.LoadBitmapEx(DIALOG_TOP_LEFT);
m_bmpTopRepeat.LoadBitmapEx(DIALOG_TOP_REPEAT);
m_bmpTopRight.LoadBitmapEx(DIALOG_TOP_RIGHT);
m_bmpLeftRepeat.LoadBitmapEx(DIALOG_LEFT_REPEAT);
m_bmpRightRepeat.LoadBitmapEx(DIALOG_RIGHT_REPEAT);
m_bmpBottomLeft.LoadBitmapEx(DIALOG_BOTTOM_LEFT);
m_bmpBottomRepeat.LoadBitmapEx(DIALOG_BOTTOM_REPEAT);
m_bmpBottomRight.LoadBitmapEx(DIALOG_BOTTOM_RIGHT);
在OnNcPaint (WM_NCPAINT的消息映射,这个消息是用来处理frame的重绘的,具体的网址
点击打开链接)中加入如下代码:
CWindowDC mdc(this);
CDC dcMemory;
dcMemory.CreateCompatibleDC(&mdc);
CRect rcWnd;
GetWindowRect(&rcWnd);
rcWnd.OffsetRect(-rcWnd.TopLeft());
CRect rcTopLeft(rcWnd);
rcTopLeft.right=m_bmpTopLeft.GetWidth()+rcTopLeft.left;
rcTopLeft.bottom=m_bmpTopLeft.GetHeight()+rcTopLeft.top;
CBitmap *pOldBitmap=dcMemory.SelectObject(&m_bmpTopLeft);
mdc.BitBlt(rcTopLeft.left,rcTopLeft.top,rcTopLeft.Width(),rcTopLeft.Height(),
&dcMemory,0,0,SRCCOPY);
CRect rcTopRepeat(rcWnd);
rcTopRepeat.left=rcTopLeft.right;
rcTopRepeat.bottom=rcTopLeft.bottom;
rcTopRepeat.right=rcWnd.right-m_bmpTopRight.GetWidth();
dcMemory.SelectObject(&m_bmpTopRepeat);
mdc.StretchBlt(rcTopRepeat.left,rcTopRepeat.top,rcTopRepeat.Width(),rcTopRepeat.Height(),
&dcMemory,0,0,m_bmpTopRepeat.GetWidth(),m_bmpTopRepeat.GetHeight(),SRCCOPY);
CRect rcTopRight(rcWnd);
rcTopRight.left=rcTopRepeat.right;
rcTopRight.bottom=rcTopRepeat.bottom;
dcMemory.SelectObject(&m_bmpTopRight);
mdc.BitBlt(rcTopRight.left,rcTopRight.top,rcTopRight.Width(),rcTopRight.Height(),
&dcMemory,0,0,SRCCOPY);
CRect rcLeftRepeat;
rcLeftRepeat.SetRect(rcWnd.left,rcTopLeft.bottom,m_bmpLeftRepeat.GetWidth(),rcWnd.bottom-m_bmpBottomLeft.GetHeight());
dcMemory.SelectObject(&m_bmpLeftRepeat);
mdc.StretchBlt(rcLeftRepeat.left,rcLeftRepeat.top,rcLeftRepeat.Width(),rcLeftRepeat.Height(),
&dcMemory,0,0,m_bmpLeftRepeat.GetWidth(),m_bmpLeftRepeat.GetHeight(),SRCCOPY);
CRect rcRightRepeat(rcLeftRepeat);
rcRightRepeat.left=rcTopRepeat.right;
rcRightRepeat.right=rcWnd.right;
dcMemory.SelectObject(&m_bmpRightRepeat);
mdc.StretchBlt(rcRightRepeat.left,rcRightRepeat.top,rcRightRepeat.Width(),rcRightRepeat.Height(),
&dcMemory,0,0,m_bmpRightRepeat.GetWidth(),m_bmpRightRepeat.GetHeight(),SRCCOPY);
CRect rcBottomLeft(rcWnd);
rcBottomLeft.top=rcLeftRepeat.bottom;
rcBottomLeft.right=rcLeftRepeat.right;
dcMemory.SelectObject(&m_bmpBottomLeft);
mdc.BitBlt(rcBottomLeft.left,rcBottomLeft.top,rcBottomLeft.Width(),rcBottomLeft.Height(),
&dcMemory,0,0,SRCCOPY);
CRect rcBottomRepeat(rcWnd);
rcBottomRepeat.left=rcBottomLeft.right;
rcBottomRepeat.top=rcBottomLeft.top;
rcBottomRepeat.right=rcWnd.right-m_bmpBottomRight.GetWidth();
dcMemory.SelectObject(&m_bmpBottomRepeat);
mdc.StretchBlt(rcBottomRepeat.left,rcBottomRepeat.top,rcBottomRepeat.Width(),rcBottomRepeat.Height(),
&dcMemory,0,0,m_bmpBottomRepeat.GetWidth(),m_bmpBottomRepeat.GetHeight(),SRCCOPY);
CRect rcBottomRight(rcWnd);
rcBottomRight.left=rcBottomRepeat.right;
rcBottomRight.top=rcBottomRepeat.top;
dcMemory.SelectObject(&m_bmpBottomRight);
mdc.BitBlt(rcBottomRight.left,rcBottomRight.top,rcBottomRight.Width(),rcBottomRight.Height(),
&dcMemory,0,0,SRCCOPY);
dcMemory.SelectObject(pOldBitmap) ;
其实就是把图片重新画到frame上的过程。
然后在OnCtlColor(WM_CTLCOLOR的消息处理函数,这个消息是用来处理控件的颜色的,具体介绍的网址点击打开链接)中加入如下代码:
static CBrush brushDialog(RGB(240,240,240));
if (nCtlColor == CTLCOLOR_DLG)
{
return brushDialog;
}
if(nCtlColor == CTLCOLOR_STATIC)
{
pDC->SetBkColor(RGB(240,240,240));
HBRUSH m_bkBrush = ::CreateSolidBrush(RGB(240,240,240));
return m_bkBrush;
}
上面的代码是用来重绘对话框上控件的颜色的。
再在OnEraseBkgnd中重绘对话框的背景:
CRect rcClient;
CBrush brushBkgrnd(RGB(240,240,240));
GetWindowRect(&rcClient);
ScreenToClient(&rcClient);
pDC->FillRect(&rcClient, &brushBkgrnd);
再在OnNcHitTest(WM_NCHITTEST 发送到一个窗口,以确定窗口的一部分对应于一个特定的屏幕坐标,具体点击打开链接)中加入如下代码:
CPoint p(point) ;
ScreenToClient(&p) ;
if (p.y>=0&&p.y<=20)
return HTCAPTION ;
这是指定窗口的一部分为非客户区,用来拖动窗口。
当然我们也可以用上面的方法来重写一个DLG的类,这样所有的对话框就都可以变成这种效果了。
就此就完成了对话框的重绘过程,上面需要的位图可以在这里下载。运用好这些消息处理,MFC也可以制作出很好看的对话框。
●重绘按钮
自己重绘一个按钮。
首先在PreSubclassWindow中 加入如下代码:
ModifyStyle(0, BS_OWNERDRAW);
允许进行按钮的重绘工作。
然后,创建一些我们需要使用的对象,代码如下:
//按钮的外边框
CPen m_BoundryPen;
//鼠标指针置于按钮之上时按钮的内边框
CPen m_InsideBoundryPenLeft;
CPen m_InsideBoundryPenRight;
CPen m_InsideBoundryPenTop;
CPen m_InsideBoundryPenBottom;
//按钮获得焦点时按钮的内边框
CPen m_InsideBoundryPenLeftSel;
CPen m_InsideBoundryPenRightSel;
CPen m_InsideBoundryPenTopSel;
CPen m_InsideBoundryPenBottomSel;
//按钮的底色,包括有效和无效两种状态
CBrush m_FillActive;
CBrush m_FillInactive;
//按钮的状态
BOOL m_bOver; //鼠标位于按钮之上时该值为true,反之为flase
BOOL m_bTracking; //在鼠标按下没有释放时该值为true
BOOL m_bSelected; //按钮被按下是该值为true
BOOL m_bFocus; //按钮为当前焦点所在时该值为true
初始化这些对象,在构造函数中进行:
m_BoundryPen.CreatePen(PS_INSIDEFRAME | PS_SOLID, 1, RGB(0, 0, 0));
m_InsideBoundryPenLeft.CreatePen(PS_INSIDEFRAME | PS_SOLID, 3, RGB(250, 196, 88));
m_InsideBoundryPenRight.CreatePen(PS_INSIDEFRAME | PS_SOLID, 3, RGB(251, 202, 106));
m_InsideBoundryPenTop.CreatePen(PS_INSIDEFRAME | PS_SOLID, 2, RGB(252, 210, 121));
m_InsideBoundryPenBottom.CreatePen(PS_INSIDEFRAME | PS_SOLID, 2, RGB(229, 151, 0));
m_FillActive.CreateSolidBrush(RGB(223, 222, 236));
m_FillInactive.CreateSolidBrush(RGB(222, 223, 236));
m_InsideBoundryPenLeftSel.CreatePen(PS_INSIDEFRAME | PS_SOLID, 3, RGB(153, 198, 252));
m_InsideBoundryPenTopSel.CreatePen(PS_INSIDEFRAME | PS_SOLID, 2, RGB(162, 201, 255));
m_InsideBoundryPenRightSel.CreatePen(PS_INSIDEFRAME | PS_SOLID, 3, RGB(162, 189, 252));
m_InsideBoundryPenBottomSel.CreatePen(PS_INSIDEFRAME | PS_SOLID, 2, RGB(162, 201, 255));
m_bOver = m_bSelected = m_bTracking = m_bFocus = FALSE;
重写绘制按钮底色的虚函数:
void CXPButton::DoGradientFill(CDC *pDC, CRect* rect)
{
CBrush brBk[64];
int nWidth = rect->Width();
int nHeight = rect->Height();
CRect rct;
for (int i = 0; i < 64; i ++)
{
if (m_bOver)
{
if (m_bFocus)
brBk[i].CreateSolidBrush(RGB(255 - (i / 4), 255 - (i / 4), 255 - (i / 3)));
else
brBk[i].CreateSolidBrush(RGB(255 - (i / 4), 255 - (i / 4), 255 - (i / 5)));
}
else
{
if (m_bFocus)
brBk[i].CreateSolidBrush(RGB(255 - (i / 3), 255 - (i / 3), 255 - (i / 4)));
else
brBk[i].CreateSolidBrush(RGB(255 - (i / 3), 255 - (i / 3), 255 - (i / 5)));
}
}
for (i = rect->top; i <= nHeight + 2; i ++)
{
rct.SetRect(rect->left, i, nWidth + 2, i + 1);
pDC->FillRect(&rct, &brBk[((i * 63) / nHeight)]);
}
for (i = 0; i < 64; i ++)
brBk[i].DeleteObject();
}
重写绘制按钮内边框的函数:
void CXPButton::DrawInsideBorder(CDC *pDC, CRect* rect)
{
CPen *pLeft, *pRight, *pTop, *pBottom;
if (m_bSelected && !m_bOver)
{
pLeft = & m_InsideBoundryPenLeftSel;
pRight = &m_InsideBoundryPenRightSel;
pTop = &m_InsideBoundryPenTopSel;
pBottom = &m_InsideBoundryPenBottomSel;
}
else
{
pLeft = &m_InsideBoundryPenLeft;
pRight = &m_InsideBoundryPenRight;
pTop = &m_InsideBoundryPenTop;
pBottom = &m_InsideBoundryPenBottom;
}
CPoint oldPoint = pDC->MoveTo(rect->left, rect->bottom - 1);
CPen* pOldPen = pDC->SelectObject(pLeft);
pDC->LineTo(rect->left, rect->top + 1);
pDC->SelectObject(pRight);
pDC->MoveTo(rect->right - 1, rect->bottom - 1);
pDC->LineTo(rect->right - 1, rect->top);
pDC->SelectObject(pTop);
pDC->MoveTo(rect->left - 1, rect->top);
pDC->LineTo(rect->right - 1, rect->top);
pDC->SelectObject(pBottom);
pDC->MoveTo(rect->left, rect->bottom);
pDC->LineTo(rect->right - 1, rect->bottom);
pDC->SelectObject(pOldPen);
pDC->MoveTo(oldPoint);
if (m_bSelected && !m_bOver)
DrawFocusRect(pDC->m_hDC,rect);
}
添加消息响应WM_MOUSEMOVE,并且根据鼠标移动函数添加鼠标经过和离开按钮的消息:
BEGIN_MESSAGE_MAP(CXPButton, CButton)
//{{AFX_MSG_MAP(CXPButton)
ON_WM_MOUSEMOVE()
ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CXPButton::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (!m_bTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = m_hWnd;
tme.dwFlags = TME_LEAVE | TME_HOVER;
tme.dwHoverTime = 1;
m_bTracking = _TrackMouseEvent(&tme);
}
CButton::OnMouseMove(nFlags, point);
}
分别处理各种情况,将标志的值设成相应的状态:
LRESULT CXPButton::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
m_bOver = FALSE;
m_bTracking = FALSE;
InvalidateRect(NULL, FALSE);
return 0;
}
LRESULT CXPButton::OnMouseHover(WPARAM wParam, LPARAM lParam)
{
m_bOver = TRUE;
InvalidateRect(NULL);
return 0;
}
最后在DrawItem中绘制按钮:
void CXPButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
//从lpDrawItemStruct获取控件的相关信息
CRect rect = lpDrawItemStruct->rcItem;
CDC *pDC=CDC::FromHandle(lpDrawItemStruct->hDC);
int nSaveDC=pDC->SaveDC();
UINT state = lpDrawItemStruct->itemState;
POINT pt ;
TCHAR strText[MAX_PATH + 1];
::GetWindowText(m_hWnd, strText, MAX_PATH);
//画按钮的外边框,它是一个半径为5的圆角矩形
pt.x = 5;
pt.y = 5;
CPen* hOldPen = pDC->SelectObject(&m_BoundryPen);
pDC->RoundRect(&rect, pt);
//获取按钮的状态
if (state & ODS_FOCUS)
{
m_bFocus = TRUE;
m_bSelected = TRUE;
}
else
{
m_bFocus = FALSE;
m_bSelected = FALSE;
}
if (state & ODS_SELECTED || state & ODS_DEFAULT)
{
m_bFocus = TRUE;
}
pDC->SelectObject(hOldPen);
rect.DeflateRect(CSize(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE)));
//根据按钮的状态填充按钮的底色
CBrush* pOldBrush;
if (m_bOver)
{
pOldBrush = pDC->SelectObject(&m_FillActive);
DoGradientFill(pDC, &rect);
}
else
{
pOldBrush = pDC->SelectObject(&m_FillInactive);
DoGradientFill(pDC, &rect);
}
//根据按钮的状态绘制内边框
if (m_bOver || m_bSelected)
DrawInsideBorder(pDC, &rect);
pDC->SelectObject(pOldBrush);
//显示按钮的文本
if (strText!=NULL)
{
CFont* hFont = GetFont();
CFont* hOldFont = pDC->SelectObject(hFont);
CSize szExtent = pDC->GetTextExtent(strText, lstrlen(strText));
CPoint pt( rect.CenterPoint().x - szExtent.cx / 2, rect.CenterPoint().y - szExtent.cy / 2);
if (state & ODS_SELECTED)
pt.Offset(1, 1);
int nMode = pDC->SetBkMode(TRANSPARENT);
if (state & ODS_DISABLED)
pDC->DrawState(pt, szExtent, strText, DSS_DISABLED, TRUE, 0, (HBRUSH)NULL);
else
pDC->DrawState(pt, szExtent, strText, DSS_NORMAL, TRUE, 0, (HBRUSH)NULL);
pDC->SelectObject(hOldFont);
pDC->SetBkMode(nMode);
}
pDC->RestoreDC(nSaveDC);
}
到此漂亮的按钮就制作完了,如果有想直接用的朋友,可以去 这里下载。
●如果有用到PropertySheet的朋友,也可以用到上述的方法去改变界面的样式,但是有一些不同的地方。
去除属性页标题:
在sheet的OnInitDialog函数中,添加如下代码:
//去掉标题栏
DWORD style = GetStyle();
style = style & ~WS_CAPTION;
::SetWindowLong( GetSafeHwnd(), GWL_STYLE, style );
改变PropertyPage上的按钮,及和按钮在一行的标题栏的颜色:
需要我们去重写一个TabCtrl的类,并且在OnEraseBkgnd中加入如下代码:
CRect rect;
GetWindowRect(&rect);
ScreenToClient(&rect);
// CBrush brush(GetSysColor(COLOR_3DFACE)); // Tab Control背景色
CBrush brush(RGB(240,240,240));
pDC->FillRect(rect,&brush);
return TRUE;
在DrawItem里加入如下代码:
CRect rect;
GetWindowRect(&rect);
ScreenToClient(&rect);
// CBrush brush(GetSysColor(COLOR_3DFACE)); // Tab Control背景色
CBrush brush(RGB(240,240,240));
pDC->FillRect(rect,&brush);
return TRUE;
}
void COwnerDrawTabCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
TCHAR szTabText[64]={0};
TC_ITEM tci;
tci.mask = TCIF_TEXT;
tci.pszText = szTabText;
tci.cchTextMax = sizeof(szTabText)-1;
CBrush m_brushBK(RGB(240,240,240));
GetItem( lpDrawItemStruct->itemID, &tci);
CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
pDC->FillRect( &lpDrawItemStruct->rcItem, &m_brushBK);
pDC->SetBkColor( RGB(240,240,240));
if ((lpDrawItemStruct->itemState & ODS_SELECTED) &&
(lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)))
{
//Make the color of text of the selected tab to be BLUE.
pDC->SetTextColor(RGB( 0,0 , 255));
}
//! 文字的位置可能的偏移
pDC->TextOut(lpDrawItemStruct->rcItem.left+4,lpDrawItemStruct->rcItem.top+4,tci.pszText,lstrlen(tci.pszText));
最后在sheet的OnInitDialog中加入如下代码:
//更改TabCtrl颜色
m_tabOwnerDraw.SubclassWindow( GetTabControl()->m_hWnd);
m_tabOwnerDraw.ModifyStyle(0,TCS_OWNERDRAWFIXED);
希望这些美化MFC界面的小经验对大家有用。