MFC自绘-WzdButton按钮类

本文介绍了一个自定义按钮控件的设计与实现过程,包括如何加载按钮图片、设置字体颜色及响应鼠标事件等。该控件支持图片拉伸、自绘背景及不同状态下的图片显示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这次来说说在窗口类里用到的按钮类的设计。
还是先看头文件:

#include "WzdImage.h"

class CWzdButton : public CButton
{
    DECLARE_DYNAMIC(CWzdButton)

protected:
    bool           m_bExpand;        // 能否拉伸
    bool           m_isHovering;     // 是否悬停
    COLORREF       m_crTextColor;    // 字体颜色
    CWzdImage      m_imageButton;    // 按钮图片

public:
    CWzdButton();
    virtual ~CWzdButton();

    // 加载位图
    bool SetButtonImage(LPCTSTR pszFileName, bool bExpandImage = false);
    bool SetButtonImage(HINSTANCE hInstance, LPCTSTR pszResourceName, bool bExpandImage = false);
    bool SetTextColor(COLORREF crTextColor);    // 设置字体颜色
    bool FixButtonSize();    // 调整按钮大小

protected:
    virtual void PreSubclassWindow();   // 子类化
    virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);    // 界面绘画函数

    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg LRESULT OnMouseLeave(WPARAM wparam, LPARAM lparam);
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);

    DECLARE_MESSAGE_MAP()
};

同样需要加载图片,因此包含WzdImage.h。
m_bExpand控制按钮图片能否根据按钮大小拉伸,m_bHovering标记鼠标是否悬停。

接着先看看构造函数,默认不拉伸、没有悬停、字体颜色为黑色。

CWzdButton::CWzdButton()
: m_bExpand(false)
, m_isHovering(false)
, m_crTextColor(RGB(0,0,0))
{
}

同样有两种加载图片的方式,资源加载和路径加载,还有设置字体颜色的接口。

bool CWzdButton::SetButtonImage(LPCTSTR pszFileName, bool bExpandImage/*=false*/)
{
    if (NULL == pszFileName) return false;

    // 加载位图
    m_bExpand = bExpandImage;
    if (!m_imageButton.IsNull())
    {
        m_imageButton.DestroyImage();
    }
    m_imageButton.LoadImage(pszFileName);

    if (!m_bExpand)
        FixButtonSize();  // 调整按钮大小
    if (GetSafeHwnd())
        Invalidate(FALSE);

    return true;
}

bool CWzdButton::SetButtonImage(HINSTANCE hInstance,LPCTSTR pszResourceName, bool bExpandImage/*=false*/)
{
    if (NULL == pszResourceName) return false;

    // 加载位图
    m_bExpand = bExpandImage;
    if (!m_imageButton.IsNull())
    {
        m_imageButton.DestroyImage();
    }
    m_imageButton.LoadImage(hInstance,pszResourceName);

    if (!m_bExpand)
        FixButtonSize(); // 调整按钮大小
    if (GetSafeHwnd())
        Invalidate(FALSE);

    return true;
}

bool CWzdButton::SetTextColor(COLORREF crTextColor)
{
    m_crTextColor = crTextColor;
    if (GetSafeHwnd()) Invalidate(FALSE);

    return true;
}

FixButtonSize(),用于在按钮不拉伸时,改变按钮的大小。为什么按钮背景图宽度要除去4呢?默认采用的图片都是4种按钮背景(正常、悬停、点击、无效)水平排列,所以按钮大小就是图片大小的四分之一。

bool CWzdButton::FixButtonSize()
{
    if (!m_imageButton.IsNull() && GetSafeHwnd())
    {
        SetWindowPos(NULL, 0, 0, m_imageButton.GetWidth() / 4, m_imageButton.GetHeight(), SWP_NOMOVE);
        return true;
    }
    return false;
}

可以看到有PreSubclassWindow(),它是在按钮不是动态创建的时候,在创建之前会调用到这个函数。在这里只改变风格,让按钮支持自绘。

void CWzdButton::PreSubclassWindow()
{
    SetButtonStyle(GetButtonStyle() | BS_OWNERDRAW);
    CButton::PreSubclassWindow();
}

那动态创建的时候自然就是在OnCreate()里改变风格。

int CWzdButton::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (-1 == CButton::OnCreate(lpCreateStruct)) return -1;
    SetButtonStyle(GetButtonStyle() | BS_OWNERDRAW);

    return 0;
}

最关键的就是重写DrawItem()绘制按钮。先绘制背景,因为在WzdDialog窗口中设置了WS_CLIPCHILDREN 属性,窗口不对子控件背景进行重绘。而这里使用的是PNG透明图片,不自绘背景会是默认画刷的颜色,而我们需要窗口的背景色(只支持单色背景)。接着是根据按钮状态绘画不同的图像,最后绘画文字。

void CWzdButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);

    CRect rcClient;
    GetClientRect(rcClient);

    //CRect ParRect;
    //GetWindowRect(&ParRect);
    //GetParent()->ScreenToClient(&ParRect);
    // 获取父窗口的背景,自己绘制背景,防止窗口背景重绘时的闪烁
    CDC* BkDc= GetParent()->GetDC();
    pDC->BitBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
         BkDc, 0, 0, SRCCOPY);

    bool bDisable = !!((lpDrawItemStruct->itemState & ODS_DISABLED));
    bool bButtonDown = !!((lpDrawItemStruct->itemState & ODS_SELECTED));
    if (!m_imageButton.IsNull())
    {
        // 计算位图位置
        int nWidth = m_imageButton.GetWidth() / 4;
        int nDrawPos = 0;
        if (bDisable)
            nDrawPos = nWidth * 3;
        else if (bButtonDown)
            nDrawPos = nWidth * 2;
        else if (m_isHovering)
            nDrawPos = nWidth * 1;

        // 绘画按钮
        if (!m_bExpand)
            m_imageButton.DrawImage(pDC , 0, 0, rcClient.Width(),
            rcClient.Height(), nDrawPos, 0);
        else
            m_imageButton.DrawImage(pDC, 0, 0, rcClient.Width(),
            rcClient.Height(), nDrawPos, 0, nWidth, m_imageButton.GetHeight());
    }

    // 绘画字体
    CString strText;
    GetWindowText(strText);
    rcClient.top+=1;
    pDC->SetBkMode(TRANSPARENT);

    if (bDisable)
        pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
    else
        pDC->SetTextColor(m_crTextColor);

    pDC->DrawText(strText, strText.GetLength(), rcClient,
        DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
}

响应鼠标移动,重载OnMouseMove。m_isHovering是鼠标悬停在按钮上,TrackMouseEvent那段代码是因为MouseLeave的消息发送存在问题,需要自己来触发MouseLeave消息,实现鼠标离开时m_isHovering状态的改变。

void CWzdButton::OnMouseMove(UINT nFlags, CPoint point)
{
    if (!m_isHovering)
    {
        m_isHovering = true;
        Invalidate(FALSE);
        // 注册消息
        TRACKMOUSEEVENT TrackMouseEvent;
        TrackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
        TrackMouseEvent.dwFlags = TME_LEAVE;
        TrackMouseEvent.hwndTrack = GetSafeHwnd();
        TrackMouseEvent.dwHoverTime = HOVER_DEFAULT;
        _TrackMouseEvent(&TrackMouseEvent);
    }

    CButton::OnMouseMove(nFlags, point);
}

void CWzdButton::OnMouseLeave()
{
    m_isHovering = false;
    Invalidate(FALSE);

    CButton::OnMouseLeave();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值