MFC中如何给CButton添加不同状态位图


前言

如何给MFC中CButton按钮添加位图,分别在正常、单击、停留、获得焦点四种状态时显示不同位图,可以采用从CButton类派生一个新类的方式来实现。
网上有一些其他的类似的实现方式,本文重点参考了这篇文章,并做了一些修改。
https://blog.csdn.net/chen1083376511/article/details/73321840


一、环境

vs2008+Win10

二、步骤

1 、创建MFC工程

  1. 创建基于对话框的MFC工程,名称取为BitmapButton。

  2. 在对话框资源编辑器中拖入一个CButton按钮,修改ID号,Owner Draw属性修改为True,这样,这个按钮就成为了自绘控件(具有风格BS_OWNERDRAW)。自绘控件的消息路由过程如下图,子控件首先发送消息给父窗口Dialog,父窗口调用父类CWnd::OnDrawItem响应函数,如果这个子控件重写了DrawItem方法,则把消息路由给子控件类中的DrawItem;否则,将消息路由给父窗口Dilalog。为了使父窗口类不过于臃肿,本文采用了重写CButton类的方式,也就是在CMyButton中重写了DrawItem函数。
    在这里插入图片描述

  3. 准备四张位图,分别显示按钮的四种状态,添加至工程的资源中,正常、按下、获取焦点、停留状态ID号分别改为IDB_BMP_NORMAL、IDB_BMP_PRESS、IDB_BMP_HOVER、IDB_BMP_FOCUS。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2、从CButton中派生一个类

取名为CMyButton,并为这个类添加OnMouseMove、OnMouseLeave、OnMouseHover响应函数,重写DrawItem函数。详细可见下面代码

//MyButton.h

#pragma once
#include "afxwin.h"

class CMyButton : public CButton
{
public:
	CMyButton(UINT idBmpNormal,UINT idBmpPress,UINT idBmpHover,UINT idBmpFocus);
	~CMyButton(void);
	void SetBmpId(UINT idBmpNormal,UINT idBmpPress,UINT idBmpHover,UINT idBmpFocus);
protected:
	virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnMouseLeave();
	afx_msg void OnMouseHover(UINT nFlags, CPoint point);
	DECLARE_MESSAGE_MAP()
private:
	CBitmap m_bmpNormal;	//正常状态位图
	CBitmap m_bmpPress;		//按下位图
	CBitmap m_bmpHover;		//鼠标悬停位图
	CBitmap m_bmpFocus;		//获取焦点位图

	bool m_bTrackMouseEvent;	//是否跟踪鼠标状态
	bool m_bHover;				//鼠标是否悬停上方
};

实现文件为:

#include "StdAfx.h"
#include "MyButton.h"

CMyButton::CMyButton(UINT idBmpNormal,UINT idBmpPress,UINT idBmpHover,UINT idBmpFocus)
	:m_bTrackMouseEvent(true)
	,m_bHover(false)
{
	m_bmpNormal.LoadBitmap(idBmpNormal);
	m_bmpPress.LoadBitmap(idBmpPress);
	m_bmpHover.LoadBitmap(idBmpHover);
	m_bmpFocus.LoadBitmap(idBmpFocus);
}

CMyButton::~CMyButton(void)
{
}

void CMyButton::SetBmpId(UINT idBmpNormal,UINT idBmpPress,UINT idBmpHover,UINT idBmpFocus)
{
	//此处注意,CBitmap多次加载位图,程序会报错
	if(m_bmpNormal.m_hObject)
		m_bmpNormal.DeleteObject();	
	if(m_bmpPress.m_hObject)
		m_bmpPress.DeleteObject();
	if(m_bmpFocus.m_hObject)
		m_bmpFocus.DeleteObject();
	if(m_bmpHover.m_hObject)
		m_bmpHover.DeleteObject();

	m_bmpNormal.LoadBitmap(idBmpNormal);
	m_bmpPress.LoadBitmap(idBmpPress);
	m_bmpFocus.LoadBitmap(idBmpFocus);
	m_bmpHover.LoadBitmap(idBmpHover);	
}

BEGIN_MESSAGE_MAP(CMyButton, CButton)
	ON_WM_MOUSEMOVE()
	ON_WM_MOUSELEAVE()
	ON_WM_MOUSEHOVER()
END_MESSAGE_MAP()

void CMyButton::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	if(m_bTrackMouseEvent)
	{
		TRACKMOUSEEVENT cTrackEvent;
		cTrackEvent.cbSize=sizeof(TRACKMOUSEEVENT);
		cTrackEvent.dwFlags=TME_HOVER|TME_LEAVE;
		cTrackEvent.dwHoverTime=10;		//鼠标停留多久触发悬停消息
		cTrackEvent.hwndTrack=GetSafeHwnd();
		if(::_TrackMouseEvent(&cTrackEvent))	//如果调用失败,不再跟踪鼠标事件
			m_bTrackMouseEvent=false;
	}

	CButton::OnMouseMove(nFlags, point);
}

void CMyButton::OnMouseLeave()
{
	m_bHover=false;
	m_bTrackMouseEvent=true;	//重新开始跟踪
	Invalidate();				//重绘按钮
	
	CButton::OnMouseLeave();
}

void CMyButton::OnMouseHover(UINT nFlags, CPoint point)
{
	m_bTrackMouseEvent=false;	//不再跟踪,因为已经在控件范围内了
	m_bHover=true;
	Invalidate();				//重绘按钮
	CButton::OnMouseHover(nFlags, point);
}

void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	CRect rect=lpDrawItemStruct->rcItem;
	CDC* pDc=CDC::FromHandle(lpDrawItemStruct->hDC);
	int nSaveDc=pDc->SaveDC();

	CBitmap* pBmp=NULL;
	if(lpDrawItemStruct->itemState & ODS_SELECTED)	//选中
	{
		pBmp=&m_bmpPress;
	}
	else if(lpDrawItemStruct->itemState & ODS_FOCUS)	//焦点
	{
		pBmp=&m_bmpFocus;
	}
	else if(m_bHover)	//停留
	{
		pBmp=&m_bmpHover;
	}
	else				//正常
		pBmp=&m_bmpNormal;

	BITMAP bmp;
	pBmp->GetBitmap(&bmp);
	CDC dcCompatible;
	dcCompatible.CreateCompatibleDC(pDc);
	dcCompatible.SelectObject(pBmp);
	pDc->StretchBlt(0,0,rect.Width(),rect.Height(),
		&dcCompatible,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);

	pDc->RestoreDC(nSaveDc);
}

3、关于CMyButton类的必要说明

  1. _TrackMouseEven函数:当鼠标离开特定窗口时,这个函数会发出WM_MOUSELEAVE;当鼠标悬停在窗口时,这个函数会发出WM_MOUSEHOVER消息,至于悬停多久会发消息,取决于TRACKMOUSEEVENT结构中的dwHoverTime值。
  2. OnMouseMove响应函数:注意在该函数中,首先检查成员变量m_bTrackMouseEvent是否为真,然后再调用_TrackMouseEvent函数。
  3. OnMouseHover响应函数:注意在该函数中,将m_bTrackMouseEvent赋值为false,这是因为此时鼠标已经在控件范围内了,没有必要再执行_TrackMouseEvent函数。这里有个问题,为什么不再跟踪鼠标事件,却可以收到鼠标离开的事件,这是因为:只要调用一次_TrackMouseEvent函数,只有等到WM_MOUSELEAVE消息产生时,所有跟踪请求才会结束。
  4. 通过在OnMouseHover、OnMouseLeave调用Invalidate(),使框架能够调用DrawItem。

4、在对话框类中使用CMyButton

在对话框类中新增成员变量m_wndBtn,并让它和CButton按钮相关联。

// BitmapButtonDlg.h : 头文件
//

#pragma once
#include "afxwin.h"
#include "MyButton.h"


// CBitmapButtonDlg 对话框
class CBitmapButtonDlg : public CDialog
{
public:
	CBitmapButtonDlg(CWnd* pParent = NULL);	// 标准构造函数
	enum { IDD = IDD_BITMAPBUTTON_DIALOG };
protected:
	HICON m_hIcon;
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
private:
	 CMyButton m_wndBtn;		//可以实现位图切换的按钮
};

然后,在构造函数中初始化这个CMyButton类型的按钮

CBitmapButtonDlg::CBitmapButtonDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CBitmapButtonDlg::IDD, pParent)
	,m_wndBtn(IDB_BMP_NORMAL,IDB_BMP_PRESS,IDB_BMP_HOVER,IDB_BMP_FOCUS)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

实际上,CMyButton的SetBmpId接口也可以使程序在其他时候更换显示的四张位图,本实例暂未使用。

5、最终效果

在这里插入图片描述
最终可以实现:鼠标停留时、按TAB键切换焦点、单击时显示不同的位图。

总结

在以上类的基础上,可以扩展CButton的更多自定义功能。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Santiago

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值