
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(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進程間通信的話,如此延伸下去感覺妙用多多、牛逼哄哄、前途不可限量啊,是不是突然感覺又找到了一種新的開發模式。
