VC++界面美化---模仿MS Office 选项对话框

本文介绍如何使用MFC自绘技术实现类似MS Office Word的选项对话框,包括自绘按钮类的设计与实现,以及选项对话框的整体布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<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);
		}
	}

下面看一下效果图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值