You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MFC C++中CButton鼠标悬停判断异常,求改按钮边框方法

MFC实现按钮鼠标悬停修改边框的问题解决

问题代码

用户原代码中,鼠标悬停判断失效,总是进入else分支:

BEGIN_MESSAGE_MAP(CPQVDirect2DControlsDlg, CDialogEx)
    ON_WM_TIMER()
    ON_WM_PAINT()
    ON_WM_DRAWITEM()
    ON_WM_ERASEBKGND()
    ON_WM_QUERYDRAGICON()

    ON_BN_CLICKED(IDC_BUTTON1, &CPQVDirect2DControlsDlg::OnBnClickedTestButton)
    ON_BN_CLICKED(IDC_CHECK1, &CPQVDirect2DControlsDlg::OnBnClickedTestCheckbox)
    ON_BN_CLICKED(IDOK, &CPQVDirect2DControlsDlg::OnBnClickedOk)
    ON_NOTIFY(NM_CUSTOMDRAW, IDC_SLIDER1, &CPQVDirect2DControlsDlg::OnNMCustomdrawSlider1)
    ON_BN_CLICKED(IDC_BUTTON3, &CPQVDirect2DControlsDlg::OnBnClickedButton3)
    ON_BN_CLICKED(IDC_BUTTON2, &CPQVDirect2DControlsDlg::OnBnClickedButton2)
 
    ON_WM_MOUSEMOVE() // 已添加
END_MESSAGE_MAP()

void CPQVDirect2DControlsDlg::OnMouseMove(UINT nFlags, CPoint point)
{
    CRect rect;
    CButton* pButton = static_cast<CButton*>(GetDlgItem(IDC_BUTTON1));

    if (pButton != nullptr && pButton->GetSafeHwnd() != nullptr) {
        pButton->GetClientRect(&rect);
        pButton->ScreenToClient(&point); // 这里坐标转换错误

        if (rect.PtInRect(point)) {
            int i = 10;
            pButton->Invalidate();
        }
        else {
            int i = 20;
            pButton->Invalidate();
        }
    }
 
    CDialogEx::OnMouseMove(nFlags, point);
}

核心错误:坐标转换逻辑错误

OnMouseMove的point参数是对话框客户区坐标,而非屏幕坐标。直接调用pButton->ScreenToClient(&point)会把对话框坐标当成屏幕坐标转换,导致坐标匹配完全错误,所以永远进入else分支。

完整解决方案

步骤1:修正鼠标位置判断逻辑

把按钮的屏幕坐标转换为对话框客户区坐标,再判断鼠标是否在按钮范围内:

void CPQVDirect2DControlsDlg::OnMouseMove(UINT nFlags, CPoint point)
{
    CButton* pButton = static_cast<CButton*>(GetDlgItem(IDC_BUTTON1));
    if (pButton && pButton->GetSafeHwnd()) {
        CRect btnRect;
        pButton->GetWindowRect(&btnRect); // 获取按钮的屏幕坐标
        this->ScreenToClient(&btnRect);   // 转换为对话框客户区坐标
        
        bool bHover = btnRect.PtInRect(point);
        if (bHover != m_bBtnHover) {
            m_bBtnHover = bHover;
            pButton->Invalidate(); // 仅状态变化时重绘,避免频繁刷新
        }
    }

    // 启用鼠标离开跟踪
    static bool bTracking = false;
    if (!bTracking) {
        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(TRACKMOUSEEVENT);
        tme.dwFlags = TME_LEAVE;
        tme.hwndTrack = this->m_hWnd;
        _TrackMouseEvent(&tme);
        bTracking = true;
    }

    CDialogEx::OnMouseMove(nFlags, point);
}

步骤2:添加悬停状态变量与鼠标离开处理

  1. 在对话框类头文件中添加成员变量:
private:
    bool m_bBtnHover; // 记录按钮是否处于悬停状态
    virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); // 重绘按钮
  1. 在对话框构造函数初始化变量:
CPQVDirect2DControlsDlg::CPQVDirect2DControlsDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_PQVDIRECT2DCONTROLS_DIALOG, pParent)
{
    m_bBtnHover = false;
    // 其他初始化代码...
}
  1. 添加WM_MOUSELEAVE消息映射与处理函数:
// 消息映射中添加
ON_WM_MOUSELEAVE()

// 实现函数
void CPQVDirect2DControlsDlg::OnMouseLeave()
{
    if (m_bBtnHover) {
        m_bBtnHover = false;
        CButton* pButton = static_cast<CButton*>(GetDlgItem(IDC_BUTTON1));
        if (pButton && pButton->GetSafeHwnd()) {
            pButton->Invalidate();
        }
    }
    CDialogEx::OnMouseLeave();
}

步骤3:设置按钮自绘样式并实现自定义绘制

  1. 在资源编辑器中选中IDC_BUTTON1,将Owner draw属性设为True(启用自绘)。
  2. 实现DrawItem函数自定义按钮外观:
void CPQVDirect2DControlsDlg::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    if (lpDrawItemStruct->CtlID != IDC_BUTTON1) {
        CDialogEx::DrawItem(lpDrawItemStruct);
        return;
    }

    CDC dc;
    dc.Attach(lpDrawItemStruct->hDC);
    CRect rect = lpDrawItemStruct->rcItem;
    UINT itemState = lpDrawItemStruct->itemState;

    // 绘制背景
    COLORREF bgColor;
    if (itemState & ODS_SELECTED) {
        bgColor = RGB(200, 200, 200); // 按下状态背景
    } else if (m_bBtnHover) {
        bgColor = RGB(230, 230, 230); // 悬停状态背景
    } else {
        bgColor = RGB(240, 240, 240); // 默认背景
    }
    dc.FillSolidRect(rect, bgColor);

    // 绘制边框
    COLORREF borderColor = m_bBtnHover ? RGB(0, 120, 215) : RGB(192, 192, 192);
    dc.Draw3dRect(rect, borderColor, borderColor);

    // 绘制按钮文本
    CString strText;
    GetDlgItemText(IDC_BUTTON1, strText);
    dc.SetBkMode(TRANSPARENT);
    dc.DrawText(strText, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

    dc.Detach();
}

关键说明

  • 坐标转换必须保证所有矩形和点处于同一坐标系(要么全是屏幕坐标,要么全是父窗口客户区坐标)。
  • 启用BS_OWNERDRAW样式是自定义按钮外观的前提,否则系统会接管按钮绘制。
  • _TrackMouseEvent用于捕获鼠标离开对话框的事件,确保按钮能恢复默认状态。

内容的提问来源于stack exchange,提问作者user2951849

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 02:25:16