24. 在QML界面中嵌入QWidget窗口

1. 说明:

目前QT官方主推的界面开发方式是采用QML进行界面设计,但在QML未流行之前,很多的项目都是采用QWidget开发的,把之前的代码全部转换为QML代码显然工作量非常大,如果能将QWidget窗口嵌入到QML界面中,那么开发效率将提高很多,且QML无法实现的功能也能借助于QWidget框架进行实现。
本篇文章将介绍一种方式来实现QML和QWidget的混合开发
其中,参考了两篇文章:
第一篇:震惊!QWidget竟然可以嵌入到QML中,QMl窗口句柄竟然是这样获取,这篇文章能够成功将QWidget窗口嵌入到QML界面中,但无法实现QWidget窗口的的任意布局。
第二篇:在QML 中,嵌入QWidget 对象,这篇文章介绍了另一种思路,按照其说法是可以实现QWidget窗口在QML界面中任意布局的,但其博客里提供的代码有残缺,真正移植到自己的项目中还是无法实现功能。
所以,本文章结合上述两位大佬的思路,融合者两种方法,实现了预期效果。
效果展示:

QML_QWidget

2. 实现步骤:

整体的项目框架如图所示:因为要使用到QWidget,所以要在项目的.pro文件中加入quickwidgets库:QT += quick quickwidgets
24. 在QML界面中嵌入QWidget窗口_第1张图片

2.1 第一步:

既然是要将QWidget窗口嵌入到QML界面中,那么先创建一个QWidget窗口,使用自带的Qt Designer设计大师进行简单布局,在其中放置一个tabWidget控件,简单设置一下样式,如下图所示:
24. 在QML界面中嵌入QWidget窗口_第2张图片
这里只是简单的设计了一下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;
}

2.2 第二步:

因为想要在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());
    }
}

2.3 第三步:

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
    }
}

2.4 第四步:

在项目的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();
}

3. 实际效果展示:

24. 在QML界面中嵌入QWidget窗口_第3张图片

持续更新中,请大家多多关注…

你可能感兴趣的:(QML常用技巧汇总,QML,QWidget,窗口嵌入)