提交 1195d65c 编写于 作者: mahuifa's avatar mahuifa

feat:完成截图工具功能开发

    1、完成窗口截图功能开发;
    2、完成linux系统适配。
上级 50963ac8
#---------------------------------------------------------------------------------------
# @功能: Qt实现截图工具
# 1、实现Windowslinux系统下截图功能
# 2、实现全屏截图、矩形截图、窗口截图功能;
# 3、实现保存截图、取消截图功能;
# 4、实时显示截取的图片。
# @编译器: Desktop Qt 5.12.5 MSVC2017 64bit(也支持其它编译器)
# @Qt IDE D:/Qt/Qt5.12.5/Tools/QtCreator/share/qtcreator
#
......@@ -20,13 +24,15 @@ FORMS += \
HEADERS += \
mainwindow.h \
playimage.h \
screenrect.h
screenrect.h \
windowrect.h
SOURCES += \
main.cpp \
mainwindow.cpp \
playimage.cpp \
screenrect.cpp
screenrect.cpp \
windowrect.cpp
# Default rules for deployment.
......
......@@ -9,12 +9,15 @@
#include <QToolBar>
#include <QDebug>
#include <QFileDialog>
#include <QDateTime>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setWindowTitle(QString("Qt-截图工具 - V%1").arg(APP_VERSION));
// 设置工具栏
QAction* acNew = new QAction("新建截图");
......@@ -39,7 +42,7 @@ MainWindow::MainWindow(QWidget *parent)
connect(acSave, &QAction::triggered, this, &MainWindow::on_saveImage);
connect(acClear, &QAction::triggered, this, &MainWindow::on_clearImage);
connect(&m_screenRect, &ScreenRect::selectRect, this, &MainWindow::on_selectRect);
connect(&m_windowRect, &WindowRect::selectRect, this, &MainWindow::on_selectRect);
}
......@@ -72,7 +75,8 @@ void MainWindow::on_newGrab(bool checked)
}
else if(strModel == "窗口")
{
this->hide();
m_windowRect.show();
}
else
{
......@@ -85,15 +89,23 @@ void MainWindow::on_newGrab(bool checked)
*/
void MainWindow::grabPixmap(QRect rect)
{
// this->hide(); // 截图之前将当前窗口隐藏,避免截取的图像中包含当前窗口,这种方法很慢,需要延时等待几百毫秒,否则还是会有当前窗口
#if defined(Q_OS_WIN)
setWindowOpacity(0); // 最好的方法是将当前窗口设置成完全透明
QDesktopWidget *desk = QApplication::desktop(); // 获取桌面根窗口
QScreen * screen = QGuiApplication::primaryScreen(); // 获取默认屏幕
m_pixmap = screen->grabWindow(desk->winId(), rect.x(), rect.y(), rect.width(), rect.height()); // 抓取屏幕图像
ui->centralwidget->updatePixmap(m_pixmap); // 显示捕获的图像
// this->show(); // 截图完成后显示窗口
setWindowOpacity(1);
#elif defined(Q_OS_LINUX)
// linux下setWindowOpacity设置透明后截图还可以看到一个透明的边框,效果不是很好,所以使用hide
this->hide(); // 截图之前将当前窗口隐藏,避免截取的图像中包含当前窗口,这种方法很慢,需要延时等待几百毫秒,否则还是会有当前窗口
QThread::msleep(300);
QDesktopWidget *desk = QApplication::desktop(); // 获取桌面根窗口
QScreen * screen = QGuiApplication::primaryScreen(); // 获取默认屏幕
m_pixmap = screen->grabWindow(desk->winId(), rect.x(), rect.y(), rect.width(), rect.height()); // 抓取屏幕图像
ui->centralwidget->updatePixmap(m_pixmap); // 显示捕获的图像
this->show(); // 截图完成后显示窗口
#endif
}
/**
......@@ -105,7 +117,9 @@ void MainWindow::on_saveImage(bool checked)
Q_UNUSED(checked)
if(m_pixmap.isNull()) return;
QString strName = QFileDialog::getSaveFileName(this, "保存到", "./", "便携式网络图形(*.png);;JPEG文件(*.jpg)");
// linux下getSaveFileName不会返回默认文件后缀,所以需要在文件名中添加后缀,否则QImage::save无法通过后缀推测出文件类型,就会保存失败
QString name = QString("%1.png").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh-mm-ss"));
QString strName = QFileDialog::getSaveFileName(this, "保存到", name, "便携式网络图形(*.png);;JPEG文件(*.jpg)");
if(strName.isEmpty()) return;
QImage image = m_pixmap.toImage();
......@@ -115,7 +129,7 @@ void MainWindow::on_saveImage(bool checked)
}
else
{
qDebug() << "保存失败!";
QMessageBox::warning(this, "注意!", "文件保存失败,请检查有没有输入文件后缀名。");
}
}
......
......@@ -4,6 +4,7 @@
#include <QMainWindow>
#include <qtoolbutton.h>
#include "screenrect.h"
#include "windowrect.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
......@@ -30,5 +31,6 @@ private:
QToolButton* m_butModel = nullptr;
QPixmap m_pixmap; // 保存截取的图像
ScreenRect m_screenRect;
WindowRect m_windowRect;
};
#endif // MAINWINDOW_H
#include "windowrect.h"
#include <QDebug>
#include <QGridLayout>
#include <QEvent>
#include <QMouseEvent>
#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: // 鼠标左键按下
{
emit MouseEvent::getInstance()->mouseSignal(new QMouseEvent(QEvent::MouseButtonPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier));
break;
}
default:
break;
}
return CallNextHookEx(nullptr, nCode, wParam, lParam); // 注意这一行一定不能少,否则会出大问题
}
#endif
WindowRect::WindowRect(QWidget *parent) : QWidget(parent)
{
#if defined(Q_OS_WIN)
// linux下鼠标穿透要放在后面两行代码的全前面,否则无效(但是鼠标穿透了会导致一些奇怪的问题,如窗口显示不全,所以这里不使用)
// windows下如果不设置鼠标穿透则只能捕获到当前窗口
this->setAttribute(Qt::WA_TransparentForMouseEvents, true);
#endif
this->setWindowFlags(Qt::FramelessWindowHint); // 去掉边框、标题栏
this->setAttribute(Qt::WA_TranslucentBackground); // 背景透明
this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint); // 设置顶级窗口,防止遮挡
// 在当前窗口上增加一层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(MouseEvent::getInstance(), &MouseEvent::mouseSignal, this, &WindowRect::on_mouseSignal);
// 使用定时器定时获取当前鼠标位置的窗口位置信息
connect(&m_timer, &QTimer::timeout, this, &WindowRect::on_timeout);
m_timer.start(200);
}
WindowRect::~WindowRect()
{
#if defined(Q_OS_WIN)
if(g_hook)
{
bool ret = UnhookWindowsHookEx(g_hook);
if(ret)
{
qDebug() << "卸载鼠标钩子。";
}
}
#endif
}
/**
* @brief 通过截图全局鼠标事件将当前窗口大小发生出去
* @param event
*/
void WindowRect::on_mouseSignal(QEvent *event)
{
delete event;
this->hide();
emit this->selectRect(QRect(this->pos(), this->size()));
}
/**
* @brief Windows使用全局鼠标钩子,显示窗口时挂载鼠标钩子
* @param event
*/
void WindowRect::showEvent(QShowEvent *event)
{
#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::showEvent(event);
}
/**
* @brief 隐藏窗口时卸载鼠标钩子
* @param event
*/
void WindowRect::hideEvent(QHideEvent *event)
{
#if defined(Q_OS_WIN)
if(g_hook)
{
bool ret = UnhookWindowsHookEx(g_hook);
if(ret)
{
qDebug() << "卸载鼠标钩子。";
g_hook = nullptr;
}
}
#endif
QWidget::hideEvent(event);
}
/**
* @brief linux下使用自带的鼠标点击事件就可以
* @param event
*/
void WindowRect::mousePressEvent(QMouseEvent *event)
{
#if defined(Q_OS_LINUX)
this->hide();
emit this->selectRect(QRect(this->pos(), this->size()));
#endif
QWidget::mousePressEvent(event);
}
void WindowRect::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 0
QRect rect(attrs.x + 1, attrs.y, attrs.width, attrs.height); // 这里x+1防止全屏显示,如果不+1,设置的大小等于屏幕大小是会自动切换成全屏显示状态,后面就无法缩小了
#else
QRect rect(attrs.x, attrs.y, attrs.width, attrs.height);
#endif
if(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
}
/******************************************************************************
* @文件名 windowrect.h
* @功能 选中鼠标当前位置窗口的类
*
* @开发者 mhf
* @邮箱 1603291350@qq.com
* @时间 2022/11/19
* @备注
*****************************************************************************/
#ifndef WINDOWRECT_H
#define WINDOWRECT_H
#include <QTimer>
#include <QWidget>
class WindowRect : public QWidget
{
Q_OBJECT
public:
explicit WindowRect(QWidget *parent = nullptr);
~WindowRect() override;
signals:
void selectRect(QRect rect);
private slots:
void on_mouseSignal(QEvent* event);
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
protected:
void on_timeout();
private:
QTimer m_timer;
};
/**
* 全局鼠标事件单例信号类
*/
class MouseEvent : public QObject
{
Q_OBJECT
public:
static MouseEvent* getInstance()
{
static MouseEvent mouseEvent;
return &mouseEvent;
}
signals:
void mouseSignal(QEvent* event);
private:
MouseEvent(){}
};
#endif // WINDOWRECT_H
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册