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(400);

    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 下載

示例代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM