文章目录
CMake+QT+大漠插件的桌面应用开发(QThread)
简介
-
在CMake+QT+大漠插件的桌面应用开发中已经给出了QT 配合大漠插件开发桌面应用的样例
-
不过由于主窗口的UI操作和大漠的调用是在一个线程里面的,所以当大漠调用时间过长时会出现UI界面卡顿的现象
-
我们可以利用子线程处理耗时操作,处理完后再由主线程(UI线程)更新界面,这样界面就不会出现卡顿。
-
在这里,我们将会用到QThread,调整后的QT主线程与子线程交互逻辑图如下:
-
交互逻辑描述
- 当点击"注册"选项时,会发出
regDM
信号,子线程接收到该信号会执行MyMainWorker中的doRegDM
方法,执行完成后会发出regDMReady
信号,主线程接收到该信号会执行更新UI的操作 - 当点击"搜索"按钮时,同理
- 当点击"截图"按钮时,同理
- 当点击"注册"选项时,会发出
环境
版本/规范 | 备注 | |
---|---|---|
平台 | win32 | 操作系统为Windows10 |
CMake | 3.27.8 | CLion自带 |
C++ | 17 | |
Toolchain | VisualStudio 2022 | 只用其工具链,记得先安装好 |
QT | 5.12.12 | 安装时选择msvc2017,不要64位的 |
DM | 7.2353 | |
CLion | 2023.3.2 | 你也可以用其他IDE工具 |
- 启动IDE时,记得以管理员模式启动
项目结构
-
新建一个项目 qt_dm_demo_x_02
-
目录同CMake+QT+大漠插件的桌面应用开发中一致,会多出MyMainWorker,用于处理子线程逻辑
qt_dm_demo_x_02 # 项目目录
-- ......
--MyMainWorker.cpp
--MyMainWorker.h
-- ......
配置编译环境
- 其他同CMake+QT+大漠插件的桌面应用开发中一致
- CMakeLists.txt 文件中生成可执行文件时,会多出MyMainWorker.cpp 、MyMainWorker.h
cmake
# 生成可执行文件
add_executable(${PROJECT_NAME} main.cpp
strutils.cpp strutils.h
dmutil.cpp dmutil.h
mymainwindow.cpp mymainwindow.h mymainwindow.ui
MyMainWorker.cpp MyMainWorker.h
)
代码
- dmutil.h、dmutil.cpp、strutils.h、strutils.cpp、mymainwindow.ui、main.cpp同CMake+QT+大漠插件的桌面应用开发中一致
- mymainwindow.h
c
#ifndef QT_DM_DEMO_X_MYMAINWINDOW_H
#define QT_DM_DEMO_X_MYMAINWINDOW_H
#include <QMainWindow>
#include <QTextEdit>
#include <QThread>
#include "dmutil.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MyMainWindow; }
QT_END_NAMESPACE
class MyMainWindow : public QMainWindow {
Q_OBJECT
QThread workerThread;
public:
explicit MyMainWindow(QWidget *parent = nullptr);
~MyMainWindow() override;
public:
void showInfo(const QString &message, const QString &title = "提示");
void showWarn(const QString &message, const QString &title = "告警");
signals:
void regDM(Idmsoft **pDm);
void findWindow(Idmsoft *pDm, const QString &title);
void captureWindow(Idmsoft *pDm, const long hwnd);
public slots:
void showMessageBox(bool result, const QString &message);
void showTable(bool result, const QString &msg, const vector<MyWindow> &windowVec);
private:
Ui::MyMainWindow *ui;
Idmsoft *pCommonDm = nullptr;
};
#endif //QT_DM_DEMO_X_MYMAINWINDOW_H
- mymainwindow.cpp
cpp
// You may need to build the project (run Qt uic code generator) to get "ui_MyMainWindow.h" resolved
#include <QFont>
#include <QHeaderView>
#include <QMessageBox>
#include <QPushButton>
#include <QAction>
#include <QString>
#include <QTableWidgetItem>
#include <QObject>
#include <QVector>
#include <iostream>
#include "mymainwindow.h"
#include "ui_MyMainWindow.h"
#include "MyMainWorker.h"
using namespace std;
MyMainWindow::MyMainWindow(QWidget *parent) :
QMainWindow(parent), ui(new Ui::MyMainWindow) {
ui->setupUi(this);
qRegisterMetaType<QVector<int>>("QVector<int>");
qRegisterMetaType<vector<MyWindow>>("vector<MyWindow>");
// Init Views
setFixedSize(1280, 720);
ui->tableWidget->setColumnCount(3);
ui->tableWidget->setHorizontalHeaderLabels(QStringList() << "进程ID" << "句柄" << "标题");
ui->tableWidget->horizontalHeader()->setStretchLastSection(true); // 最后一列自动铺满表格
// ui->tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
ui->tableWidget->horizontalHeader()->setHighlightSections(false);
ui->tableWidget->horizontalHeader()->setStyleSheet("QHeaderView::section{background:gray;}");
ui->tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
QFont font = ui->tableWidget->horizontalHeader()->font();
font.setBold(true);
ui->tableWidget->horizontalHeader()->setFont(font);
ui->tableWidget->setStyleSheet("QTableWidget::item:hover { background-color: lightblue; }");
ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); // 禁止编辑
ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); // 选中整行
// Init Listener
auto worker = new MyMainWorker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
// 注册大漠
connect(ui->actionReg, &QAction::triggered, [this]() {
ui->actionReg->setEnabled(false);
emit this->regDM(&this->pCommonDm);
});
connect(this, &MyMainWindow::regDM, worker, &MyMainWorker::doRegDM);
connect(worker, &MyMainWorker::regDMReady, this, &MyMainWindow::showMessageBox);
// 查找窗口
connect(ui->btnQuery, &QPushButton::clicked, [this]() {
ui->btnQuery->setEnabled(false);
emit this->findWindow(this->pCommonDm, ui->edtTitle->text());
});
connect(this, &MyMainWindow::findWindow, worker, &MyMainWorker::doFindWindow);
connect(worker, &MyMainWorker::findWindowReady, this, &MyMainWindow::showTable);
// 截图
connect(ui->btnCapture, &QPushButton::clicked, [this]() {
ui->btnCapture->setEnabled(false);
// 获取选中行的句柄列的字段
const QList<QTableWidgetItem *> &selectedItems = ui->tableWidget->selectedItems();
if (selectedItems.size() >= 2) {
QTableWidgetItem *item = selectedItems.at(1);
const QString &hwnd = item->data(Qt::DisplayRole).toString();
bool res = false;
long hwndL = hwnd.toLong(&res, 0);
cout << res << endl;
if (res) {
emit this->captureWindow(this->pCommonDm, hwndL);
} else {
ui->btnCapture->setEnabled(true);
this->showWarn("选中行的窗口句柄解析异常!");
}
} else {
ui->btnCapture->setEnabled(true);
this->showWarn("请选中列表中的其中一行!");
}
});
connect(this, &MyMainWindow::captureWindow, worker, &MyMainWorker::doCaptureWindow);
connect(worker, &MyMainWorker::captureWindowReady, this, &MyMainWindow::showMessageBox);
workerThread.start();
}
MyMainWindow::~MyMainWindow() {
delete ui;
workerThread.quit();
workerThread.wait();
}
void MyMainWindow::showInfo(const QString &message, const QString &title) {
QMessageBox::information(this, title, message);
}
void MyMainWindow::showWarn(const QString &message, const QString &title) {
QMessageBox::critical(this, title, message);
}
void MyMainWindow::showMessageBox(const bool result, const QString& message) {
ui->actionReg->setEnabled(true);
ui->btnCapture->setEnabled(true);
if (result) {
this->showInfo(message);
} else {
this->showWarn(message);
}
}
void MyMainWindow::showTable(const bool result, const QString &msg, const vector<MyWindow> &windowVec) {
ui->btnQuery->setEnabled(true);
if (result) {
auto rowNum = windowVec.size();
ui->tableWidget->setRowCount(rowNum);
for (int i = 0; i < rowNum; ++i) {
const MyWindow &item = windowVec[i];
ui->tableWidget->setItem(i, 0, new QTableWidgetItem(QString::number(item.processId)));
ui->tableWidget->setItem(i, 1, new QTableWidgetItem(QString::number(item.hwnd)));
ui->tableWidget->setItem(i, 2, new QTableWidgetItem(QString::fromStdWString(item.title)));
}
} else {
this->showWarn(msg);
}
}
- MyMainWorker.h
cpp
#ifndef QT_DM_DEMO_X_MYMAINWORKER_H
#define QT_DM_DEMO_X_MYMAINWORKER_H
#include <QObject>
#include "dmutil.h"
class MyMainWorker: public QObject {
Q_OBJECT
signals:
void regDMReady(const bool result, const QString &msg);
void findWindowReady(const bool result, const QString &msg, const vector <MyWindow> &windowVec);
void captureWindowReady(const bool result, const QString &msg);
public slots:
/**
* 注册大漠
* @param pDm 大漠插件,待赋值
*/
void doRegDM(Idmsoft **pDm);
/**
* 查询匹配的窗口
* @param pDm 大漠插件
* @param title 窗口标题(模糊查询)
*/
void doFindWindow(Idmsoft *pDm, const QString &title);
/**
* 对窗口截图
* @param pDm 大漠插件
* @param hwnd 窗口句柄
*/
void doCaptureWindow(Idmsoft *pDm, long hwnd);
};
#endif //QT_DM_DEMO_X_MYMAINWORKER_H
- MyMainWorker.cpp
cpp
#include <iostream>
#include "MyMainWorker.h"
using namespace std;
void MyMainWorker::doRegDM(Idmsoft **pDm) {
cout << "========== Initial DM ............ ==========" << endl;
*pDm = initialDMAndRegVIP();
if (*pDm == nullptr) {
cout << "========== Initial DM <Failed> ==========" << endl;
emit this->regDMReady(false, "DM 注册失败!");
return;
}
cout << "========== Initial DM <Successful> ==========" << endl;
cout << endl;
emit this->regDMReady(true, "DM 注册完成!");
}
void MyMainWorker::doFindWindow(Idmsoft *pDm, const QString &title) {
vector<MyWindow> windowVec;
if (pDm == nullptr) {
cout << "this->pCommonDm == nullptr" << endl;
emit this->findWindowReady(false, "请先在菜单中完成注册!", windowVec);
return;
}
// 找一下包含title的窗口
getMatchedWindows(windowVec, pDm, title.toStdWString());
if (windowVec.empty()) {
cout << "can not find such window" << endl;
emit this->findWindowReady(false, "没有找到包含该标题的窗口!", windowVec);
return;
}
emit this->findWindowReady(true, "成功!", windowVec);
}
void MyMainWorker::doCaptureWindow(Idmsoft *pDm, long hwnd) {
if (pDm == nullptr) {
cout << "this->pCommonDm == nullptr" << endl;
emit this->captureWindowReady(false, "请先在菜单中完成注册!");
return;
}
// 绑定窗口句柄
long dmBind = pDm->BindWindowEx(
hwnd,
"normal",
"normal",
"normal",
"",
0
);
if (dmBind == 1) {
// 恢复并激活指定窗口,置顶窗口,
pDm->SetWindowState(hwnd, 12);
pDm->SetWindowState(hwnd, 8);
pDm->delay(600);
// 延迟一下截图,存到相对路径
wstring filename = wstring(L"./capture_window_").append(std::to_wstring(hwnd)).append(L".bmp");
long retCap = pDm->Capture(0, 0, 2000, 2000, filename.c_str());
if (retCap != 1) {
cout << "capture failed" << endl;
emit this->captureWindowReady(false, "截图失败!");
} else {
cout << "capture success" << endl;
emit this->captureWindowReady(true, QString::fromStdWString(L"截图成功,保存地址为: " + filename));
}
// 取消置顶窗口
pDm->SetWindowState(hwnd, 9);
} else {
cout << "DM BindWindow failed" << endl;
emit this->captureWindowReady(false, "绑定窗口异常!");
}
pDm->UnBindWindow();
}