目前QT官方主推的界面开发方式是采用QML进行界面设计,但在QML未流行之前,很多的项目都是采用QWidget开发的,把之前的代码全部转换为QML代码显然工作量非常大,如果能将QWidget窗口嵌入到QML界面中,那么开发效率将提高很多,且QML无法实现的功能也能借助于QWidget框架进行实现。
本篇文章将介绍一种方式来实现QML和QWidget的混合开发
其中,参考了两篇文章:
第一篇:震惊!QWidget竟然可以嵌入到QML中,QMl窗口句柄竟然是这样获取,这篇文章能够成功将QWidget窗口嵌入到QML界面中,但无法实现QWidget窗口的的任意布局。
第二篇:在QML 中,嵌入QWidget 对象,这篇文章介绍了另一种思路,按照其说法是可以实现QWidget窗口在QML界面中任意布局的,但其博客里提供的代码有残缺,真正移植到自己的项目中还是无法实现功能。
所以,本文章结合上述两位大佬的思路,融合者两种方法,实现了预期效果。
效果展示:
QML_QWidget
整体的项目框架如图所示:因为要使用到QWidget,所以要在项目的.pro文件中加入quickwidgets库:QT += quick quickwidgets
既然是要将QWidget窗口嵌入到QML界面中,那么先创建一个QWidget窗口,使用自带的Qt Designer设计大师进行简单布局,在其中放置一个tabWidget控件,简单设置一下样式,如下图所示:
这里只是简单的设计了一下QWdiget的窗口,并没有实现什么功能,所以代码相对简单,如下所示:
unitywidget.h:
#ifndef UNITYWIDGET_H
#define UNITYWIDGET_H
#include
namespace Ui {
class unityWidget;
}
class unityWidget : public QWidget
{
Q_OBJECT
public:
explicit unityWidget(QWidget *parent = nullptr);
~unityWidget();
private:
Ui::unityWidget *ui;
};
#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);
}
unityWidget::~unityWidget()
{
delete ui;
}
因为想要在QML界面中像其他控件一样使用锚布局对QWdiget窗口进行布局,但是直接加进来的QWidget窗口是无法实现的,所以要自定义一个辅助类,这个辅助类就是专门对QWdiget窗口进行布局的,该类的构造函数需要两个参数,一个是要加载到QML界面中的QWdiget窗口,另一个是QML界面中的一个Item控件,这个Item控件将会成为QWdiget窗口的父对象,在QML界面中对这个Item布局,即可间接实现对QWdiget窗口布局。辅助类的代码如下:
WdigetAnchorHelper.h:
#ifndef WDIGETANCHORHELPER_H
#define WDIGETANCHORHELPER_H
#include
#include
#include
#include
class WdigetAnchorHelper : public QObject
{
Q_OBJECT
public:
explicit WdigetAnchorHelper(QWidget* mWidget, QQuickItem* mItem);
~WdigetAnchorHelper();
private:
//实时更新widge的位置
void updateGeometry();
private:
//QPointer用于创建一个指针 (效果等同于 QWidget* _mWidget
QPointer<QWidget> _mWidget;
QPointer<QQuickItem> _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.qml文件中,在根节点Window内添加一个Item控件,这个控件是用来装载即将加载进来的QWidget窗口的,本代码中,左部分显示一张图片,右部分显示QWidget窗口,代码如下所示:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 500
height: 300
visible: true
title: qsTr("QML QWidget")
Image{
width: parent.width/2
height: parent.height
anchors.left: parent.left
source: "qrc:/images/katong.jfif"
}
Item{
objectName: "widgetItem"
width: parent.width/2
height: parent.height
anchors.right: parent.right
}
}
在项目的main.cpp中,需要获取QML中的根元素以及相关控件对象,并设置自定义的窗口句柄和其父窗口,一些其他的注意事项和具体解释详看代码注释,整体代码如下:
#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窗口
//所以要使用QApplication
QApplication 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<QWindow*>(QmlObj);//将跟对象转换成QWindow类型
WId parentHWND = QmlWindow->winId();//获取Qml窗口的句柄
QQuickItem* unityItem = QmlObj->findChild<QQuickItem*>("widgetItem");//获取QML中用于装载widget的Item
unityWidget* mUnityWidget = new unityWidget(); //自定义widget窗体
mUnityWidget->setProperty("_q_embedded_native_parent_handle",QVariant(parentHWND));//给widget父对象句柄赋值
mUnityWidget->winId();//必须调用,才能为widget创建句柄,否则将会失败
mUnityWidget->windowHandle()->setParent(QmlWindow);//通过窗口句柄设置父窗口
new WdigetAnchorHelper(mUnityWidget, unityItem);//将widget和Item加入自定义辅助类中,管理widget在QML中的布局
mUnityWidget->show();//在QML界面中显示自定义widget窗口
return app.exec();
}