Qt实战11.进程窗口集成之假装写了个第三方软件

1 需求描述

有这样一种情况,明明人家已经把软件实现好了,自己又想将其集成到自己开发的软件中,如果有源码,一般做法是将源码拿过来自己改吧改吧最后再编译,但是如果没有源码呢?只有可执行程序咋办?岂不是只能干瞪眼,其实,在windows下Qt提供了将进程窗口集成到本地应用的方法,而且很简单。

2 设计思路

三步走:

  • 获取窗口句柄WId
  • 根据WId创建QWindow
  • 调用QWidget::createWindowContainer,将QWindow参数传入即可

2.1 动态获取WId

  1. 在windows下每个窗口都有窗口句柄的概念,但是程序每次运行窗口句柄WId都会改变,为了使WId可控,这里使用win函数获取:
FindWindowA(
    _In_opt_ LPCSTR lpClassName,
    _In_opt_ LPCSTR lpWindowName);

这里窗口类名lpClassName、窗口标题lpWindowName是不变的,这样每次都可以精准定位窗口了。

  1. 获取窗口类名和标题
    获取窗口信息的工具: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 下载

示例代码

posted @ 2021-02-09 19:29  Qt小罗  阅读(3338)  评论(1编辑  收藏  举报