当前位置:C++技术网 > 资讯 > 控件自绘之自定义按钮控件实作源码

控件自绘之自定义按钮控件实作源码

更新时间:2016-04-07 09:17:16浏览次数:1+次

在我们写代码的时候,控件是最常用的代码辅助工作。当然,现在你可能就是用C,C++,STL等写算法的话,等你以后工作了,你还是得和控件打招乎。今天,我们来研究一下,控件按钮的实作源码。我在《子类化方法自绘按钮-presubclasswindow函数》利用DrawFrameControl函数也很好的实作出了按钮控件。只不过,在我们现在的文章里,我们是利用纯代码来写的,而不依靠某些已封装好的函数。另外请你看看《VC++创建自定义按钮类实现的分析》。
自定义按钮的实现,说到底还是重载Create,以及继承CButton类。然后利用presubclasswindow,drawitem等函数。
下面,我们就看看详细的代码:
首先我们在派生自CButton类的CMybutton类头文件中定义代码:
BOOL Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID);
//默认颜色及边框的按钮创建函数
BOOL Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID, COLORREF color); 
//创建具有选中态及悬挂态颜色的按钮
BOOL Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID, UINT nBorder, COLORREF nColor, COLORREF sColor, COLORREF hColor, COLORREF dColor); 
// 完全指定属性的按钮创建函数
// lpszCaption - 按钮标题
// dwStyle - 按钮样式
// point - 按钮在父窗体上的坐标
// hRgn - 按钮的路径
// pParentWnd - 父窗体
// nID - 控件ID
// nBorder - 3D边框的宽度(可以是1或2(像素))
// nColor - 正常状态的颜色
// sColor - 选中状态的颜色
// hColor - 悬挂状态的颜色
// dColor - 禁用状态的颜色
UINT m_nBorder;	//边框的宽度
LONG m_lfEscapement;	//标题的位置
COLORREF m_nColor;	//普通状态时按钮的背景色
COLORREF m_sColor;	//选中状态时按钮的背景色
COLORREF m_hColor;	//悬挂状态时按钮的背景色
COLORREF m_dColor;	//禁用状态时按钮的背景色
CBitmap * m_pNormal;	//普通状态时按钮的位图
CBitmap * m_pSelected;	//选中状态时按钮的位图
CBitmap * m_pHover;	//悬挂状态时按钮的位图
CBitmap * m_pDisabled;	//禁用状态时按钮的位图
CPoint m_CenterPoint;	//标题的坐标中点
BOOL m_bMouseDown;	//标识鼠标按下
BOOL m_bHover;	//标识鼠标经过按钮
BOOL m_bCapture;	//标识鼠标被捕获
HRGN m_hRgn;	//按钮路径
BOOL m_bNeedBitmaps;	//标识状态位图是否重绘
void DrawButton(CDC * pDC, CRect * pRect, UINT state);	//将位图绘制到屏幕上
void PrepareStateBitmaps(CDC * pDC, CRect * pRect);	//准备按钮状态位图
BOOL HitTest(CPoint point);	//判断鼠标是否在按钮区域内
//	void RgnPixelWork(CDC * pDC, CRgn * pRgn);	// region pixel work - unused
void FrameRgn3D(HDC hDC, const HRGN hRgn, BOOL bSunken);	//绘制3D边框
void CheckHover(CPoint point);	//检测鼠标是否经过按钮
protected:
void PrepareNormalState(CDC * pDC, CDC * pMemDC, CRect * pRect);	//准备按钮普通状态时的位图
void PrepareSelectedState(CDC * pDC, CDC * pMemDC, CRect * pRect);	//准备按钮被选中状态时的位图
void PrepareHoverState(CDC * pDC, CDC * pMemDC, CRect * pRect);	//准备按钮悬挂状态时的位图
void PrepareDisabledState(CDC * pDC, CDC * pMemDC, CRect * pRect);	//准备按钮禁用状态时的位图
void DrawButtonCaption(HDC hDC, CRect * pRect, BOOL bEnabled, BOOL bSunken);	//绘制按钮标题
void PaintRgn(CDC * pDC, CDC * pMemDC, CBitmap * pBitmap, COLORREF color, CRect * pRect, BOOL bEnabled, BOOL bSunken);	//绘制按钮
public:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
protected:
virtual void PreSubclassWindow();
//}}AFX_VIRTUAL

protected:
//{{AFX_MSG(CMybutton)
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
在CMybutton的实现文件中:
CMybutton::CMybutton()
{
	m_nColor = GetSysColor(COLOR_BTNFACE);
	m_sColor = m_nColor;
	m_hColor = m_nColor;
	m_dColor = m_nColor;
	m_nBorder = 1;
	m_pNormal = NULL;
	m_pSelected = NULL;
	m_pHover = NULL;
	m_pDisabled = NULL;
	m_hRgn = 0;
	m_bHover = false;
	m_bCapture = false;
	m_bMouseDown = false;
	m_bNeedBitmaps = true;
}

CMybutton::~CMybutton()
{
	delete m_pNormal;
	delete m_pSelected;
	delete m_pHover;
	delete m_pDisabled;
	DeleteObject(m_hRgn);
}
BOOL CMybutton::Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID)
{
	// store region in member variable
	DeleteObject(m_hRgn);
	m_hRgn = CreateRectRgn(0, 0, 31, 31);
	CRect box(0, 0, 0, 0);
	if (m_hRgn != 0) 
		CombineRgn(m_hRgn, hRgn, 0, RGN_COPY);

	// make sure that region bounding rect is located in (0, 0)
	GetRgnBox(m_hRgn, &box);
	OffsetRgn(m_hRgn, -box.left, -box.top);
	GetRgnBox(m_hRgn, &box);

	// update position of region center for caption output
	m_CenterPoint = CPoint(box.left + box.Width() /2 , box.top + box.Height() /2);
	box.OffsetRect(point);

	return CButton::Create(lpszCaption, dwStyle, box, pParentWnd, nID);
}

BOOL CMybutton::Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID, COLORREF color)
{
	m_sColor = color;
	m_hColor = color;

	// call another constructor
	return Create(lpszCaption, dwStyle, point, hRgn, pParentWnd, nID);
}


BOOL CMybutton::Create(LPCTSTR lpszCaption, DWORD dwStyle, const CPoint point, const HRGN hRgn, CWnd* pParentWnd, UINT nID, UINT nBorder, COLORREF nColor, COLORREF sColor, COLORREF hColor, COLORREF dColor)
{
	// change default colors
	m_nBorder = nBorder;
	m_nColor = nColor;
	m_sColor = sColor;
	m_hColor = hColor;
	m_dColor = hColor;

	// call another constructor
	return Create(lpszCaption, dwStyle, point, hRgn, pParentWnd, nID);
}

void CMybutton::PreSubclassWindow() 
{
	// change window style to allow owner draw
	ModifyStyle(0, BS_OWNERDRAW | BS_PUSHBUTTON);	
	CButton::PreSubclassWindow();
}

int CMybutton::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CButton::OnCreate(lpCreateStruct) == -1)
		return -1;

	// assign new region to a window
	m_bNeedBitmaps = true;
	SetWindowRgn(m_hRgn, true);

	return 0;
}
void CMybutton::OnMouseMove(UINT nFlags, CPoint point) 
{
	// Test if mouse is above the button.
	if (!m_bMouseDown)
		CheckHover(point);

	CButton::OnMouseMove(nFlags, point);
}

void CMybutton::CheckHover(CPoint point)
{
		if (HitTest(point)) {
			if (!m_bCapture) {
				SetCapture();
				m_bCapture = true;
			}
			if (!m_bHover) {
				m_bHover = true;
				RedrawWindow();
			}
		}
		else {
			if (m_bCapture) {
				ReleaseCapture();
				m_bCapture = false;
			}
			m_bHover = false;
			RedrawWindow();
		}
}


BOOL CMybutton::HitTest(CPoint point)
{
	BOOL result = false;

	// Obtain handle to window region.
	HRGN hRgn = CreateRectRgn(0, 0, 0, 0);
	GetWindowRgn(hRgn);
	CRect rgnRect;
	GetRgnBox(hRgn, &rgnRect);

	// First check if point is in region bounding rect.
	// Then check if point is in the region in adition to being in the bouding rect.
	 result = PtInRect(&rgnRect, point)&& PtInRegion(hRgn, point.x, point.y);

	// Clean up and exit.
	DeleteObject(hRgn);
	return result;
}

BOOL CMybutton::OnEraseBkgnd(CDC* pDC) 
{
	// do not erase background
	return 1;
}

//////////////////////// DRAWING ROUTINES ////////////////////////////

void CMybutton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	// prepare DC
	CDC* pDC = CDC::FromHandle(lpDrawItemStruct -> hDC);
	CRect rect;
	GetClientRect(rect);

	// prepare bitmaps they need to be prepared
	if (m_bNeedBitmaps)
		PrepareStateBitmaps(pDC, &rect);

	// draw button to the screen
	DrawButton(pDC, &rect, lpDrawItemStruct -> itemState);
}

void CMybutton::PrepareStateBitmaps(CDC * pDC, CRect * pRect)
{
	// 准备DC
	CDC * pMemDC;
	pMemDC = new CDC;
	pMemDC -> CreateCompatibleDC(pDC);

	//为按钮的各种状态准备位图
	PrepareNormalState(pDC, pMemDC, pRect);
	PrepareSelectedState(pDC, pMemDC, pRect);
	PrepareHoverState(pDC, pMemDC, pRect);
	PrepareDisabledState(pDC, pMemDC, pRect);

	// 释放资源
	delete pMemDC; 
	m_bNeedBitmaps = false;
}


void CMybutton::PrepareNormalState(CDC * pDC, CDC * pMemDC, CRect * pRect)
{
	// prepare MYBS_NORMAL state bitmap
	delete m_pNormal;
	m_pNormal = new CBitmap;
	PaintRgn(pDC, pMemDC, m_pNormal, m_nColor, pRect, true, false);
}

void CMybutton::PrepareSelectedState(CDC * pDC, CDC * pMemDC, CRect * pRect)
{
	// prepare MYBS_SELECTED state bitmap
	delete m_pSelected;
	m_pSelected = new CBitmap;
	PaintRgn(pDC, pMemDC, m_pSelected, m_sColor, pRect, true, true);
}

void CMybutton::PrepareHoverState(CDC * pDC, CDC * pMemDC, CRect * pRect)
{
	// prepare MYBS_HOVER state bitmap
	delete m_pHover;
	m_pHover = new CBitmap;
	PaintRgn(pDC, pMemDC, m_pHover, m_hColor, pRect, true, false);
}

void CMybutton::PrepareDisabledState(CDC * pDC, CDC * pMemDC, CRect * pRect)
{
	// prepare MYBS_DISABLED state bitmap
	delete m_pDisabled;
	m_pDisabled = new CBitmap;
	PaintRgn(pDC, pMemDC, m_pDisabled, m_dColor, pRect, false, false);
}

void CMybutton::PaintRgn(CDC * pDC, CDC * pMemDC, CBitmap * pBitmap, COLORREF color, CRect * pRect, BOOL bEnabled, BOOL bSunken)
{
	// 创建位图
	pBitmap -> CreateCompatibleBitmap(pDC, pRect -> Width(), pRect -> Height());
	CBitmap * pOldBitmap = pMemDC -> SelectObject(pBitmap); 

	// 准备路径
	HRGN hRgn = CreateRectRgn(0, 0, 0, 0);
	GetWindowRgn(hRgn);

	// 用传进来的颜色填充按钮路径区域
	HBRUSH hBrush = CreateSolidBrush(color);
	pMemDC -> FillSolidRect(pRect, RGB(0, 0, 0));
	FillRgn(pMemDC -> GetSafeHdc(), hRgn, hBrush);
	DeleteObject(hBrush);

	// 绘制按钮标题及3D边框
	DrawButtonCaption(pMemDC -> GetSafeHdc(), pRect, bEnabled, bSunken);
	FrameRgn3D(pMemDC -> GetSafeHdc(), hRgn, bSunken);

	// 释放资源
	DeleteObject(hRgn);
	pMemDC -> SelectObject(pOldBitmap); 
}

void CMybutton::DrawButtonCaption(HDC hDC, CRect * pRect, BOOL bEnabled, BOOL bSunken) 
{
	//选择父窗体的字体
	int nOldMode = SetBkMode(hDC, TRANSPARENT);
	CString text;
	GetWindowText(text);
	LOGFONT lf;
	GetParent() -> GetFont() -> GetLogFont(&lf);
	HFONT hFont = CreateFontIndirect(&lf);
	
	HFONT hOldFont = (HFONT) SelectObject(hDC, hFont);

	// 计算表条输出的位置
	TEXTMETRIC tm;
	GetTextMetrics(hDC, &tm);
	CPoint p = CPoint(m_CenterPoint.x, m_CenterPoint.y + tm.tmHeight/ 2); 
	if (bSunken) 
		p.Offset(m_nBorder, m_nBorder); 

	//根据按钮的状态绘制按钮标题
	if (bEnabled ) 
	{
		if(m_bHover || m_bCapture)
		{
			SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
			SetTextAlign(hDC, TA_CENTER | TA_BOTTOM);
			TextOut(hDC, p.x, p.y, text, text.GetLength());
		}
		else
		{
			SetTextColor(hDC, GetSysColor(COLOR_3DHILIGHT));
			SetTextAlign(hDC, TA_CENTER | TA_BOTTOM);
			TextOut(hDC, p.x, p.y, text, text.GetLength());
		}
	}
	else {
		SetTextColor(hDC, GetSysColor(COLOR_3DHILIGHT));
		TextOut(hDC, p.x + 1, p.y + 1, text, text.GetLength());
		SetTextColor(hDC, GetSysColor(COLOR_3DSHADOW));
		TextOut(hDC, p.x, p.y, text, text.GetLength());
	}

	SelectObject(hDC, hOldFont);
	DeleteObject(hFont);
	SetBkMode(hDC, nOldMode);
}

void CMybutton::FrameRgn3D(HDC hDC, const HRGN hRgn, BOOL bSunken)
{
	// we need two differenr regions to keep base region and border region
	HBRUSH hBrush;
	HRGN hBaseRgn = CreateRectRgn(0, 0, 0, 0);

	COLORREF ltOuter;	//左上外侧边框的颜色
	COLORREF ltInner;	//左上内存边框的颜色
	COLORREF rbOuter;	//右下外侧边框的颜色
	COLORREF rbInner;	//右下内侧边框的颜色

	//边框颜色配置
	if (!bSunken) {
		ltOuter = GetSysColor(COLOR_3DLIGHT);
		ltInner = GetSysColor(COLOR_3DHILIGHT);
		rbOuter = GetSysColor(COLOR_3DDKSHADOW);
		rbInner = GetSysColor(COLOR_3DSHADOW);
	}
	else {
		rbInner = GetSysColor(COLOR_3DLIGHT);
		rbOuter = GetSysColor(COLOR_3DHILIGHT);
		ltInner = GetSysColor(COLOR_3DDKSHADOW);
		ltOuter = GetSysColor(COLOR_3DSHADOW);
	}

	// 移动3D边框,将其与基本区域分离
	switch (m_nBorder)
	{
	case 2:
		CombineRgn(hBaseRgn, hRgn, 0, RGN_COPY);
		OffsetRgn(hBaseRgn, 2, 2);
		CombineRgn(hBaseRgn, hRgn, hBaseRgn, RGN_DIFF);
		hBrush = CreateSolidBrush(ltInner);
		FillRgn(hDC, hBaseRgn, hBrush);
		DeleteObject(hBrush);

		CombineRgn(hBaseRgn, hRgn, 0, RGN_COPY);
		OffsetRgn(hBaseRgn, -2, -2);
		CombineRgn(hBaseRgn, hRgn, hBaseRgn, RGN_DIFF);
		hBrush = CreateSolidBrush(rbInner);
		FillRgn(hDC, hBaseRgn, hBrush);
		DeleteObject(hBrush);

		CombineRgn(hBaseRgn, hRgn, 0, RGN_COPY);
		OffsetRgn(hBaseRgn, 1, 1);
		CombineRgn(hBaseRgn, hRgn, hBaseRgn, RGN_DIFF);
		hBrush = CreateSolidBrush(ltOuter);
		FillRgn(hDC, hBaseRgn, hBrush);
		DeleteObject(hBrush);

		CombineRgn(hBaseRgn, hRgn, 0, RGN_COPY);
		OffsetRgn(hBaseRgn, -1, -1);
		CombineRgn(hBaseRgn, hRgn, hBaseRgn, RGN_DIFF);
		hBrush = CreateSolidBrush(rbOuter);
		FillRgn(hDC, hBaseRgn, hBrush);
		DeleteObject(hBrush);
		break;
	default:
		CombineRgn(hBaseRgn, hRgn, 0, RGN_COPY);
		OffsetRgn(hBaseRgn, 1, 1);
		CombineRgn(hBaseRgn, hRgn, hBaseRgn, RGN_DIFF);
		hBrush = CreateSolidBrush(ltInner);
		FillRgn(hDC, hBaseRgn, hBrush);
		DeleteObject(hBrush);

		CombineRgn(hBaseRgn, hRgn, 0, RGN_COPY);
		OffsetRgn(hBaseRgn, -1, -1);
		CombineRgn(hBaseRgn, hRgn, hBaseRgn, RGN_DIFF);
		hBrush = CreateSolidBrush(rbOuter);
		FillRgn(hDC, hBaseRgn, hBrush);
		DeleteObject(hBrush);
		break;
	}
	
	//释放资源
	DeleteObject(hBaseRgn);
}

void CMybutton::DrawButton(CDC * pDC, CRect * pRect, UINT state)
{
	// 创建内存设备
	CDC * pMemDC = new CDC;
	pMemDC -> CreateCompatibleDC(pDC);
	CBitmap * pOldBitmap;

	//获取按钮路径
	HRGN hRgn = CreateRectRgn(0, 0, 0, 0);
	GetWindowRgn(hRgn);

	//根据按钮状态为按钮选择状态位图
	if (state & ODS_DISABLED)
		pOldBitmap = pMemDC -> SelectObject(m_pDisabled);
	else {
		if (state & ODS_SELECTED)
			pOldBitmap = pMemDC -> SelectObject(m_pSelected);
		else {
			if (m_bHover)
				pOldBitmap = pMemDC -> SelectObject(m_pHover);
			else 
				pOldBitmap = pMemDC -> SelectObject(m_pNormal);
		}	
	}

	//绘制按钮的裁剪区域
	::SelectClipRgn(pDC -> GetSafeHdc(), hRgn);
	pDC -> BitBlt(0, 0, pRect -> Width(), pRect -> Height(), pMemDC, 0, 0, SRCCOPY);
	::SelectClipRgn(pDC -> GetSafeHdc(), NULL);

	//释放资源
	DeleteObject(hRgn);
	pMemDC -> SelectObject(pOldBitmap);
	delete pMemDC;

最后看看实现结果:

这就是我们自定义的按钮了。