Windows平台鼠标按下标题栏的阻塞问题研究(使用Qt框架)

这篇具有很好参考价值的文章主要介绍了Windows平台鼠标按下标题栏的阻塞问题研究(使用Qt框架)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Windows平台鼠标按下标题栏的阻塞问题研究

以下内容是Windows平台特有问题,其他平台可以忽略。
一直以来使用Qt开发桌面程序,拖拽移动窗口时,偶尔会发现明显的“掉帧”,以为是机器性能或者Qt框架的机制引起的刷新异常便没有在意。最近在使用QTimer定时在QWidget上渲染视频时,才发现比想象的更严重。

经过测试当鼠标按住标题栏不动时,画面会卡500ms;当鼠标右键按住标题栏不抬起,整个画面卡住直到抬起才恢复。跟刘大师和群友沟通后,确认了这个问题确实存在。

Windows的坑

网络上几乎没有这个问题的讨论,可能是无边框设计盛行,用户也不会去按住标题栏不动,也就没有什么反馈。所以一开始考虑是Qt的问题,便从Qt源码入手,找出问题所在。

Qt源码也是使用Windows的消息处理过程来处理消息,当一个消息没有被处理时,会调用Win32的默认处理过程 DefWindowProc:

// qwindowscontext.cpp
extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    ...
    const bool handled = QWindowsContext::instance()->windowsProc(hwnd, message, et, wParam, lParam, &result, &platformWindow);
	...
    if (!handled)
        result = DefWindowProc(hwnd, message, wParam, lParam);
    ...
}

因为调试断点命中时,鼠标状态会更改,一开始没发现这块的异常,就陷入了误区,精力全放在了找阻塞代码,无果。最后一点一点打印时间,发现 DefWindowProc 在处理 WM_NCLBUTTONDOWN 和 WM_NCRBUTTONDOWN 时,会阻塞在该函数内,左键会最多等待500ms返回,右键则一直阻塞。经过三天的调试,最终得出个惊人的结论:居然是Windows自己的问题

如何解决

DefWindowProc 毕竟不开源,也只能尝试自己处理 WM_NCLBUTTONDOWN 、 WM_NCRBUTTONDOWN 和相关消息的逻辑,避开 DefWindowProc 的调用。

所以重写 QWidget::nativeEvent,或者使用 QAbstractNativeEventFilter 做全局的过滤,在消息的特定场景下,直接处理并返回true。

目前的解决方案完全基于推理,基于 Win10 19044 版本测试,不保证解决所有场景。同时,本文不提供完整源码,需要自行实现!

主要的消息参数

  1. WM_NCRBUTTONDOWN 和 WM_NCRBUTTONUP

    右键相对比较简单,这两个消息的 wParam 参数表示是在非客户区什么元素上触发的点击,来源于WM_NCHITTEST 的返回值,这是个更早一点的消息。

    Windows标题栏的右键只有一个表现,是在鼠标抬起时弹出菜单。经过测试可以在WM_NCRBUTTONUP时发送 WM_CONTEXTMENU 消息解决:

    PostMessage(msg->hwnd, WM_CONTEXTMENU, 0, msg->lParam);
    

    这里 msg->lParam 是 WM_NCRBUTTONUP 消息的参数,表示坐标。最终表现跟默认会有些差异,不过可以接受。

    (一开始是考虑是用 GetSystemMenu 获取系统菜单并触发显示,但不知道怎么初始化菜单状态)

  2. WM_NCLBUTTONDOWN 和 WM_NCLBUTTONUP

    左键会非常复杂, 这里涉及到Windows鼠标移动窗口时到底做了什么(实际也只能猜测)。通常标题栏分为三大区域,即wParam参数代表的类型:

    • 左侧程序图标位置

      wParam为HTSYSMENU,在此点击会触发菜单,跟右键菜单一样,但位置固定。

      该区域点击不会产生阻塞,不需要处理。

    • 右侧按钮区域

      wParam有多个值,主要是HTMINBUTTON、 HTMAXBUTTON、HTCLOSE三个。HTHELP比较少,图标是问号。当按钮抬起时,触发对应的功能。

      这些按钮的点击,都有对应的命令。通过手动发送 WM_SYSCOMMAND 消息,附带对应按钮的命令参数即可,可以参考文档。 如:

      PostMessage(msg->hwnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
      

      这部分的逻辑,需要判断按下、抬起的wParam是不是一致,按下时需要缓存数据。

    • 中间拖拽区域

      wParam为HTCAPTION,主要实现移动窗口,拖拽到桌面边界会有最大化等布局效果。

      虽然有SC_MOVE实现移动逻辑,但该命令没有最大化等布局效果。所以只能走 DefWindowProc 的逻辑。这里做个猜测,当收到左键按下后,进入 DefWindowProc 阻塞,内部会拦截后续消息;当收到移动消息时,进入拖拽逻辑。此过程会延迟500ms,有可能是为了等待双击消息执行最大化(仅仅是猜测)。

      所以考虑了一种方案是,当鼠标在该区域按下,记录必要的状态,直接返回true;当之后收到 WM_NCMOUSEMOVE 的时候,向窗口发送上次收到的 WM_NCLBUTTONDOWN (按下)消息参数和此次的 WM_NCMOUSEMOVE(移动)参数,标记一下,只发送一次;窗口会再次收到对应按下和移动的消息,但不做任何处理。

      流程大体如下:
      Windows平台鼠标按下标题栏的阻塞问题研究(使用Qt框架)
      发送的左键按下消息、移动消息会被窗口再次处理,这时候走默认逻辑,就能正确触发拖拽移动逻辑。

      需要注意的是,在该区域拦截 WM_NCLBUTTONDOWN ,窗口不会激活,需要调用激活窗口的接口。但是在按钮区域,却能自行激活(都是什么鬼设计)。

      这里面仍然有一些奇怪的问题,放到后面一并说明。

其他问题

上面的方案基本是解决了主要的交互, 由于整个需要缓存一些状态和参数来模拟Windows本身的行为,所以必须要在合适的时机给清除掉。另外,用户也总是会有奇怪的操作。但最大的问题是,不要指望Windows会给你想要的消息!!

  1. 关于按键顺序

  2. 关于移动方案

    移动方案有个细节问题,我假设在按下鼠标左键,默认行为会等待 WM_NCMOUSEMOVE消息然后触发移动,所以模拟发送消息。但表现是窗口没有立刻移动,等继续移动鼠标才会。而Windows默认行为能保证立刻移动。

  3. 关于鼠标抬起

    当拦截按下消息后,鼠标抬起能收到相关UP消息。但实际的默认行为,应该是将UP消息吞掉了。对于左键,取而代之的是WM_EXITSIZEMOVE,即结束移动;右键因为触发了菜单,会出现 WM_INITMENUPOPUP、WM_ENTERMENULOOP 等消息。

    当鼠标左键在右侧按钮区域按下,或者右键在任何区域按下,移动鼠标能移动到窗口之外,然后抬起,也没有相应的UP消息,只有 WM_NCMOUSELEAVE,即鼠标离开了非客户区。

    所以一些清除状态的代码得在这些消息里处理。

  4. 关于右键菜单

    通常右键弹出菜单后,如果点击菜单外区域,菜单会自动关闭。

    但如果此时直接按住拖拽区域,执行移动窗口呢?实际按下鼠标左键的同时,会收到WM_NCMOUSEMOVE,此时必须需要忽略该消息,等下次收到时再进行模拟发送消息,否则应该是导致了 DefWindowProc 内部出现了异常,整个状态都乱了。

    解决方案参考问题2。

  5. 关于多窗口

    如果程序有多窗口A和B,A处于激活状态,B处于非激活状态,此时在B窗口标题栏拖拽区域按下,又是类似问题3,虽然也有些特性消息,但都不好代表这种操作。

    我个人在 WM_WINDOWPOSCHANGING 时做个标记,忽略一次WM_NCMOUSEMOVE消息。理论上WM_ACTIVATE比较准确。

  6. 关于窗口退出激活状态

    这个比较好理解,窗口处于非激活状态,那必然没有鼠标按下等,需要完全清理状态。

总结

以上就是针对标题栏鼠标按下时阻塞的研究,解决了大部分场景,应该还有特殊情况没有考虑到。不过总归是个小众问题,估计也到此为止了。文章来源地址https://www.toymoban.com/news/detail-506715.html

到了这里,关于Windows平台鼠标按下标题栏的阻塞问题研究(使用Qt框架)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Qt 鼠标按下移动释放事件

    QEvent::MouseButtonPress ​ 鼠标按下时,触发该事件,它对应的子类是 QMouseEvent QEvent::MouseMove ​ 鼠标移动时,触发该事件,它对应的子类是 QMouseEvent QEvent::MouseButtonRelease ​ 鼠标释放时,触发该事件,它对应的子类是 QMouseEvent 自定义一个标签控件 LabelX ,让它继承自 QLabel ,然后

    2024年01月22日
    浏览(107)
  • Qt编写的小软件:一个模拟按键按下和鼠标(左键)按下的小工具

    最近玩SLG游戏的时候有大量对剧情推进无意义的对话想要跳过的时候只能狂按空格键或者狂点鼠标,还好本人好歹是程序员,于是写了个小工具来处理。 下载地址:Qt编写的模拟鼠标按下和按键按下的小工具-C++文档类资源-CSDN下载 上面的资源包含打包好的程序和代码。 界面

    2024年02月11日
    浏览(48)
  • Qt 之按钮鼠标 悬浮、按下、松开后的效果

    本文介绍了Qt中的按钮实现响应鼠标悬浮、按下、松开后的效果,在三种状态下,按钮改变不同的背景图片。 方式1:通过修改样式表的方式去实现; 方式2:通过继承QPushButton去实现一个自定义的按钮; 方式3:在主界面中给按钮安装事件过滤器的方式去实现 这里尽量不要有

    2024年02月10日
    浏览(46)
  • Qt获取鼠标移动事件,窗口内任意位置按下鼠标左键拖动窗口

    重写窗口的两个事件函数mousePressEvent和mouseMoveEvent即可: 在mousePressEvent 中,按下鼠标左键时,记录窗口坐标,其中窗口坐标的计算是由鼠标事件获取到鼠标在整个屏幕中的坐标(ev-globalpos()),然后再使用pos()获取到鼠标在窗口内的相对位置,两者之差就是窗口在整个屏幕上

    2024年02月12日
    浏览(60)
  • VBA高级应用30例应用2:MouseMove鼠标左键按下并移动鼠标事件

    《VBA高级应用30例》(版权10178985),是我推出的第十套教程,教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开,这套教程案例与理论结合,紧贴“实战”,并做“战术总结”,以便大家能很好的应用。教程的目的是要求大家 在实际工作中分发VBA程序,写好的

    2024年04月27日
    浏览(41)
  • Qt音视频开发37-识别鼠标按下像素坐标

    在和视频交互过程中,用户一般需要在显示视频的通道上点击对应的区域,弹出对应的操作按钮,将当前点击的区域或者绘制的多边形区域坐标或者坐标点集合,发送出去,通知其他设备进行处理。比如识别到很多人脸,用户单击某个人脸后指定对该人脸进行详细的信息查询

    2023年04月16日
    浏览(47)
  • Qt Windows 去掉标题栏后 最大化窗口时任务栏被窗口遮住问题

    在写Qt窗口时,假如对窗口设置了 Qt::FramelessWindowHint 或者 Qt::CustomizeWindowHint 标志,会发现窗口在副屏上最大化会遮住任务栏。 假如自己实现最大化来解决这个问题,虽然能够解决,但窗口状态就需要自己维护 在Windows中通过拦截窗口消息的方式可以更优雅的解决这个问题 在

    2024年02月05日
    浏览(42)
  • Qt/C++音视频开发37-识别鼠标按下像素坐标

    在和视频交互过程中,用户一般需要在显示视频的通道上点击对应的区域,弹出对应的操作按钮,将当前点击的区域或者绘制的多边形区域坐标或者坐标点集合,发送出去,通知其他设备进行处理。比如识别到很多人脸,用户单击某个人脸后指定对该人脸进行详细的信息查询

    2024年02月11日
    浏览(48)
  • unity | EventTrigger(实现鼠标/按钮按下、抬起等时执行的方法)

    一、类比常用按钮 按钮组件一般只有当点击时,执行的方法。 点击:需要按下再起来 二、按钮的其他用法 但有时候,我们可能只需要按下的时候执行,或者抬起的时候执行,这时需要在加入EventTrigger组件  一、EventTrigger组件 当点击时Add New Event Type时,可以选择这个按钮响

    2024年02月09日
    浏览(37)
  • QT QToolButton在鼠标悬浮以及按下的情况下内容会下沉

    使用样式表可以解决此问题 使用此样式可以取消按下状态的下沉效果 如果鼠标悬浮出现下沉效果 我发现的其中一个原因是:按钮原始状态下无边框,而悬浮状态下有边框。 就可以将原始状态的边框颜色设置为透明,即可取消下沉效果 注意:边框的粗细要一致,因为原始状

    2024年02月12日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包