前言
如何给MFC中CButton按钮添加位图,分别在正常、单击、停留、获得焦点四种状态时显示不同位图,可以采用从CButton类派生一个新类的方式来实现。
网上有一些其他的类似的实现方式,本文重点参考了这篇文章,并做了一些修改。
https://blog.csdn.net/chen1083376511/article/details/73321840
一、环境
vs2008+Win10
二、步骤
1 、创建MFC工程
-
创建基于对话框的MFC工程,名称取为BitmapButton。
-
在对话框资源编辑器中拖入一个CButton按钮,修改ID号,Owner Draw属性修改为True,这样,这个按钮就成为了自绘控件(具有风格BS_OWNERDRAW)。自绘控件的消息路由过程如下图,子控件首先发送消息给父窗口Dialog,父窗口调用父类CWnd::OnDrawItem响应函数,如果这个子控件重写了DrawItem方法,则把消息路由给子控件类中的DrawItem;否则,将消息路由给父窗口Dilalog。为了使父窗口类不过于臃肿,本文采用了重写CButton类的方式,也就是在CMyButton中重写了DrawItem函数。
-
准备四张位图,分别显示按钮的四种状态,添加至工程的资源中,正常、按下、获取焦点、停留状态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类的必要说明
- _TrackMouseEven函数:当鼠标离开特定窗口时,这个函数会发出WM_MOUSELEAVE;当鼠标悬停在窗口时,这个函数会发出WM_MOUSEHOVER消息,至于悬停多久会发消息,取决于TRACKMOUSEEVENT结构中的dwHoverTime值。
- OnMouseMove响应函数:注意在该函数中,首先检查成员变量m_bTrackMouseEvent是否为真,然后再调用_TrackMouseEvent函数。
- OnMouseHover响应函数:注意在该函数中,将m_bTrackMouseEvent赋值为false,这是因为此时鼠标已经在控件范围内了,没有必要再执行_TrackMouseEvent函数。这里有个问题,为什么不再跟踪鼠标事件,却可以收到鼠标离开的事件,这是因为:只要调用一次_TrackMouseEvent函数,只有等到WM_MOUSELEAVE消息产生时,所有跟踪请求才会结束。
- 通过在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的更多自定义功能。