QML学习笔记(四十八)QML与C++交互:QML中可实例化C++对象

前言

在之前的学习中,无论是上下文属性、上下文对象、还是Q_PROPERTY宏,我们都是需要现在C++侧创建好一个实例化对象,然后将它暴露给QML引擎。也就是:

cpp 复制代码
engine.rootContext()->setContextProperty("Movie", &movie);
或
engine.rootContext()->setContextObject(&wrapper);

这意味着,这个对象的创建和管理,它的生命周期都是在C++端的,QML只是间接的使用者。

但在某些场合下,我们不希望在C++端维护一个C++类对象,因为它的功能和QML端强相关,比如设置了一些Q_PROPERTY属性,所以会希望它能直接在QML中创建、使用和管理,并随着QML的生命周期而销毁。此时,我们便需要再QML中实例化C++对象了。

一、qmlRegisterType注册模块和对象类型

想要在qml中实例C++对象,首先要将这个类型注册,告诉QML引擎。实现说明一下,我的qt版本是5.14.1,所以我首选这个方法。

我们先准备之前使用过的Movie类,因为和之前一样,只是暴露了两个字符串属性,直接粘贴代码:

cpp 复制代码
#ifndef MOVIE_H
#define MOVIE_H

#include <QObject>
#include <QtQml>

class Movie : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString mainCharacter READ mainCharacter WRITE setMainCharacter NOTIFY mainCharacterChanged)
    Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)

public:
    explicit Movie(QObject *parent = nullptr);

    QString mainCharacter() const;
    void setMainCharacter(const QString &newMainCharacter);

    QString title() const;
    void setTitle(const QString &newTitle);

signals:
    void mainCharacterChanged();
    void titleChanged();

private:
    QString m_mainCharacter;
    QString m_title;

};

#endif // MOVIE_H
cpp 复制代码
#include "movie.h"
#include <QDebug>
#include <QTimer>

Movie::Movie(QObject *parent) : QObject(parent)
{
}

QString Movie::mainCharacter() const
{
    return m_mainCharacter;
}

void Movie::setMainCharacter(const QString &newMainCharacter)
{
    if(m_mainCharacter == newMainCharacter)
        return;

    m_mainCharacter = newMainCharacter;
    emit mainCharacterChanged();    // 非常重要,否则qml中绑定该属性的地方将会失效
    qDebug() << "setMainCharacter..." << newMainCharacter;
}

QString Movie::title() const
{
    return  m_title;
}

void Movie::setTitle(const QString &newTitle)
{
    if(m_title == newTitle)
        return;

    m_title = newTitle;
    emit titleChanged();
    qDebug() << "setTitle..." << newTitle;
}

然后,我们需要在main中的qml引擎注册这个类型:

cpp 复制代码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <movie.h>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    qmlRegisterType<Movie>("com.mycompany", 1, 0, "Movie");

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

    return app.exec();
}

咱们重点关注这一句:

cpp 复制代码
qmlRegisterType<Movie>("com.mycompany", 1, 0, "Movie");

com.mycompany是你自定义的模块名,而前面的Movie是该类的本名,后面的Movie是暴露给qml的名字,事实上你可以取一个别名,都没有关系的。

最后在qml侧:

cpp 复制代码
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.12
import com.mycompany 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Movie{
        id: movieId
        title: "Titanic"
        mainCharacter: "Leonardo D"
    }

    Button {
        text: "Now"
        onClicked: {
            console.log("Now: " + movieId.title + " , " + movieId.mainCharacter);
        }
    }

    Button {
        y:50
        text: "New"
        onClicked: {
            movieId.title = "Fast and Furious"
            movieId.mainCharacter = "Vin Diesel"

            console.log("New: " + movieId.title + " , " + movieId.mainCharacter);
        }
    }
}

这里首先创建了一个Movie对象,因为title和mainCharacter已经实现做过属性设置,也就是Q_PROPERTY,所以可以直接当做属性来初始化。如果是其他成员变量是不行的哦。

然后,我设置了两个按钮,now按钮只是单纯的打印一下当前的属性,而new会进行一次修改。

看下运行效果:

可以修改属性,符合预期。

二、QML_ELEMENT (QT6)

在qt6中,似乎有QML_ELEMENT 这个宏,可以用来修饰C++类。此时无需专门的qmlRegisterType注册,QML侧就可以识别这个类了,算是一个优化。

不过我没安装,所以尝试不了,先记录一下。

三、总结

  1. 上下文属性/对象 + Q_PROPERTY

    = C++ 先实例、QML 只引用

    → 生命周期由 C++ 控制,适合 全局单例、工具、配置

  2. qmlRegisterType + QML 里 MyType {}

    = QML 按需实例、随 QML 生命周期销毁

    → 适合 有状态的小部件、页面、临时业务对象

    避免 C++ 端"为了 QML 而 new" 的维护负担。

以上是AI帮我总结的。的确,在qml中实例C++对象会有点违反直觉,原因是C++对象并不是一个界面组件类。但某些轻量封装的,与界面相关的业务对象,也是可以放到qml侧的。

值得一提的是,我昨晚尝试过在qml显示摄像头画面,调用了qml中的Camera类型,它本质上也不是一个界面相关的类。

我问了一下ai,给我回答:

Camera、Network、Timer、PositionSource 等 "纯 QML 类型" 本质上都是 C++ 类 , 只是 Qt 提前用 qmlRegisterType / qmlRegisterSingletonType 注册进了 QML 类型系统,

所以你在 QML 里写:Camera { }并不是 "QML 自己 new 了一个新语言对象", 而是 Qt 的 C++ 实现 被实例化,生命周期由 QML 引擎管理,
和你自己写的 MyType {} 原理完全一样

相关推荐
Nan_Shu_6144 小时前
学习:JavaScript(1)
开发语言·javascript·学习·ecmascript
青衫码上行4 小时前
【Java Web学习 | 第三篇】CSS(2) - 元素显示模式
java·前端·学习
2301_803554524 小时前
c++调用客户端库与kafka交互
c++·kafka·交互
小年糕是糕手4 小时前
【C/C++刷题集】二叉树算法题(一)
c语言·数据结构·c++·算法·leetcode·学习方法·改行学it
柑橘乌云_4 小时前
学习记录-package.json的scripts添加参数的方式有那些
前端·学习·node.js·json
MeowKnight9584 小时前
【数据结构】单链表 练习记录
笔记
江苏世纪龙科技6 小时前
新能源汽车故障诊断与排除虚拟实训软件——赋能职业教育新工具
学习
伐尘8 小时前
【Qt】实现单例程序,禁止程序多开的几种方式
qt
Felicity_Gao8 小时前
uni-app VOD 与 COS 选型、开发笔记
前端·笔记·uni-app