<span style="color:#990000">---把时髦的技术挂在嘴边,还不如把过时的技术记在心里。</span>
先来看看两幅图:
下图一是早期的Windows系统中常见的用于选项设置的窗体
下图二是MS Office Word的选项对话框
默认情况下用MFC的CPropertySheet和CPropertyPage创建的属性页就如图一的样子,要实现图二的样子需要做一些额外的工作:
技术及工具集:VS2010,MFC,GDI+
一、首先设计自绘按钮类CCustomButton
#pragma once
/*
* author: hels
* Date: 2013-10-01
* Ddescription: 自绘按钮类,继承至CBitmapButton。在创建一个位图按钮控件时,
设置BS_OWNERDRAW,这样,Windows就会为该按钮发送WM_MEASUREITEM和
WM_DRAWITEM消息,由框架处理这些消息并维护按钮的外观。
*/
// CCustomButton
class CCustomButton : public CBitmapButton
{
public:
CCustomButton();
virtual ~CCustomButton();
public:
void Init(UINT uNormal,
UINT uHover,
UINT uDown,
UINT uDisable,
LPCTSTR lpType,
HMODULE hModule = NULL);
void Check(BOOL bCheck);
void Enable(BOOL bEnable);
//设置按钮所在对话框的背景图片及位置,方便绘制按钮没有Focus和Selected时的状态
void SetDlgBG(Bitmap* DlgBg , CRect btnArea);
void SetText(CString strText){ m_strText = strText; }
void SetFontName(CString strName){ m_strFontName = strName; }
void SetTextColor(Color crText){ m_crText = crText; }
void SetBackgroundColor(Color crBackground){ m_crBackground = crBackground; }
public:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
static BOOL ImageFromIDResource(UINT nID, LPCTSTR lpType, Image * &pImg, HINSTANCE hModule = NULL);
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnMouseLeave();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
protected:
Image *m_pNormalImg;
Image *m_pHoverImg;
Image *m_pDownImg;
Image *m_pDisableImg;
UINT m_uNormal;
UINT m_uHover;
UINT m_uDown;
UINT m_uDisable;
UINT m_uState;
BOOL m_bCheck;
BOOL m_bEnable;
BOOL m_bHover;
Color m_crBackground;
Color m_crText;
CString m_strText;
CString m_strFontName;
CRect m_rtArea;
Image *m_pDlgBGImg;
};
CCustomButton关键实现:
void CCustomButton::Init( UINT uNormal, UINT uHover, UINT uDown, UINT uDisable, LPCTSTR lpType, HMODULE hModule /*= NULL*/ )
{
ImageFromIDResource(uNormal, lpType, m_pNormalImg, hModule);
ImageFromIDResource(uHover, lpType, m_pHoverImg, hModule);
ImageFromIDResource(uDown, lpType, m_pDownImg, hModule);
ImageFromIDResource(uDisable, lpType, m_pDisableImg, hModule);
m_uNormal = uNormal;
m_uHover = uHover;
m_uDown = uDown;
m_uDisable = uDisable;
}
void CCustomButton::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_uState = ODS_SELECTED;
CBitmapButton::OnLButtonDown(nFlags, point);
}
void CCustomButton::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_uState = ODS_FOCUS;
CBitmapButton::OnLButtonUp(nFlags, point);
}
void CCustomButton::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (m_uState & ODS_SELECTED)
{
return;
}
else
{
m_uState = ODS_FOCUS;
}
//默认情况下,窗口是不响应WM_MOUSELEAVE和WM_MOUSEHOVER消息的,
//所以要使用_TrackMouseEvent函数来激活这两个消息。
//调用这个函数后,当鼠标在指定窗口上停留超过一定时间或离开窗口后,
//该函数会Post这两个消息到指定窗口
TRACKMOUSEEVENT csTME;
csTME.cbSize = sizeof(csTME);
csTME.dwFlags = TME_LEAVE;
csTME.hwndTrack = m_hWnd;
::TrackMouseEvent(&csTME);
//这里自己实现Hover的记录,不需要WM_MOUSEHOVER消息,因为
//WM_MOUSEHOVER时不需要更新按钮状态和图片,完全不需要管它
//只处理进入和离开时的图片切换即可
if (m_bHover == FALSE)
{
//这里使用RedrawWindow而不用InvalidateRect等,是希望马上进行刷新操作,对于这两个函数以及WM_PAINT的关系,下面两个网址有详细的解释
//http://baike.baidu.com/link?url=iBXyaBZaId7DEMTtnOhUymJTKXloSl0SRV310SqEvdcGRMetqYk05cvKTV3RgYNzIhgby8YnSOTGbNM9jpOUwK
//http://hi.baidu.com/aidfan/item/93e062758f50602ad7a89c38
RedrawWindow();
m_bHover = TRUE;
}
}
void CCustomButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CBitmap bitmap;
CDC memDC;
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
pDC->SetBkMode(TRANSPARENT);
CRect rect = lpDrawItemStruct->rcItem;
bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
memDC.CreateCompatibleDC(pDC);
memDC.SelectObject(&bitmap);
Graphics graphics(pDC->GetSafeHdc());
Bitmap *button = new Bitmap(rect.Width(), rect.Height());
Graphics graphicsBtn(button);
graphicsBtn.Clear(m_crBackground);
RectF rectDst(0.0f, 0.0f, rect.Width(), rect.Height());
if(m_pDlgBGImg)
{
RectF rectBtnArea(m_rtArea.left, m_rtArea.top,m_rtArea.Width(), m_rtArea.Height());
graphicsBtn.DrawImage(m_pDlgBGImg,rectDst,m_rtArea.left,
m_rtArea.top,m_rtArea.Width(), m_rtArea.Height(),UnitPixel);
}
if (FALSE == m_bEnable)
{
if (m_pDisableImg)
{
graphicsBtn.DrawImage(m_pDisableImg, rectDst);
}
}
else if (m_bCheck || ODS_SELECTED == m_uState)
{
graphicsBtn.DrawImage(m_pDownImg, rectDst);
}
else if (ODS_FOCUS == m_uState)
{
graphicsBtn.DrawImage(m_pHoverImg, rectDst);
}
else
{
graphicsBtn.DrawImage(m_pNormalImg, rectDst);
}
if (m_strText.GetLength() > 0)
{
WCHAR* pWc = m_strText.AllocSysString();
WCHAR* pFontName = m_strFontName.AllocSysString();
Gdiplus::Font font(pFontName, 9);
SysFreeString(pFontName);
StringFormat strFormat;
strFormat.SetAlignment(StringAlignmentCenter);
strFormat.SetLineAlignment(StringAlignmentCenter);
RectF rcBound;
graphicsBtn.MeasureString(pWc,-1,&font,PointF(0,0), &rcBound);
rectDst.Y += 1.0;
SolidBrush br(m_crText);
rectDst.Offset(0, 2);
graphicsBtn.DrawString(pWc, -1, &font, rectDst, &strFormat, &br);
SysFreeString(pWc);
}
bitmap.DeleteObject();
memDC.DeleteDC();
graphics.DrawImage(button, 0, 0);
}
void CCustomButton::OnMouseLeave()
{
// TODO: Add your message handler code here and/or call default
m_uState = 0 ;
m_bHover = FALSE ;
RedrawWindow() ;
}
二、设计Button和每个选项对话框的管理类CCustomPropertyPage,简单设计一下,只是为了方便示例
class CCustomPropertyPage
{
public:
CCustomPropertyPage(void);
~CCustomPropertyPage(void);
public:
UINT m_uID;
CRect m_rtArea;
CCustomButton* m_pPageBtn;
CPropertyPageEx* m_pPropertyPage;
UINT m_uDlgID;
};
--------------------------------------------------------
CCustomPropertyPage::CCustomPropertyPage(void)
{
m_pPageBtn = NULL;
m_pPropertyPage = NULL;
}
CCustomPropertyPage::~CCustomPropertyPage(void)
{
if(m_pPageBtn)
{
m_pPageBtn->DestroyWindow();
delete m_pPageBtn;
}
if(m_pPropertyPage)
{
m_pPropertyPage->DestroyWindow();
delete m_pPropertyPage;
}
}
三、设计并实现选项对话框COptionsDialog类
以下列出关键部分代码:
CRect rect;
GetClientRect(&rect);
m_pDlgBGImage = new Bitmap(rect.Width(), rect.Height());
Graphics graphicsBg(m_pDlgBGImage);
graphicsBg.Clear(Color::White);
//贴一个渐变的背景并绘制分割框
Pen pen(Color(128, 128, 128));
Image* bgImage;
CCustomButton::ImageFromIDResource(IDB__BACKGROUND, _T("PNG"), bgImage, theApp.m_hInstance);
TextureBrush bgTextureBrush(bgImage);
rect = m_rtButtons;
graphicsBg.FillRectangle(&bgTextureBrush, rect.left, rect.top, rect.Width(), rect.Height());
rect.InflateRect(1, 1, 1, 1);
graphicsBg.DrawRectangle(&pen, rect.left, rect.top, rect.Width() -1, rect.Height() -1);
rect = m_rtPropPage;
graphicsBg.FillRectangle(&bgTextureBrush, rect.left, rect.top, rect.Width(), rect.Height());
rect.InflateRect(1,1,1,1);
graphicsBg.DrawRectangle(&pen, rect.left, rect.top, rect.Width()-1, rect.Height()-1 ) ;
//初始化按钮,这里准备了两个PNG按钮图片,分别用于按钮处于按下和悬停状态
CCustomButton* btn0 = new CCustomButton;
btn0->Init(IDB_BTN_NORMAL, IDB_BTN_HOVER, IDB_BTN_DOWN, IDB_BTN_DOWN,
_T("PNG"), theApp.m_hInstance);
btn0->SetText(_T("常用"));
CCustomPropertyPage *page0 = new CCustomPropertyPage;
page0->m_uID = 0;
page0->m_pPageBtn = btn0;
page0->m_uDlgID = IDD_DIALOG1;
//page0->m_pPropertyPage = new CPropertyPageEx(IDD_DIALOG1);
m_PropPageArray.push_back(page0);
CCustomButton* btn1 = new CCustomButton;
btn1->Init(IDB_BTN_NORMAL, IDB_BTN_HOVER, IDB_BTN_DOWN, IDB_BTN_DOWN,
_T("PNG"), theApp.m_hInstance);
btn1->SetText(_T("显示"));
CCustomPropertyPage *page1 = new CCustomPropertyPage;
page1->m_uID = 1;
page1->m_pPageBtn = btn1;
page1->m_uDlgID = IDD_DIALOG2;
//page1->m_pPropertyPage = new CPropertyPageEx(IDD_DIALOG2);
m_PropPageArray.push_back(page1);
CCustomButton* btn2 = new CCustomButton;
btn2->Init(IDB_BTN_NORMAL, IDB_BTN_HOVER, IDB_BTN_DOWN, IDB_BTN_DOWN,
_T("PNG"), theApp.m_hInstance);
btn2->SetText(_T("自定义"));
CCustomPropertyPage *page2 = new CCustomPropertyPage;
page2->m_uID = 2;
page2->m_pPageBtn = btn2;
page2->m_uDlgID = IDD_DIALOG1;
//page2->m_pPropertyPage = new CPropertyPageEx(IDD_DIALOG1);
m_PropPageArray.push_back(page2);
CCustomButton* btn3 = new CCustomButton;
btn3->Init(IDB_BTN_NORMAL, IDB_BTN_HOVER, IDB_BTN_DOWN, IDB_BTN_DOWN,
_T("PNG"), theApp.m_hInstance);
btn3->SetText(_T("加载项"));
CCustomPropertyPage *page3 = new CCustomPropertyPage;
page3->m_uID = 3;
page3->m_pPageBtn = btn3;
page3->m_uDlgID = IDD_DIALOG2;
//page3->m_pPropertyPage = new CPropertyPageEx(IDD_DIALOG2);
m_PropPageArray.push_back(page3);
CCustomButton* btn4 = new CCustomButton;
btn4->Init(IDB_BTN_NORMAL, IDB_BTN_HOVER, IDB_BTN_DOWN, IDB_BTN_DOWN,
_T("PNG"), theApp.m_hInstance);
btn4->SetText(_T("信任中心"));
CCustomPropertyPage *page4 = new CCustomPropertyPage;
page4->m_uID = 4;
page4->m_pPageBtn = btn4;
page4->m_uDlgID = IDD_DIALOG1;
//page4->m_pPropertyPage = new CPropertyPageEx(IDD_DIALOG1);
m_PropPageArray.push_back(page4);
//设置按钮位置
CRect rt(m_rtButtons);
rt.right -= 4 ;
rt.left += 4 ;
rt.top += 4 ;
rt.bottom = rt.top + 30 ;
//按钮创建,注意指定BS_OWNERDRAW标识
for(int i = 0; i < m_PropPageArray.size(); ++i)
{
if(m_PropPageArray.at(i)->m_pPageBtn)
{
//保存按钮区域
m_PropPageArray.at(i)->m_rtArea = rt;
//创建按钮
m_PropPageArray.at(i)->m_pPageBtn->Create(NULL,
WS_CHILD|WS_VISIBLE|BS_OWNERDRAW,
rt,
this,
ID_BTN_PAGE0+i) ;
rt.top = rt.bottom + 2 ;
rt.bottom = rt.top + 30 ;
}
}
//设置按钮背景图片及按钮的位置
for(int i = 0; i < m_PropPageArray.size(); ++i)
{
if(m_PropPageArray.at(i)->m_pPageBtn)
{
m_PropPageArray.at(i)->m_pPageBtn->SetDlgBG(m_pDlgBGImage, m_PropPageArray.at(i)->m_rtArea);
}
}
下面看一下效果图:
