Qt跨平臺(tái)窗口選擇功能的實(shí)現(xiàn)過(guò)程
1、概述
- Qt版本:V5.12.5
- 兼容系統(tǒng):
- Windows:這里測(cè)試了Windows10,其它的版本沒(méi)有測(cè)試;
- Linux:這里測(cè)試了ubuntu18.04、20.04,其它的沒(méi)有測(cè)試(ubuntu自帶的截圖軟件沒(méi)有這個(gè)功能);
- Mac:等啥時(shí)候我有了Mac電腦再說(shuō)。
- 我們?cè)谑褂媒貓D軟件、錄屏軟件時(shí)常常有一個(gè)選項(xiàng),就是窗口截圖,當(dāng)我們鼠標(biāo)移動(dòng)到窗口上時(shí),程序會(huì)自動(dòng)識(shí)別到鼠標(biāo)位置的窗口,獲取窗口的大小、位置,這是怎么實(shí)現(xiàn)的呢;
- 這里就研究了一下,如果使用Qt的鼠標(biāo)事件、事件過(guò)濾器,一般鼠標(biāo)出了窗口范圍就不會(huì)觸發(fā)鼠標(biāo)事件了,想要獲取全局鼠標(biāo)事件只能使用系統(tǒng)API,Windows下就是使用user32、ubuntu下就是X11;
- 在這個(gè)示例中實(shí)現(xiàn)了自動(dòng)獲取鼠標(biāo)所在坐標(biāo)窗口的位置、大小信息,并使用一個(gè)矩形窗口框選、覆蓋住所在窗口;
- 在windows實(shí)現(xiàn)的窗口選擇功能可精確識(shí)別系統(tǒng)任務(wù)欄、程序圖標(biāo)、文件資源管理器的各個(gè)窗口部件等(很多截屏軟件都沒(méi)這個(gè)功能)。
2、實(shí)現(xiàn)效果
Windows下實(shí)現(xiàn)效果

Linux下實(shí)現(xiàn)效果

3、實(shí)現(xiàn)原理
Windows
- 使用定時(shí)器每隔200ms獲取一次當(dāng)前鼠標(biāo)位置;
- 調(diào)用系統(tǒng)user32 API通過(guò)鼠標(biāo)位置查詢(xún)當(dāng)前位置的窗口句柄;
- 通過(guò)獲取的窗口句柄獲取窗口的位置、大??;
- 將當(dāng)前窗口覆蓋到鼠標(biāo)所在窗口上方(注意:當(dāng)前窗口需要設(shè)置鼠標(biāo)穿透,否則第2部獲取到的就是當(dāng)前窗口的句柄);
Linux
- 使用定時(shí)器每隔200ms獲取一次當(dāng)前鼠標(biāo)位置;
- 調(diào)用系統(tǒng)x11 API獲取當(dāng)前屏幕的所有窗口;
- 通過(guò)獲取的窗口句柄獲取窗口的位置、大??;
- 通過(guò)當(dāng)前窗口的句柄過(guò)濾掉獲取的所有窗口句柄中的當(dāng)前窗口(否則當(dāng)前窗口因?yàn)槭窃谧钌蠈?,每次獲取的都是當(dāng)前窗口的大?。?/li>
- 遍歷所有窗口的位置、大小,判斷包含鼠標(biāo)位置的窗口,并記錄位置、大小信息,由于遍歷是從最底層窗口到最頂層窗口,所以需要保存最后一個(gè)窗口的位置、大小信息;
- 將當(dāng)前窗口覆蓋到鼠標(biāo)所在窗口上方(注意:linux下不能設(shè)置鼠標(biāo)穿透,否則窗口出現(xiàn)顯示不全的問(wèn)題);
4、關(guān)鍵代碼
由于使用到了系統(tǒng)API,所以pro文件中需要鏈接系統(tǒng)庫(kù)
win32 {
LIBS+= -luser32 # 使用WindowsAPI需要鏈接庫(kù)
}
unix:!macx{
LIBS += -lX11 # linux獲取窗口信息需要用到xlib
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTimer>
class Widget : public QWidget
{
Q_OBJECT
public:
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 處理鼠標(biāo)事件的回調(diào)函數(shù)
* @param nCode
* @param wParam
* @param lParam
* @return
*/
LRESULT CALLBACK CallBackProc(int nCode, WPARAM wParam, LPARAM lParam)
{
switch (wParam)
{
case WM_LBUTTONDOWN: // 鼠標(biāo)左鍵按下
{
POINT pos;
bool ret = GetCursorPos(&pos);
if(ret)
{
qDebug() << pos.x <<" " << pos.y;
}
qDebug() << "鼠標(biāo)左鍵按下";
break;
}
default:
break;
}
return CallNextHookEx(nullptr, nCode, wParam, lParam); // 注意這一行一定不能少,否則會(huì)出大問(wèn)題
}
#endif
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->setWindowTitle(QString("Qt-框選鼠標(biāo)當(dāng)前位置窗口范圍 - V%1").arg(APP_VERSION));
#if defined(Q_OS_WIN)
// linux下鼠標(biāo)穿透要放在后面兩行代碼的全前面,否則無(wú)效(但是鼠標(biāo)穿透了會(huì)導(dǎo)致一些奇怪的問(wèn)題,如窗口顯示不全,所以這里不使用)
// windows下如果不設(shè)置鼠標(biāo)穿透則只能捕獲到當(dāng)前窗口
this->setAttribute(Qt::WA_TransparentForMouseEvents, true);
#endif
this->setWindowFlags(Qt::FramelessWindowHint); // 去掉邊框、標(biāo)題欄
this->setAttribute(Qt::WA_TranslucentBackground); // 背景透明
this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint); // 設(shè)置頂級(jí)窗口,防止遮擋
#if defined(Q_OS_WIN)
// 由于windows不透明的窗體如果不設(shè)置設(shè)置鼠標(biāo)穿透WindowFromPoint只能捕捉到當(dāng)前窗體,而設(shè)置鼠標(biāo)穿透后想要獲取鼠標(biāo)事件只能通過(guò)鼠標(biāo)鉤子
g_hook = SetWindowsHookExW(WH_MOUSE_LL, CallBackProc, GetModuleHandleW(nullptr), 0); // 掛載全局鼠標(biāo)鉤子
if (g_hook)
{
qDebug() << "鼠標(biāo)鉤子掛接成功,線(xiàn)程ID:" << GetCurrentThreadId();
}
else
{
qDebug() << "鼠標(biāo)鉤子掛接失敗:" << GetLastError();
}
#endif
// 在當(dāng)前窗口上增加一層QWidget,否則不會(huì)顯示邊框
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);"); // 設(shè)置窗口邊框樣式 dashed虛線(xiàn),solid 實(shí)線(xiàn)
// 使用定時(shí)器定時(shí)獲取當(dāng)前鼠標(biāo)位置的窗口位置信息
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() << "卸載鼠標(biāo)鉤子。";
}
}
#endif
}
void Widget::on_timeout()
{
QPoint point = QCursor::pos(); // 獲取鼠標(biāo)當(dāng)前位置
#if defined(Q_OS_WIN)
POINT pos;
pos.x = point.x();
pos.y = point.y();
HWND hwnd = nullptr;
hwnd = WindowFromPoint(pos); // 通過(guò)鼠標(biāo)位置獲取窗口句柄
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); // 設(shè)置窗口邊框
#elif defined(Q_OS_LINUX) // linux下使用x11獲取的窗口大小有可能不太準(zhǔn)確,例如瀏覽器的大小會(huì)偏小
// 獲取根窗口
Display* display = XOpenDisplay(nullptr);
Window rootWindow = DefaultRootWindow(display);
Window root_return, parent_return;
Window * children = nullptr;
unsigned int nchildren = 0;
// 函數(shù)詳細(xì)說(shuō)明見(jiàn)xlib文檔:https://tronche.com/gui/x/xlib/window-information/XQueryTree.html
// 該函數(shù)會(huì)返回父窗口的子窗口列表children,因?yàn)檫@里用的是當(dāng)前桌面的根窗口作為父窗口,所以會(huì)返回所有子窗口
// 注意:窗口順序(z-order)為自底向上
XQueryTree(display, rootWindow, &root_return, &parent_return, &children, &nchildren);
QRect recte; // 保存鼠標(biāo)當(dāng)前所在窗口的范圍
for(unsigned int i = 0; i < nchildren; ++i)
{
if(children[i] == this->winId()) continue; // 由于當(dāng)前窗口一直在最頂層,所以這里要過(guò)濾掉當(dāng)前窗口,否則一直獲取到的就是當(dāng)前窗口大小
XWindowAttributes attrs;
XGetWindowAttributes(display, children[i], &attrs); // 獲取窗口參數(shù)
if (attrs.map_state == IsViewable) // 只處理可見(jiàn)的窗口, 三個(gè)狀態(tài):IsUnmapped, IsUnviewable, IsViewable
{
#if 0
QRect rect(attrs.x + 1, attrs.y, attrs.width, attrs.height); // 這里x+1防止全屏顯示,如果不+1,設(shè)置的大小等于屏幕大小是會(huì)自動(dòng)切換成全屏顯示狀態(tài),后面就無(wú)法縮小了
#else
QRect rect(attrs.x, attrs.y, attrs.width, attrs.height);
#endif
if(rect.contains(point)) // 判斷鼠標(biāo)坐標(biāo)是否在窗口范圍內(nèi)
{
recte = rect; // 記錄最后一個(gè)窗口的范圍
}
}
}
#if 0 // 在linux下使用setGeometry設(shè)置窗口會(huì)有一些問(wèn)題
this->showNormal(); // 第一次顯示是如果是屏幕大小,則后面無(wú)法縮小,這里需要設(shè)置還原
this->setGeometry(recte); // 設(shè)置窗口邊框
#else // 使用setFixedSize+move可以避免這些問(wèn)題
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、源代碼
總結(jié)
到此這篇關(guān)于Qt跨平臺(tái)窗口選擇功能實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Qt跨平臺(tái)窗口選擇功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解C++設(shè)計(jì)模式編程中對(duì)狀態(tài)模式的運(yùn)用
這篇文章主要介紹了C++設(shè)計(jì)模式編程中對(duì)狀態(tài)模式的運(yùn)用,狀態(tài)模式允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為,對(duì)象看起來(lái)似乎修改了它的類(lèi),需要的朋友可以參考下2016-03-03
Windows 環(huán)境下使用 Qt 連接 MySQL
這篇文章主要介紹了Windows 環(huán)境下使用 Qt 連接 MySQL的相關(guān)資料,需要的朋友可以參考下2017-07-07
C++ OpenCV實(shí)戰(zhàn)之圖像透視矯正
這篇文章主要介紹了通過(guò)C++ OpenCV實(shí)現(xiàn)圖像的透視矯正,文中的示例代碼講解詳細(xì),對(duì)我們的學(xué)習(xí)或工作有一定的參考價(jià)值,感興趣的可以了解一下2022-01-01
C++?測(cè)試框架GoogleTest入門(mén)介紹
這篇文章主要為大家介紹了C++測(cè)試框架GoogleTest入門(mén)基礎(chǔ),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
C語(yǔ)言中的數(shù)據(jù)類(lèi)型強(qiáng)制轉(zhuǎn)換
這篇文章主要介紹了C語(yǔ)言中的數(shù)據(jù)類(lèi)型強(qiáng)制轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03

