有的QT项目中可能需要把使用其它语言编写好的程序窗口给加载到QT界面当中,进行融合以提供程序功能性。这个时候可以利用Windows当中的窗口句柄进行实现,目前在网上能找到的实现方法大部分都是在QWidget框架中把外部程序加载到界面中,很少使用QML当作外部程序的承载界面,本篇文章简单记录一下自己的小方法。
效果展示:
QML加载Unity外部程序
主要思路:
在QML界面当中把其它外部程序窗口加载进来的方法,其实质上还是通过把外部程序加载到QWidget框架中,然后再将QWidget窗口加载到QML界面中,相当于做了一次中间的转换,具体QWidget怎么加载到QML界面中,可以参考我的这篇博客:在QML界面中加载QWidget窗口。
首先,使用Unity软件先打包一个游戏小程序。打包之前需要对unity项目设置一些参数,在菜单栏选择Edit -> Project Settings…,如下图所示:

然后再打开的窗口中将FullScreen Mode设置为FullScreen Window,这样加载到QT当中后会自动填充到窗口中,也可以选择Windowed,这种可以自己设置unity程序窗口的尺寸大小,但是在加载到QT中时,该unity程序会以窗口的形式悬浮在界面上,不会自动填充,建议两者都尝试一下,感受一下效果。

最后在菜单栏的File -> Build And Run进行游戏小程序的打包,建议将打包的程序放在一个新的文件夹内,后面便于文件管理,会将这个新的文件夹拷贝到QT目录中,本篇文章将打包的unity小程序放置在games文件夹内。用到的游戏小程序是在b站看的一篇unity教程,感兴趣的可以参考一下:unity初级教程,程序打包操作如下图所示:

准备好游戏小程序之后,将打包好的游戏文件夹games拷贝到QT项目目录中,不拷贝也是可以,这里只是为了便于管理。注意不要只拷贝games文件夹内的***.exe程序,要将整个games文件夹全部拷贝。
然后,在QT项目中创建一个自定义类,命名为showUnityWindow***,这个类是辅助加载外部程序的,在其中会用到QProcess,Window.h,winuser.h,因为要使用windows的api,所以需要在QT项目中的***.pro文件中加上win32{LIBS += -luser32}***,如下图所示:

其中,showunitywindow类的相关代码如下,具体解释详看注释:
showunitywindow.h:
#ifndef SHOWUNITYWINDOW_H
#define SHOWUNITYWINDOW_H#include
#include
#include
#include
#include
#include
#include
#include class ShowUnityWindow : public QObject
{Q_OBJECT
public:explicit ShowUnityWindow(QObject *parent = nullptr);~ShowUnityWindow();//启动外部程序void startUnityProgram(const QString& unityExePath);//设置外部程序的父窗口void setWindowParent(HWND parentWidgetHandle, const QString& sonWindowTitleName);signals://外部程序启动后,发送此信号void unityProgramStarted();private:QProcess* process;//用于启动外部程序};#endif // SHOWUNITYWINDOW_H
showunitywindow.cpp:
#include "showunitywindow.h"ShowUnityWindow::ShowUnityWindow(QObject *parent): QObject{parent}
{process = new QProcess(this);//程序启动后,发送信号connect(process, &QProcess::started, this, &ShowUnityWindow::unityProgramStarted);
}ShowUnityWindow::~ShowUnityWindow()
{//程序关闭后,要将外部程序unity窗口进程关闭process->kill();
}void ShowUnityWindow::startUnityProgram(const QString &unityExePath)
{//设置外部程序路径process->setProgram(unityExePath);//启动外部程序process->start(QIODevice::Truncate);qDebug() << "PID: " << process->processId();
}void ShowUnityWindow::setWindowParent(HWND parentWidgetHandle, const QString &sonWindowTitleName)
{//利用外部程序名称寻找其对应的windows窗口HWND hfigure = FindWindow(NULL,LPCWSTR(sonWindowTitleName.unicode()));qDebug() << hfigure;//SetParent根据父窗口句柄值parentWidgetHandle,把对应句柄值的父窗口设置为外部程序窗口的父窗口if (NULL != ::SetParent(hfigure, parentWidgetHandle))qDebug() << "Success";elseqDebug() << "Fail";
}
使用QT Designer设计一个窗口,这个widget是用来装载上面的unity外部程序的,同时也是要嵌入到QML界面中进行使用的,这样就可简介的将unity外部程序嵌入到QML界面当中,设计的widget窗口样式大致如下:只使用到了一个tabWidget控件,然后简单布局一下即可。

对应代码如下:
unitywidget.h:
#ifndef UNITYWIDGET_H
#define UNITYWIDGET_H#include
#include
#include namespace Ui {
class unityWidget;
}class unityWidget : public QWidget
{Q_OBJECTpublic:explicit unityWidget(QWidget *parent = nullptr);~unityWidget();//自定义延迟函数void selfSleep(unsigned int msec);private:Ui::unityWidget *ui;ShowUnityWindow* showUnity;//用于启动unity程序的类
};#endif // UNITYWIDGET_H
unitywidget.cpp:
#include "unitywidget.h"
#include "ui_unitywidget.h"unityWidget::unityWidget(QWidget *parent) :QWidget(parent),ui(new Ui::unityWidget)
{ui->setupUi(this);showUnity = new ShowUnityWindow(this);QString titleName("AFDemo");//在unity程序启动之前绑定信号,否则会找不到unity程序窗口connect(showUnity, &ShowUnityWindow::unityProgramStarted, this, [=](){selfSleep(400);//延迟一段时间再去设置unity子窗口的句柄,否则QT程序启动了,Unity程序还没启动,这样的话无法正确获得unity窗口的句柄showUnity->setWindowParent((HWND)ui->unityTab->winId(),titleName);});QString unityExePath(R"(E:\python_file\QtDocument\QmlUnity\QML_UnityGame\QML_UnityGame\games2\AFDemo.exe)");showUnity->startUnityProgram(unityExePath);//先启动unity程序,然后会发射信号,去设置其父窗口}unityWidget::~unityWidget()
{delete ui;
}//自定义延迟函数
void unityWidget::selfSleep(unsigned int msec)
{//用当前时间加上需要延迟的时间得到一个新的时间QTime reachTime = QTime::currentTime().addMSecs(msec);while(QTime::currentTime() < reachTime){QCoreApplication::processEvents(QEventLoop::AllEvents,100);}
}
这一步需要将上面创建的widget窗口嵌入到QML界面中进行使用,具体方法可参照博客:在QML界面中加载QWidget窗口。这里不再赘述,仅贴出相关代码如下:
布局辅助类
WdigetAnchorHelper.h:
#ifndef WDIGETANCHORHELPER_H
#define WDIGETANCHORHELPER_H#include
#include
#include
#include class WdigetAnchorHelper : public QObject
{Q_OBJECTpublic:explicit WdigetAnchorHelper(QWidget* mWidget, QQuickItem* mItem);~WdigetAnchorHelper();private://实时更新widge的位置void updateGeometry();private://QPointer用于创建一个指针 (效果等同于 QWidget* _mWidgetQPointer _mWidget;QPointer _mQuickItem;
};#endif // WDIGETANCHORHELPER_H
WdigetAnchorHelper.cpp:
#include "wdigetanchorhelper.h"WdigetAnchorHelper::WdigetAnchorHelper(QWidget* mWidget, QQuickItem* mItem):_mWidget(mWidget), _mQuickItem(mItem)
{//QML中的Item相关属性发生变化时,执行更新槽函数connect(_mQuickItem, &QQuickItem::xChanged, this, &WdigetAnchorHelper::updateGeometry);connect(_mQuickItem, &QQuickItem::yChanged, this, &WdigetAnchorHelper::updateGeometry);connect(_mQuickItem, &QQuickItem::widthChanged, this, &WdigetAnchorHelper::updateGeometry);connect(_mQuickItem, &QQuickItem::heightChanged, this, &WdigetAnchorHelper::updateGeometry);//初始化位置updateGeometry();
}WdigetAnchorHelper::~WdigetAnchorHelper()
{}void WdigetAnchorHelper::updateGeometry()
{if (_mQuickItem){_mWidget->setGeometry(_mQuickItem->x(),_mQuickItem->y(),_mQuickItem->width(),_mQuickItem->height());}
}
main.cpp:加载QWidget窗口
#include
#include
#include
#include
#include
#include #include "unitywidget.h"
#include "wdigetanchorhelper.h"int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif//创建项目时默认使用的是QGuiApplication,但因为要嵌入QWidget窗口//所以要使用QApplicationQApplication app(argc, argv);QQmlApplicationEngine engine;const QUrl url(QStringLiteral("qrc:/main.qml"));QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,&app, [url](QObject *obj, const QUrl &objUrl) {if (!obj && url == objUrl)QCoreApplication::exit(-1);}, Qt::QueuedConnection);engine.load(url);QObject *QmlObj = engine.rootObjects().at(0);//获取QML当中的根对象QWindow *QmlWindow = qobject_cast(QmlObj);//将跟对象转换成QWindow类型WId parentHWND = QmlWindow->winId();//获取Qml窗口的句柄QQuickItem* unityItem = QmlObj->findChild("widgetItem");//获取QML中用于装载widget的ItemunityWidget* mUnityWidget = new unityWidget(); //自定义widget窗体new WdigetAnchorHelper(mUnityWidget, unityItem);//将widget和Item加入自定义辅助类中,管理widget在QML中的布局mUnityWidget->setProperty("_q_embedded_native_parent_handle",QVariant(parentHWND));//给widget父对象句柄赋值mUnityWidget->winId();//必须调用,才能为widget创建句柄,否则将会失败mUnityWidget->windowHandle()->setParent(QmlWindow);//通过窗口句柄设置父窗口mUnityWidget->show();//在QML界面中显示自定义widget窗口return app.exec();
}
在main.qml文件中进行使用:
main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15Window {width: 1000height: 800visible: truetitle: qsTr("QML QWidget")Item{id:unityWidgetobjectName: "widgetItem"width: 600height: 500anchors.right: parent.rightanchors.top:parent.topanchors.topMargin: 35}Rectangle{id:btnRectanchors.top: parent.topanchors.left: parent.leftanchors.right: unityWidget.leftanchors.bottom: unityWidget.bottomanchors.bottomMargin: 40color: "lightblue"Text{anchors.centerIn: parenttext:"其它区域一"font.pixelSize: 30}}Rectangle{id:figueanchors.top: btnRect.bottomanchors.left: parent.leftanchors.right: parent.rightanchors.bottom: parent.bottomcolor: "lightgreen"Text{anchors.centerIn: parenttext:"其它区域二"font.pixelSize: 30}}
}