MFC 定时器轮询实现按住按钮进度条增加(鼠标悬停/长按检测)

本文详细讲解如何在 MFC 对话框中实现按钮长按检测。主要内容包括:① 为什么 OnMouseMove 在鼠标进入按钮后会失效;② 定时器轮询的设计思路;③ GetCursorPosGetAsyncKeyStatePtInRect 等关键 API 的使用;④ 进度条控件的实时更新;⑤ 部分代码及注释。

思路一:(未能成功)

利用MFC的ON_WM_MOUSEMOVE()消息及其响应函数OnMouseMove()结合使用,当鼠标移动时,会不停触发ON_WM_MOUSEMOVE()消息,然后不断调用其响应函数,响应函数内填写相关逻辑代码,大致内容为:获取当前鼠标坐标,与按钮所在区域不断对比,若鼠标在按钮区域内,则返回TRUE,让按钮内文本变为:"鼠标来了",(验证了这个后续再写按住和进度条增加)

响应函数代码如下:

cpp 复制代码
void CMouse_Press_DetectDlg::OnMouseMove(UINT nFlags, CPoint point)
{
    CPoint screenPt = point;
    ClientToScreen(&screenPt);
    
    CRect rcBtn;
    GetDlgItem(IDC_BUTTON1)->GetWindowRect(&rcBtn);
    
    CPoint clientPt = point;
    // 【诊断】输出坐标数值
    CString dbg;
    dbg.Format(_T("client=(%d,%d) screen=(%d,%d) btn=(%d,%d,%d,%d) in=%d"),
        clientPt.x, clientPt.y,
        screenPt.x, screenPt.y,
        rcBtn.left, rcBtn.top, rcBtn.right, rcBtn.bottom,
        rcBtn.PtInRect(screenPt));
    
    SetWindowText(dbg);  // 临时显示在标题栏
	
   
    if (rcBtn.PtInRect(screenPt))
    {
        SetDlgItemText(IDC_BUTTON1, _T("鼠标来了"));
    }
    else
    {
        SetDlgItemText(IDC_BUTTON1, _T("按钮"));
    }
    CDialog::OnMouseMove(nFlags, point);
}

此处需要注意的是:OnMouseMovepoint 参数是客户区坐标(大窗口) ,而 GetWindowRect 返回的是屏幕坐标 ,两者坐标系不同,必须统一。故需要 CPoint screenPt = point;

ClientToScreen(&screenPt);这样将窗口坐标转化成屏幕坐标。

PtInRect:判断点是否在矩形区域内(包括左、上边界,不包括右、下边界)

理想很美好,现实很残酷。这段代码逻辑上是没什么问题的,但是执行时会出现如下问题:

原始按钮名设定位Button1,在窗口外鼠标活动时,文本仍为Button1,工作正常;

当鼠标移动到窗口内后,文本变成"按钮",仍然正常。

但当鼠标移动到按钮上方时,按钮名称不变,仍为"按钮"二字。

根据显示在标题栏中的坐标信息显示,当鼠标移动到按钮上方时,除非鼠标离开按钮区域,否则坐标不会再变化,即并未不断触发响应函数。

这是因为:鼠标消息(如 WM_MOUSEMOVE)的目标窗口光标下方的窗口决定,当鼠标在对话框客户区运动时,消息正常发送,响应函数正常执行。

而当鼠标移动到按钮上方时,按钮是一个独立的子窗口,目标窗口转换为按钮,鼠标消息发送给了按钮,按钮默认不处理这个消息,故不会执行大窗口消息的响应函数OnMouseMove(),自然也不会触发函数里更新标题栏数据,更新按钮文本的代码。

直接解决方案:(未实现)

直接重写Button类,或者强制设置鼠标消息接收目标,后续可能会尝试,会填坑

思路二:(已经实现)

绕过MFC的消息机制,直接采用定时器进行轮询,不依赖鼠标消息的触发,自然也无需担心响应函数是否响应的问题。

具体就是设置一个定时器,固定时间间隔触发,触发执行逻辑其实和上面的是一样的,具体执行为

cpp 复制代码
void CMouse_Press_DetectDlg::OnTimer(UINT_PTR nID)
{

	CPoint screenPt;
	GetCursorPos(&screenPt);
	
	CRect rcBtn;
	GetDlgItem(IDC_BUTTON1)->GetWindowRect(&rcBtn);
	
	if (rcBtn.PtInRect(screenPt))
		SetDlgItemText(IDC_BUTTON1, _T("鼠标来了"));
	else
            SetDlgItemText(IDC_BUTTON1, _T("按钮"));
}

需要注意的是,定时器需要初始化,故初始化代码为:SetTimer(1, 50, NULL);放置于InitDialog中,消息为:ON_WM_TIMER(),头文件定义OnTimer时需要在返回类型前面加上afx_msg,不过也可留可不留

afx_msg 是 MFC 的文档标记宏,展开后是空的,只用来告诉阅读代码的人"这是一个消息处理函数",去掉也不影响程序运行。但为了遵循 MFC 规范和类向导工具识别,建议保留

然后去实现长按按钮让进度条增长,具体思路如下:

由于原生Button类不存在按住这个状态,故要不还是重写这个类,要不就是去找寻其他方案,这里我选择的是找其他方案:检测鼠标左键状态,当鼠标在按钮上时,然后鼠标左键按下时,触发进度条增长效果,定时器函数实现如下:

cpp 复制代码
void CMouse_Press_DetectDlg::OnTimer(UINT_PTR nID)
{
	CPoint screenPt;
	GetCursorPos(&screenPt);
	
	CRect rcBtn;
	GetDlgItem(IDC_BUTTON1)->GetWindowRect(&rcBtn);

    CPoint clientPt = screenPt;
    // 【诊断】输出坐标数值
	CString dbg;
	dbg.Format(_T("client=(%d,%d) screen=(%d,%d) btn=(%d,%d,%d,%d) in=%d"),
		clientPt.x, clientPt.y,
		screenPt.x, screenPt.y,
		rcBtn.left, rcBtn.top, rcBtn.right, rcBtn.bottom,
		rcBtn.PtInRect(screenPt));
    
	SetWindowText(dbg);
	
	if (rcBtn.PtInRect(screenPt))
		SetDlgItemText(IDC_BUTTON1, _T("鼠标来了"));
	else
            SetDlgItemText(IDC_BUTTON1, _T("按钮"));

	BOOL bLeftPressed = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
	
	int iPos = m_Progress_Slow.GetPos();
	if (rcBtn.PtInRect(screenPt) && bLeftPressed)
	{
		if (iPos < 100)
		{
			iPos++;
			m_Progress_Slow.SetPos(iPos);
		}
		SetDlgItemText(IDC_BUTTON1, _T("按住中"));
	}
}

此时就可以完成,按住按钮,进度条不断增加的效果了。但是逻辑上仍然存在漏洞:

判断语句里写的是:鼠标左键按下,且鼠标在按钮范围内,就可以实现增长,那么满足这个条件的共有两个情况,第一种:想要的情况,在按钮内按住按钮,进度条不断增长;第二种:非常规情况,我在按钮外按下左键,然后在一直按着的情况下将光标移动到按钮上,同样可以实现增长,很明显第二种不是我们想要的,还需要再打一些补丁。

未完待续......

相关推荐
syker13 小时前
AIFerric深度学习框架:自研全栈AI基础设施的技术全景
开发语言·c++
xvhao201313 小时前
单源、多源最短路
数据结构·c++·算法·深度优先·动态规划·图论·图搜索算法
笑鸿的学习笔记14 小时前
qt-C++语法笔记之Qt Graphics View 框架中的类型辨析完全指南
c++·笔记·qt
山居秋暝LS14 小时前
安装C++版opencv和opencv_contrib
开发语言·c++·opencv
谭欣辰15 小时前
LCS(最长公共子序列)详解
开发语言·c++·算法
Cando学算法15 小时前
鸽笼原理(抽屉原理)
c++·算法·学习方法
郝学胜-神的一滴16 小时前
跨平台动态库与头文件:从原理到命名的深度解析
linux·c++·程序人生·unix·cmake
代码中介商16 小时前
C++ 仿函数(Functor)深度解析:从基础到应用
开发语言·c++
王老师青少年编程16 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串基础】:[NOIP 2018 普及组] 标题统计
c++·字符串·csp·高频考点·信奥赛·专项训练·标题统计
冯诺依曼的锦鲤17 小时前
从零实现高并发内存池:TCMalloc 核心架构拆解
c++·学习·算法·架构