VC++界面编程之--自定义CButton(按钮)皮肤

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/renstarone/article/details/11177051

在VC++ WTL编程中,利用CustomDraw自绘的方法,可以很轻松地实现按钮的自绘效果。
我利用了此方法,制作了一个CCustomButton类,实现了如下效果的控件,其支持普通的按钮风格,并也支持CheckBox的风格。


实现方法:
1. 首先继承CButton模板类及CCustomDraw的模板类。

class CCustomButton :	public CWindowImpl<CCustomButton, CButton>,
						public CCustomDraw<CCustomButton>

2. 在消息泵中,将CButton的自绘消息,加入至CCustomDraw消息链中,那样你就可以收到两个自绘通知了,他们是OnPreErase和OnPrePaint。
BEGIN_MSG_MAP(CCustomButton)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
		MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
		CHAIN_MSG_MAP_ALT( CCustomDraw< CCustomButton >, 1 )
	END_MSG_MAP()

这两个自绘通知,会以函数的形式通知你,类似于虚函数:
我们会在OnPreErase里,执行自绘操作,所以返回CDRF_SKIPDEFAULT,以让窗口跳过默认自绘。

DWORD OnPreErase(int /*uCtrl*/, LPNMCUSTOMDRAW lpNMCD)
	{
		// If button image is null, do nothing.
		if (m_bmpNormal.get() == NULL || m_bmpHover.get() == NULL || 
			m_bmpSelected.get() == NULL || m_bmpDisable.get() == NULL)
		{
			return CDRF_DODEFAULT;
		}

		EnterCriticalSection(&m_Lock);

		CDCHandle dc		= lpNMCD->hdc;
		BOOL bIsHot			= ((lpNMCD->uItemState & CDIS_HOT) != 0);
		BOOL bIsDisabled	= ((lpNMCD->uItemState & CDIS_DISABLED) != 0);
		BOOL bIsSelected	= ((lpNMCD->uItemState & CDIS_SELECTED) != 0);

		// Draw image depend on button status and style.
		Graphics g(dc);
		if ( bIsSelected || (m_bChecked && m_bCheckedStyle) )
		{
			m_nLast = m_nSelected;
			g.DrawImage(m_bmpSelected.get(), 0, 0);
		}
		else if (bIsHot)
		{
			g.DrawImage(m_bmpHover.get(), 0, 0);
		}
		else if(bIsDisabled)
		{
			if (m_bCheckedStyle)
			{
				// If button is checked style and been disabled, draw last status image.
				DrawLastStatusImage(g, m_nLast);
			}
			else
			{
				g.DrawImage(m_bmpDisable.get(), 0, 0);
			}
		}
		else
		{
			m_nLast = m_nNormal;
			g.DrawImage(m_bmpNormal.get(), 0, 0);       
		}

		LeaveCriticalSection(&m_Lock);

		return CDRF_SKIPDEFAULT;
	}

另外一个自绘通知OnPrePaint,我们暂不用到,让他返回默认值即可:
DWORD OnPrePaint(int /*uCtrl*/, LPNMCUSTOMDRAW /*lpNMCD*/)
	{
		return CDRF_DODEFAULT;
	}

3. 在OnPreErase里,我们需要获取按钮的四种状态:正常、热点、选中和禁用。默认是正常状态,所以我们只用处理:热点、选中和禁用。OnPreErase也会返回自绘的HDC图形描绘句柄,这也是我们需要用到的。
	CDCHandle dc		= lpNMCD->hdc;
		BOOL bIsHot			= ((lpNMCD->uItemState & CDIS_HOT) != 0);
		BOOL bIsDisabled	= ((lpNMCD->uItemState & CDIS_DISABLED) != 0);
		BOOL bIsSelected	= ((lpNMCD->uItemState & CDIS_SELECTED) != 0);

4. 判断当前按钮状态时需要注意:热点和选中这两个状态。当鼠标选中了按钮,那么按钮会返回CDIS_SELECTED的状态给你,但由于鼠标这时还停留在按钮上面,那么反馈的状态里面也包含了CDIS_HOT在里面。
所以我们这里先判断按钮是否是选中的,如果是选中的,就只描绘选中的图片,并不再描绘热点状态的图片。按钮描绘逻辑如下:

		// Draw image depend on button status and style.
		Graphics g(dc);
		if ( bIsSelected || (m_bChecked && m_bCheckedStyle) )
		{
			m_nLast = m_nSelected;
			g.DrawImage(m_bmpSelected.get(), 0, 0);
		}
		else if (bIsHot)
		{
			g.DrawImage(m_bmpHover.get(), 0, 0);
		}
		else if(bIsDisabled)
		{
			if (m_bCheckedStyle)
			{
				// If button is checked style and been disabled, draw last status image.
				DrawLastStatusImage(g, m_nLast);
			}
			else
			{
				g.DrawImage(m_bmpDisable.get(), 0, 0);
			}
		}
		else
		{
			m_nLast = m_nNormal;
			g.DrawImage(m_bmpNormal.get(), 0, 0);       
		}

5. 在MFC编程中,如果按钮是CheckBox类型,那么点击该按钮时,你必须发送一个SetCheck的消息,来通知按钮切换状态,我这里做了处理,不用再对按钮发送SetCheck的消息了。处理逻辑是:只要收到WM_LBUTTONDOWN消息时,就进行SetCheck。当然这里的SetCheck只是切换图片而已。
LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if (m_bCheckedStyle)
		{
			if (IsChecked())
			{
				m_bChecked = FALSE;
			}
			else
			{
				m_bChecked = TRUE;           
			}
		}

		bHandled = FALSE;
		return 0;
	}
6.最后一点,因为你的按钮是自绘的,有时你也想设置自己想要的字体。所以我们要处理WM_SETFONT消息,当设置字体时,重画按钮:
LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		m_Font.DeleteOldFont();
		CPaintDC dc(m_hWnd);
		m_Font.m_Font = new Font(dc, (HFONT) wParam);
		DrawAllButtons();

		bHandled = FALSE;
		return 0;
	}

本文免费工程下载链接:http://download.csdn.net/detail/renstarone/6218415

没有更多推荐了,返回首页