MFC自绘控件系列-按钮PNG贴图(GDI+)

该文章介绍如何在MFC工程中通过GDI+支持PNG图片,创建自绘按钮。关键步骤包括重载DrawItem()和PreSubclassWindow()函数,以及处理WM_ERASEBKGND消息来实现控件叠加效果。代码示例展示了按钮状态切换、文字绘制和图片资源管理的方法。
摘要由CSDN通过智能技术生成

     常规的GDI自绘控件仅支持BMP图片,不支持png图片。png图片体积小,支持透明色,可以做圆角界面。自绘控件想要支持PNG图片,需要在MFC工程引入GDI+。需要注意的是MFC工程默认是不支持GDI+,需要手工引入相关的库与头文件,具体方法见我的博客相关的文章。

      自绘按钮的关键是重载DrawItem()函数,在其中贴按键底图,绘制线框,绘画文字,切换按键的按下、焦点等状态。

      第二个关键点是重载PreSubclassWindow()函数,在其中定义按键的风格,其中必须定义的风格是BS_OWNERDRAW,其它的风格酌情选用。

      如果想实现控件叠加的效果,必须拦截WM_ERASEBKGND消息,因为CButton默认擦除背景的操作是复制对话框的背景色,而不是下面的控件的皮肤。

头文件:

#pragma once
#include "pch.h"
#include <atlimage.h>

//按钮类型
enum
{
	BTN_TYPE_NORMAL = 0x10, //普通BTN
	BTN_TYPE_CHECK, // CHECK BOX
	BTN_TYPE_TWO_STATES // 2态按钮
};

class CPngButton : public CButton
{
public:
	CPngButton();
	virtual ~CPngButton();

public:
	BOOL Init(UINT nBtnType, UINT resIDNormal, UINT resIDFocus, UINT resIDPress, UINT resIDDisenable,
		UINT resIDSelect = 0, UINT resIDSFocus = 0, UINT resIDSPress = 0, UINT resIDSDisenable=0);
	BOOL GetCheckStatus(); //BTN_TYPE_CHECK
	void SetCheckStatus(BOOL isCheck); //BTN_TYPE_CHECK
	void SetText(CString text);
	void SetTextParam(Color& color, int fontSize=12, WCHAR* fontName = L"微软雅黑");

//内部实现
private:
	Image* ImageFromResource(HINSTANCE hInstance, UINT uImgID, LPCTSTR lpType);
	void ImageFree();
	void PaintParent();

	BOOL ShowImage(Graphics* graph, Image* pImage);
	Image* TypeCalcNormal(LPDRAWITEMSTRUCT lpDIS);
	Image* TypeCalcCheck(LPDRAWITEMSTRUCT lpDIS);
	Image* TypeCalc2States(LPDRAWITEMSTRUCT lpDIS);

	BOOL ShowText(Graphics* graph, WCHAR* text);
	void CalcTextPos();

	Color m_textColor;
	WCHAR m_text[256];
	Font* m_font;
	int m_fontSize;
	float m_xPos;
	float m_yPos;
	BOOL m_isInit;
	
protected:
	void CancelHover();

	afx_msg BOOL OnEraseBkgnd(CDC* pDC);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg LRESULT OnMouseHOver(WPARAM wParam, LPARAM lParam);
	afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);
	afx_msg void OnPaint();

	DECLARE_MESSAGE_MAP()

protected:
	virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
	virtual void PreSubclassWindow();

public:
	bool m_bTracked;

private:

	Image* m_imgNormal;
	Image* m_imgFocus;
	Image* m_imgPress;
	Image* m_imgSelect;
	Image* m_imgSFocus;
	Image* m_imgDisenable;
	Image* m_imgSDisenable;
	Image* m_imgSPress;

	UINT m_nBtnType;
	BOOL m_bMenuOn; //BTN类型为BTN_TYPE_MENU时,是否处于按下的状态

	BOOL m_bIsChecked;
	BOOL m_bMouseOnButton;

// 重叠按钮
private:
	#define CHILD_MAX  30
	CWnd* m_child[CHILD_MAX];
	int m_childIndex;
	void PaintChild();
public:
	void AddChild(CWnd* child);
};

     

.cpp文件

#include "pch.h"
#include "CPngButton.h"

CPngButton::CPngButton()
{
	m_isInit = FALSE;
	m_bTracked = false;
	m_bMenuOn = FALSE;

	m_imgNormal = NULL;
	m_imgFocus = NULL;
	m_imgPress = NULL;
	m_imgDisenable = NULL;
	m_imgSelect = NULL;
	m_imgSFocus = NULL;
	m_imgSPress = NULL;
	m_imgSDisenable = NULL;

	m_bIsChecked = FALSE;
	m_bMouseOnButton = FALSE;

	m_nBtnType = BTN_TYPE_NORMAL;

	m_childIndex = 0;
	for (int i = 0; i < CHILD_MAX; i++) {
		m_child[i] = NULL;
	}
	
	m_textColor = Color(255, 10, 10, 10);
	m_fontSize = 12;
	m_font = new Font(L"宋体", m_fontSize);
	m_text[0] = '\0';
	m_xPos = 0.0;
	m_yPos = 0.0;
}

void CPngButton::ImageFree()
{
	if (m_imgNormal)
	{
		delete m_imgNormal;
		m_imgNormal = NULL;
	}

	if (m_imgFocus)
	{
		delete m_imgFocus;
		m_imgFocus = NULL;
	}

	if (m_imgPress)
	{
		delete m_imgPress;
		m_imgPress = NULL;
	}

	if (m_imgSelect)
	{
		delete m_imgSelect;
		m_imgSelect = NULL;
	}

	if (m_imgSFocus)
	{
		delete m_imgSFocus;
		m_imgSFocus = NULL;
	}

	if (m_imgSPress)
	{
		delete m_imgSPress;
		m_imgSPress = NULL;
	}

	if (m_imgDisenable)
	{
		delete m_imgDisenable;
		m_imgDisenable = NULL;
	}

	if (m_imgSDisenable)
	{
		delete m_imgSDisenable;
		m_imgSDisenable = NULL;
	}
}

CPngButton::~CPngButton()
{
	ImageFree();
	if (m_font) {
		delete m_font;
		m_font = NULL;
	}
}

BOOL CPngButton::Init(UINT nBtnType, UINT resIDNormal, UINT resIDFocus, UINT resIDPress, UINT resIDDisenable,
	UINT resIDSelect, UINT resIDSFocus, UINT resIDSPress, UINT resIDSDisenable)
{
	ImageFree();

	HINSTANCE hInstance = AfxGetResourceHandle();
	m_imgNormal = ImageFromResource(hInstance, resIDNormal, "PNG");
	m_imgFocus = ImageFromResource(hInstance, resIDFocus, "PNG");
	m_imgPress = ImageFromResource(hInstance, resIDPress, "PNG");
	m_imgDisenable = ImageFromResource(hInstance, resIDDisenable, "PNG");

	if (m_imgNormal == NULL || m_imgFocus == NULL || m_imgPress == NULL || m_imgDisenable == NULL) {
		TRACE("ImageFromResource fail[%d]\n",GetLastError());
		ImageFree();
		return FALSE;
	}

	if (nBtnType == BTN_TYPE_CHECK) {
		m_imgSelect = ImageFromResource(hInstance, resIDSelect, "PNG");
		m_imgSFocus = ImageFromResource(hInstance, resIDSFocus, "PNG");
		m_imgSPress = ImageFromResource(hInstance, resIDSPress, "PNG");
		m_imgSDisenable = ImageFromResource(hInstance, resIDSDisenable, "PNG");
		if (m_imgSelect == NULL || m_imgSFocus == NULL || m_imgSPress == NULL || m_imgSDisenable == NULL) {
			TRACE("ImageFromResource fail[%d]\n", GetLastError());
			ImageFree();
			return FALSE;
		}
	}

	m_nBtnType = nBtnType;
	SetWindowPos(NULL, 0, 0, m_imgNormal->GetWidth(), m_imgNormal->GetHeight(), SWP_NOACTIVATE | SWP_NOMOVE);

	CalcTextPos();
	m_isInit = TRUE;
	return TRUE;
}

void CPngButton::SetText(CString text)
{
	MultiByteToWideChar(CP_ACP, 0, text.GetBuffer(), -1, m_text, 255);
	text.ReleaseBuffer();

	CalcTextPos();
	if (m_isInit) {
		Invalidate();
	}
}

void CPngButton::CalcTextPos()
{
	if (wcslen(m_text) > 0) {
		CRect rect;
		GetClientRect(&rect);
		m_xPos = rect.Width() / 2;
		m_yPos = (rect.Height() - m_fontSize * 2) / 2;
		m_yPos = m_yPos < 0 ? 0 : m_yPos;
	}
}

void CPngButton::SetTextParam(Color& color, int fontSize, WCHAR* fontName)
{
	m_textColor = color;
	if (m_font) {
		delete m_font;
		m_font = NULL;
	}
	m_font = new Font(fontName, fontSize);
	m_fontSize = fontSize;

	CalcTextPos();
}

BEGIN_MESSAGE_MAP(CPngButton, CButton)
	//{{AFX_MSG_MAP(CPngButton)
	ON_WM_ERASEBKGND()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_PAINT()
	ON_MESSAGE(WM_MOUSEHOVER, OnMouseHOver)
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// CPngButton message handlers

void CPngButton::OnPaint()
{
	CButton::OnPaint();
}

Image* CPngButton::TypeCalcNormal(LPDRAWITEMSTRUCT lpDIS)
{
	BOOL isPressed = (lpDIS->itemState & ODS_SELECTED);//是不是已经按下
	BOOL isFocused = (lpDIS->itemState & ODS_FOCUS);   //是不是获得了焦点
	BOOL isGrayed = (lpDIS->itemState & ODS_GRAYED);   //是不是灰化
	BOOL isDisabled = (lpDIS->itemState & ODS_DISABLED); //是不是没使能

	/*if (isPressed) {
		m_bIsChecked = !m_bIsChecked;
	}*/

	Image* image = NULL;

	if (isGrayed || isDisabled) {
		image = m_imgDisenable;
	}
	else if (isPressed) {
		image = m_imgPress;
	}else {
		if (m_bMouseOnButton) {
			image = m_imgFocus;
		}else {
			image = m_imgNormal;
		}
	}

	return image;
}

Image* CPngButton::TypeCalcCheck(LPDRAWITEMSTRUCT lpDIS)
{
	static BOOL oldPress = -1;

	BOOL isPressed = (lpDIS->itemState & ODS_SELECTED);//是不是已经按下
	BOOL isFocused = (lpDIS->itemState & ODS_FOCUS);   //是不是获得了焦点
	BOOL isGrayed = (lpDIS->itemState & ODS_GRAYED);   //是不是灰化
	BOOL isDisabled = (lpDIS->itemState & ODS_DISABLED); //是不是没使能

	//TRACE("TypeCalcCheck() P=%d F=%d gray=%d dis=%d\n", isPressed, isFocused, isGrayed, isDisabled);

	if (oldPress != isPressed) {
		oldPress = isPressed;
		if (isPressed) {
			m_bIsChecked = !m_bIsChecked;
		}
	}

	Image* image = NULL;

	if (m_bIsChecked) {
		if (isGrayed || isDisabled) {
			image = m_imgSDisenable;
		}
		else if (isPressed) {
			image = m_imgSPress;
		}
		else {
			if (m_bMouseOnButton) {
				image = m_imgSFocus;
			}
			else {
				image = m_imgSelect;
			}
		}
	}else {
		if (isGrayed || isDisabled) {
			image = m_imgDisenable;
		}
		else if (isPressed) {
			image = m_imgPress;
		}
		else {
			if (m_bMouseOnButton) {
				image = m_imgFocus;
			}
			else {
				image = m_imgNormal;
			}
		}
	}

	return image;
}

Image* CPngButton::TypeCalc2States(LPDRAWITEMSTRUCT lpDIS)
{
	BOOL isPressed = (lpDIS->itemState & ODS_SELECTED);//是不是已经按下
	BOOL isFocused = (lpDIS->itemState & ODS_FOCUS);   //是不是获得了焦点
	BOOL isGrayed = (lpDIS->itemState & ODS_GRAYED);   //是不是灰化
	BOOL isDisabled = (lpDIS->itemState & ODS_DISABLED); //是不是没使能

	/*if (isPressed) {
		m_bIsChecked = !m_bIsChecked;
	}*/

	Image* image = NULL;

	if (isGrayed || isDisabled) {
		image = m_imgDisenable;
	}else if (m_bIsChecked) {
		image = m_imgFocus;
	}else {
		image = m_imgNormal;
	}

	return image;
}

void CPngButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
	//TRACE("CPngButton::DrawItem(%p)\n",m_imgNormal);

	CDC dc;
	dc.Attach(lpDIS->hDC);

	//dc.SetBkMode(TRANSPARENT);

	Image* image = NULL;

	switch (m_nBtnType) {
	case BTN_TYPE_NORMAL:
		image = TypeCalcNormal(lpDIS);
		break;
	case BTN_TYPE_CHECK:
		image = TypeCalcCheck(lpDIS);
		break;

	case BTN_TYPE_TWO_STATES:
		image = TypeCalc2States(lpDIS);
		break;
	}

	Graphics graph(dc.GetSafeHdc());

	ShowImage(&graph, image);

	if (wcslen(m_text) > 0) {
		ShowText(&graph, m_text);
	}

	graph.ReleaseHDC(dc.GetSafeHdc());

	PaintChild();
}

BOOL CPngButton::ShowText(Graphics* graph, WCHAR *text)
{
	PointF origin(m_xPos, m_yPos);
	SolidBrush brush(m_textColor);
	StringFormat format;
	format.SetAlignment(StringAlignmentCenter);

	graph->SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);
	Status hr = graph->DrawString(text, wcslen(text), m_font, origin, &format, &brush);

	//TRACE("graph.DrawString() hr=%d\n",hr);

	if (hr != 0) {
		return FALSE;
	}

	return TRUE;
}

BOOL CPngButton::ShowImage(Graphics* graph, Image* pImage)
{
	bool bSuc = false;

	if (pImage != NULL)
	{
		CRect rcButton;
		rcButton = CRect(0, 0, m_imgNormal->GetWidth(), m_imgNormal->GetHeight());

		//Graphics graph(pDC->GetSafeHdc());
		graph->DrawImage(pImage, 0, 0, rcButton.left, rcButton.top, rcButton.Width(), rcButton.Height(), UnitPixel);
		//graph.ReleaseHDC(pDC->GetSafeHdc());
		bSuc = true;
	}

	return bSuc;
}

Image* CPngButton::ImageFromResource(HINSTANCE hInstance, UINT uImgID, LPCTSTR lpType)
{
	HRSRC hResInfo = ::FindResource(hInstance, MAKEINTRESOURCE(uImgID), lpType);
	if (hResInfo == NULL)
		return NULL; //fail
	DWORD dwSize;
	dwSize = SizeofResource(hInstance, hResInfo); //get resource size(bytes) 
	HGLOBAL hResData;
	hResData = ::LoadResource(hInstance, hResInfo);
	if (hResData == NULL)
		return NULL; //fail
	HGLOBAL hMem;
	hMem = ::GlobalAlloc(GMEM_MOVEABLE, dwSize);
	if (hMem == NULL) {
		::FreeResource(hResData);
		return NULL;
	}
	LPVOID lpResData, lpMem;
	lpResData = ::LockResource(hResData);
	lpMem = ::GlobalLock(hMem);
	::CopyMemory(lpMem, lpResData, dwSize); //copy memory
	::GlobalUnlock(hMem);
	::FreeResource(hResData); //free memory

	IStream* pStream;
	HRESULT hr;
	hr = ::CreateStreamOnHGlobal(hMem, TRUE, &pStream);//create stream object
	Image* pImage = NULL;
	if (SUCCEEDED(hr)) {
		pImage = Image::FromStream(pStream);//get GDI+ pointer
		pStream->Release();
	}
	::GlobalFree(hMem);
	return pImage;
}

void CPngButton::PreSubclassWindow()
{
	ModifyStyle(0, BS_OWNERDRAW);
	ModifyStyle(WS_CLIPCHILDREN, 0);
	ModifyStyle(WS_CLIPSIBLINGS, 0);

	CButton::PreSubclassWindow();
}

BOOL CPngButton::OnEraseBkgnd(CDC* pDC)
{
	//TRACE("CPngButton::OnEraseBkgnd()\n");
	return TRUE;
}

void CPngButton::CancelHover()
{
	if (m_bMouseOnButton)
	{
		m_bMouseOnButton = FALSE;
		Invalidate();
	}
}

void CPngButton::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: Add your message handler code here and/or call default
	CWnd* wndUnderMouse = NULL;
	CWnd* wndActive = this;
	TRACKMOUSEEVENT csTME;
	CButton::OnMouseMove(nFlags, point);
	ClientToScreen(&point);
	wndUnderMouse = WindowFromPoint(point);//获得点下面的组件
	// If the mouse enter the button with the left button pressed then do nothing
	if (nFlags & MK_LBUTTON && m_bMouseOnButton == FALSE)
		return;
	if (wndUnderMouse && wndUnderMouse->m_hWnd == m_hWnd && wndActive)
	{
		if (!m_bMouseOnButton)
		{
			m_bMouseOnButton = TRUE;
			Invalidate();
			csTME.cbSize = sizeof(csTME);
			csTME.dwFlags = TME_LEAVE;
			csTME.hwndTrack = m_hWnd;
			::_TrackMouseEvent(&csTME);//发送鼠标离开的消息,见我之前的博客
		}
	}
	else {
		CancelHover();
	}

	CButton::OnMouseMove(nFlags, point);
}

void CPngButton::OnLButtonDown(UINT nFlags, CPoint point)
{
	CButton::OnLButtonDown(nFlags, point);
}

void CPngButton::OnLButtonUp(UINT nFlags, CPoint point)
{
	CButton::OnLButtonUp(nFlags, point);
}

LRESULT CPngButton::OnMouseHOver(WPARAM wParam, LPARAM lParam)
{
	//鼠标放上去时
	//TRACE("OnMouseHOver()\n");
	
	return 0;
}
LRESULT CPngButton::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
	//鼠标移开时
	//TRACE("OnMouseLeave()\n");
	CancelHover();
	return 0;
}

void CPngButton::PaintParent()
{
	CRect   rect;
	GetWindowRect(&rect);
	GetParent()->ScreenToClient(&rect);
	GetParent()->InvalidateRect(&rect);
}

void CPngButton::AddChild(CWnd* child)
{
	if (m_childIndex < CHILD_MAX) {
		m_child[m_childIndex++] = child;
	}else {
		TRACE("AddChild()error:full\n");
	}
}

void CPngButton::PaintChild()
{
	for (int i = 0; i < CHILD_MAX; i++) {
		if (m_child[i]) {
			m_child[i]->Invalidate();
		}
	}
}

BOOL CPngButton::GetCheckStatus() //BTN_TYPE_CHECK
{
	return m_bIsChecked;
}

void CPngButton::SetCheckStatus(BOOL isCheck) //BTN_TYPE_CHECK
{
	m_bIsChecked = isCheck;
	if (m_isInit) {
		Invalidate();
	}
}

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MFC绘图控件是Microsoft Foundation Class库中的一个工具,它可以用来进行高速图表绘制。该控件具有快速绘图的能力,可以实时更新,并且支持鼠标响应。 MFC绘图控件可以通过MFC绘图示例(MFC Drawing Demo)来演示。该示例程序提供了一个基础的绘图框架,通过这个框架可以创建和绘制各种图形,如直线、矩形、圆等。使用示例程序可以学习和了解MFC绘图控件的基本用法和功能。 在示例程序中,鼠标响应是一个重要的功能。通过对鼠标事件的处理,可以实现一些交互式的操作,比如绘制自由曲线、移动和编辑已有图形等。例如,可以通过鼠标左键点击来创建一个点,通过拖动鼠标可以绘制直线或矩形等。 MFC绘图控件的高速性是其突出的特点之一。在图表绘制中,通常需要频繁地进行绘制操作,如果绘制速度太慢,会导致界面卡顿,影响用户体验。而MFC绘图控件通过利用底层硬件加速和绘图缓冲等技术,可以快速地进行图表绘制,保证了绘制效率和流畅度。 总而言之,MFC绘图控件是一个功能强大的工具,可以用于高速图表绘制。通过鼠标响应,可以实现更多的交互式操作。示例程序提供了一个演示和学习的平台,用户可以深入了解和使用MFC绘图控件的各种功能。 ### 回答2: high-speed charting control(MFC绘图控件)是一种用于在MFC应用程序中绘制快速和高效图表的控件。该控件的作用是帮助开发者在应用程序中实现各种类型的图表,如折线图、柱状图、饼图等。通过使用这个控件,开发者可以轻松地创建具有交互性和响应式的图表。 MFC绘图demo是一个包含了鼠标响应功能的示例程序。该示例程序展示了如何使用high-speed charting control控件来创建图表,并在用户与图表交互时响应鼠标的操作。 在这个demo中,开发者可以看到如何通过添加数据点来绘制折线图。通过鼠标的点击和拖动,用户可以添加、删除和移动数据点,实时更新图表的显示。该demo还演示了如何通过鼠标的滚动来放大和缩小图表的显示范围。 除了基本的绘制功能,MFC绘图demo还提供了一些其他功能,如图表的标注、背景设置和图表的保存与导入等。这些功能使得开发者可以根据实际需要进行图表的定制和扩展。 总结起来,high-speed charting control(MFC绘图控件)以及包含鼠标响应的MFC绘图demo是一种帮助开发者快速绘制高效图表的控件和示例程序。开发者可以根据自己的需求,使用这个控件和示例程序来实现各种类型的交互式图表功能。 ### 回答3: 高速绘图控件MFC(Microsoft Foundation Classes)框架中的一个重要的组件之一,它可以用于在应用程序中实现图表的绘制和展示。MFC绘图Demo是一个示例程序,展示了如何使用MFC绘图控件来实现图表绘制,并通过鼠标响应来交互操作。 MFC绘图控件使用了高效的绘图算法,能够快速地绘制图表数据,并具有良好的性能。它支持多种类型的图表,如曲线图、柱状图、饼状图等,并提供了丰富的参数和选项,可以用于定制图表的外观和行为。 MFC绘图Demo通过鼠标响应实现了图表的交互操作。用户可以使用鼠标来选择图表中的数据点、拖动图表的视图、放大缩小图表等。通过鼠标操作,用户可以方便地查看和分析图表数据,实现更直观的数据展示和操作。 在MFC绘图Demo中,通过监听鼠标事件,获取鼠标的坐标信息,并根据鼠标的操作来改变图表的显示和行为。例如,当用户点击某个数据点时,可以在图表上显示该数据点的数值或详细信息;当用户拖动图表视图时,可以实现平移图表的功能;当用户使用滚轮滚动鼠标时,可以实现图表的放大缩小。 总之,MFC高速绘图控件MFC绘图Demo是用于实现图表绘制和交互操作的工具和示例程序,它们可以帮助开发人员快速构建和定制图表功能,并提供高效的数据展示和操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值