Qt实战11.进程窗口集成之假装写了个第三方软件
1 需求描述
有这样一种情况,明明人家已经把软件实现好了,自己又想将其集成到自己开发的软件中,如果有源码,一般做法是将源码拿过来自己改吧改吧最后再编译,但是如果没有源码呢?只有可执行程序咋办?岂不是只能干瞪眼,其实,在windows下Qt提供了将进程窗口集成到本地应用的方法,而且很简单。
2 设计思路
三步走:
- 获取窗口句柄WId
- 根据WId创建QWindow
- 调用QWidget::createWindowContainer,将QWindow参数传入即可
2.1 动态获取WId
- 在windows下每个窗口都有窗口句柄的概念,但是程序每次运行窗口句柄WId都会改变,为了使WId可控,这里使用win函数获取:
FindWindowA(
_In_opt_ LPCSTR lpClassName,
_In_opt_ LPCSTR lpWindowName);
这里窗口类名lpClassName、窗口标题lpWindowName是不变的,这样每次都可以精准定位窗口了。
- 获取窗口类名和标题
获取窗口信息的工具:ViewWizard 2.72-句柄查看精灵.rar
使用也很简单,将放大镜拖拽到需要查询的窗口,相关信息就出来了,如下图所示:
2.2 调用fromWinId创建QWindow
WId获取了,然后可以调用[static] QWindow *QWindow::fromWinId(WId id)
函数创建QWindow。
2.3 调用createWindowContainer创建QWidget
Qt提供的方法如下:
暴力翻译:创建一个QWidget,使将窗口嵌入到基于QWidget的应用程序成为可能。窗口容器作为父容器的子容器创建,并带有窗口标志。一旦窗口被嵌入到容器中,容器将控制窗口的几何形状和可见性。不推荐在嵌入式窗口上显式调用QWindow::setGeometry(), QWindow::show()或QWindow::hide()。容器接管窗口的所有权。可以通过调用QWindow::setParent()从窗口容器中移除窗口。
这里将QWindow指针传入即可。
3 代码实现
3.1 启动进程
对于想要集成的窗口,首先得要先将进程启动起来:
QProcess *process = new QProcess(this);
connect(qApp, &QApplication::aboutToQuit, [process]() {
process->kill();
process->waitForFinished();
});
process->start(processName);
process->waitForStarted();
process->waitForFinished(400);
waitForFinished是有必要的,需要等待窗口完全启动才行,不然集成不进去;当软件退出时这里会自动关闭相关进程。
3.2 窗口集成
上面的设计思路已经说得很清楚了,三步走,直接上代码:
WId wid = (WId)FindWindowA(lpClassName, lpWindowName);
QWindow *window = QWindow::fromWinId(wid);
QWidget *widget = QWidget::createWindowContainer(window, ui->tabWidget);
到这里,集成完毕。
3.3 完整代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
void addWindow(QString processName, const char *lpClassName, const char *lpWindowName);
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QWindow>
#include "qt_windows.h"
#include <QProcess>
#include <QHBoxLayout>
#include <QFileInfo>
#include <QApplication>
#include <QDebug>
#include <QSysInfo>
#pragma comment(lib, "User32.lib")
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle(QStringLiteral("Qt小罗 进程窗口集成示例"));
if ("7 SP 1" == QSysInfo::productVersion()) {
addWindow("calc.exe", "CalcFrame", "计算器");
}
addWindow("mspaint.exe", "MSPaintApp", "无标题 - 画图");
addWindow("./qtapp/collidingmice.exe", "Qt5QWindowIcon", "Colliding Mice");
addWindow("./qtapp/hellogles3.exe", "Qt5QWindowOwnDCIcon", "hellogles3");
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::addWindow(QString processName, const char *lpClassName, const char *lpWindowName)
{
QProcess *process = new QProcess(this);
connect(qApp, &QApplication::aboutToQuit, [process]() {
process->kill();
process->waitForFinished();
});
process->start(processName);
process->waitForStarted();
process->waitForFinished(800);
WId wid = (WId)FindWindowA(lpClassName, lpWindowName);
QWindow *window = QWindow::fromWinId(wid);
QWidget *widget = QWidget::createWindowContainer(window, ui->tabWidget);
QFileInfo info(processName);
ui->tabWidget->addTab(widget, QStringLiteral("进程:") + info.fileName());
ui->tabWidget->setCurrentWidget(widget);
}
4 总结
这种方式并不是对所有应用都有较好的集成效果,例如如果是自定义的无边框窗口集成进来的话,会出现一些古怪的现象;大多数普通的窗口是没有问题的,例如一些windows自带的一些小应用,一些串口工具,调试工具,以及绝大多数Qt应用等,这方面本人也没做过多的测试,有兴趣的朋友可以自行验证,这里只是一个引导作用。
拓展下思维,如果集成的窗口中有一个出现异常崩溃了,那么整个程序会崩溃么?答案是不会,它们的运行互不影响,可自行测试,如果再引入QtRO进程间通信的话,如此延伸下去感觉妙用多多、牛逼哄哄、前途不可限量啊,是不是突然感觉又找到了一种新的开发模式。
5 下载
每一步踏出,都是一次探索,一次成长。