Qt实现跨平台窗口选择功能

news/2024/11/28 21:41:14/

Qt实现跨平台获取鼠标位置窗口大小功能

文章目录

  • Qt实现跨平台获取鼠标位置窗口大小功能
    • 1、概述
    • 2、实现效果
    • 3、实现原理
    • 4、关键代码
    • 5、源代码

更多精彩内容
👉个人内容分类汇总 👈
👉Qt自定义模块、工具👈

1、概述

  • Qt版本:V5.12.5
  • 兼容系统:
    • Windows:这里测试了Windows10,其它的版本没有测试;
    • Linux:这里测试了ubuntu18.04、20.04,其它的没有测试(ubuntu自带的截图软件没有这个功能);
    • Mac:等啥时候我有了Mac电脑再说。
  1. 我们在使用截图软件、录屏软件时常常有一个选项,就是窗口截图,当我们鼠标移动到窗口上时,程序会自动识别到鼠标位置的窗口,获取窗口的大小、位置,这是怎么实现的呢;
  2. 这里就研究了一下,如果使用Qt的鼠标事件、事件过滤器,一般鼠标出了窗口范围就不会触发鼠标事件了,想要获取全局鼠标事件只能使用系统API,Windows下就是使用user32、ubuntu下就是X11;
  3. 在这个示例中实现了自动获取鼠标所在坐标窗口的位置、大小信息,并使用一个矩形窗口框选、覆盖住所在窗口;
  4. 在windows实现的窗口选择功能可精确识别系统任务栏、程序图标、文件资源管理器的各个窗口部件等(很多截屏软件都没这个功能)。

2、实现效果

  • Windows下实现效果

    在这里插入图片描述

  • Linux下实现效果

    在这里插入图片描述

3、实现原理

  • Windows

    1. 使用定时器每隔200ms获取一次当前鼠标位置;
    2. 调用系统user32 API通过鼠标位置查询当前位置的窗口句柄;
    3. 通过获取的窗口句柄获取窗口的位置、大小;
    4. 将当前窗口覆盖到鼠标所在窗口上方(注意:当前窗口需要设置鼠标穿透,否则第2部获取到的就是当前窗口的句柄);
  • Linux

    1. 使用定时器每隔200ms获取一次当前鼠标位置;
    2. 调用系统x11 API获取当前屏幕的所有窗口;
    3. 通过获取的窗口句柄获取窗口的位置、大小;
    4. 通过当前窗口的句柄过滤掉获取的所有窗口句柄中的当前窗口(否则当前窗口因为是在最上层,每次获取的都是当前窗口的大小);
    5. 遍历所有窗口的位置、大小,判断包含鼠标位置的窗口,并记录位置、大小信息,由于遍历是从最底层窗口到最顶层窗口,所以需要保存最后一个窗口的位置、大小信息;
    6. 将当前窗口覆盖到鼠标所在窗口上方(注意:linux下不能设置鼠标穿透,否则窗口出现显示不全的问题);

4、关键代码

  • 由于使用到了系统API,所以pro文件中需要链接系统库
win32 {
LIBS+= -luser32    # 使用WindowsAPI需要链接库
}
unix:!macx{
LIBS += -lX11      # linux获取窗口信息需要用到xlib
}
  • widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTimer>class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();protected:void on_timeout();
private:QTimer m_timer;
};
#endif // WIDGET_H
  • NtpClient.cpp
#include "widget.h"
#include <QDebug>
#include <qgridlayout.h>#if defined(Q_OS_WIN)
#include <Windows.h>
#include <windef.h>
#elif defined(Q_OS_LINUX)
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#elif defined(Q_OS_MAC)
#endif#if defined(Q_OS_WIN)
static HHOOK g_hook = nullptr;
/*** @brief           处理鼠标事件的回调函数* @param nCode* @param wParam* @param lParam* @return*/
LRESULT CALLBACK CallBackProc(int nCode, WPARAM wParam, LPARAM lParam)
{switch (wParam){case WM_LBUTTONDOWN:   // 鼠标左键按下{POINT pos;bool ret = GetCursorPos(&pos);if(ret){qDebug() << pos.x <<" " << pos.y;}qDebug() << "鼠标左键按下";break;}default:break;}return CallNextHookEx(nullptr, nCode, wParam, lParam);   // 注意这一行一定不能少,否则会出大问题
}
#endifWidget::Widget(QWidget *parent): QWidget(parent)
{this->setWindowTitle(QString("Qt-框选鼠标当前位置窗口范围 - V%1").arg(APP_VERSION));
#if defined(Q_OS_WIN)// linux下鼠标穿透要放在后面两行代码的全前面,否则无效(但是鼠标穿透了会导致一些奇怪的问题,如窗口显示不全,所以这里不使用)// windows下如果不设置鼠标穿透则只能捕获到当前窗口this->setAttribute(Qt::WA_TransparentForMouseEvents, true);
#endifthis->setWindowFlags(Qt::FramelessWindowHint);                            // 去掉边框、标题栏this->setAttribute(Qt::WA_TranslucentBackground);                         // 背景透明this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint);     // 设置顶级窗口,防止遮挡#if defined(Q_OS_WIN)// 由于windows不透明的窗体如果不设置设置鼠标穿透WindowFromPoint只能捕捉到当前窗体,而设置鼠标穿透后想要获取鼠标事件只能通过鼠标钩子g_hook = SetWindowsHookExW(WH_MOUSE_LL, CallBackProc, GetModuleHandleW(nullptr), 0);  // 挂载全局鼠标钩子if (g_hook){qDebug() << "鼠标钩子挂接成功,线程ID:" << GetCurrentThreadId();}else{qDebug() << "鼠标钩子挂接失败:" << GetLastError();}
#endif// 在当前窗口上增加一层QWidget,否则不会显示边框QGridLayout* gridLayout = new QGridLayout(this);gridLayout->setSpacing(0);gridLayout->setContentsMargins(0, 0, 0, 0);gridLayout->addWidget(new QWidget(), 0, 0, 1, 1);this->setStyleSheet(" background-color: rgba(58, 196, 255, 40); border: 2px solid rgba(58, 196, 255, 200);"); // 设置窗口边框样式 dashed虚线,solid 实线// 使用定时器定时获取当前鼠标位置的窗口位置信息connect(&m_timer, &QTimer::timeout, this, &Widget::on_timeout);m_timer.start(200);}Widget::~Widget()
{
#if defined(Q_OS_WIN)if(g_hook){bool ret = UnhookWindowsHookEx(g_hook);if(ret){qDebug() << "卸载鼠标钩子。";}}
#endif
}void Widget::on_timeout()
{QPoint point = QCursor::pos();  // 获取鼠标当前位置
#if defined(Q_OS_WIN)POINT pos;pos.x = point.x();pos.y = point.y();HWND hwnd = nullptr;hwnd = WindowFromPoint(pos);   // 通过鼠标位置获取窗口句柄if(!hwnd) return;RECT lrect;bool ret = GetWindowRect(hwnd, &lrect);     //获取窗口位置if(!ret) return;QRect rect;rect.setX(lrect.left);rect.setY(lrect.top);rect.setWidth(lrect.right - lrect.left);rect.setHeight(lrect.bottom - lrect.top);this->setGeometry(rect);         // 设置窗口边框
#elif defined(Q_OS_LINUX)  // linux下使用x11获取的窗口大小有可能不太准确,例如浏览器的大小会偏小// 获取根窗口Display* display = XOpenDisplay(nullptr);Window rootWindow = DefaultRootWindow(display);Window root_return, parent_return;Window * children = nullptr;unsigned int nchildren = 0;// 函数详细说明见xlib文档:https://tronche.com/gui/x/xlib/window-information/XQueryTree.html// 该函数会返回父窗口的子窗口列表children,因为这里用的是当前桌面的根窗口作为父窗口,所以会返回所有子窗口// 注意:窗口顺序(z-order)为自底向上XQueryTree(display, rootWindow, &root_return, &parent_return, &children, &nchildren);QRect recte;                      // 保存鼠标当前所在窗口的范围for(unsigned int i = 0; i < nchildren; ++i){if(children[i] == this->winId()) continue;           // 由于当前窗口一直在最顶层,所以这里要过滤掉当前窗口,否则一直获取到的就是当前窗口大小XWindowAttributes attrs;XGetWindowAttributes(display, children[i], &attrs);  // 获取窗口参数if (attrs.map_state == IsViewable)                   // 只处理可见的窗口, 三个状态:IsUnmapped, IsUnviewable, IsViewable{
#if 0QRect rect(attrs.x + 1, attrs.y, attrs.width, attrs.height);  // 这里x+1防止全屏显示,如果不+1,设置的大小等于屏幕大小是会自动切换成全屏显示状态,后面就无法缩小了
#elseQRect rect(attrs.x, attrs.y, attrs.width, attrs.height);
#endifif(rect.contains(point))                        // 判断鼠标坐标是否在窗口范围内{recte = rect;                               // 记录最后一个窗口的范围}}}
#if 0  // 在linux下使用setGeometry设置窗口会有一些问题this->showNormal();               // 第一次显示是如果是屏幕大小,则后面无法缩小,这里需要设置还原this->setGeometry(recte);         // 设置窗口边框
#else  // 使用setFixedSize+move可以避免这些问题this->move(recte.x(), recte.y());this->setFixedSize(recte.width(), recte.height());
#endif
//    qDebug() << this->rect() <<recte<< this->windowState();// 注意释放资源XFree(children);XCloseDisplay(display);#elif defined(Q_OS_MAC)
#endif
}

5、源代码

  • gitee
  • github
  • 当前示例所在模块

💡💡💡💡💡💡💡💡💡💡💡💡💡💡


http://www.ppmy.cn/news/2541.html

相关文章

(vsCode) sqlite3可视化工具的使用

vsCode - sqlite3可视化工具的使用 1.安装扩展 SQLite 因此&#xff0c;我们将引入一个名为 SQLite的扩展。 尝试照常从VScode Marketplace安装&#xff1a;搜索SQLite 安装扩展 2.如何使用SQLite&#xff1a; 打开命令选项板&#xff0c;然后输入 sql。 具体操作&#xff1…

IT培训从业6年, 正厚软件魏老师说些真心话(篇一)

其实每个行业都有一些为人知和不为人广知的信息, 称之为"路子"&#xff0c;只要说话发声吧总会有人不满意。引发的廉价的口水战, 更有甚者不知其居心的人愤然的各种神一般的操作, 我也见怪不怪了…… 可能部分文字触及一些一些利益, 但是还是想给一些准备入IT行业/软…

一款优秀的Linux终端Starship

简介 Starship是一款轻量、迅速、可无限定制的高颜值终端&#xff01; 兼容性优先 Starship 可以在各种常见的操作系统和常见的 shell 上运行。 使用 Rust 编写 具有 Rust 独树一帜的速度与安全性&#xff0c;使你的提示符尽可能的快速可靠。 可自定义 每个小细节都可以按您…

路由表和转发表的区别

1.路由表 路由信息最终要存储在用于路由器的主机或者专业路由器上&#xff0c;存放这些信息的地方称为路由表。其中最低包含三元素&#xff1a;目标地址&#xff0c;掩码&#xff0c;下一跳。 1.1.查询路由表的开销 有人认为查询路由表是一件和交换机查询MAC地址一样的事&…

直播带货行业如何入局?先了解一下直播商城源码吧

直播行业的爆火已经持续了多个年头&#xff0c;直到今天&#xff0c;在人们的生活中依然有着举足轻重的地位&#xff0c;它通过多元化的方案为许多行业带来了新的思路&#xff0c;特别是与传统商业所结合的“直播电商”、“直播商城”的卖货新形式&#xff0c;让多方因此而受益…

[附源码]Python计算机毕业设计SSM佳音大学志愿填报系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

20221207比对python和C的运行效率(以六元一次方程组为例)【大概300倍】

20221207比对python和C的运行效率&#xff08;以六元一次方程组为例&#xff09; 2022/12/7 17:30 C:\20221207比对python和C的运行效率&#xff08;以六元一次方程组为例&#xff09;\1000-1000-1000-1000-1000-1000 &#xff08;只跑一次&#xff09; Python源码&#xff1a…

如何基于企业微信通讯录,同步生成对外的LDAP服务?

不少企业使用企业微信作为办公协同的工具&#xff0c;并通过企业微信的组织架构&#xff08;即通讯录&#xff09;来管理员工身份。那么如何利用当前企业微信的组织架构和用户身份&#xff0c;同步生成对外的 LDAP 服务呢&#xff1f; 为什么要同步创建 LDAP 服务&#xff1f; …