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 {} 原理完全一样

相关推荐
你怎么知道我是队长2 小时前
C语言---typedef
c语言·c++·算法
带土13 小时前
5. enum(枚举)关键字在C/C++中的作用
c语言·c++
豆几的窝3 小时前
230+效率功能2026AI插件脚本大合集V7.0最新中文版支持Illustrator 2026-CS6 Win/Mac
ui·illustrator
气概3 小时前
法奥机器人学习使用
学习·junit·机器人
驴友花雕3 小时前
【花雕学编程】Arduino BLDC 之群体机器人协同探索
c++·单片机·嵌入式硬件·arduino bldc·群体机器人协同探索
驴友花雕3 小时前
【花雕学编程】Arduino BLDC 之仿人机器人膝关节稳定系统
c++·单片机·嵌入式硬件·arduino bldc·仿人机器人膝关节稳定系统
Qhumaing3 小时前
C++学习:【PTA】数据结构 7-1 实验7-1(最小生成树-Prim算法)
c++·学习·算法
好大哥呀4 小时前
Java Web的学习路径
java·前端·学习
Z1Jxxx5 小时前
01序列01序列
开发语言·c++·算法
梦雨羊6 小时前
Base-NLP学习
人工智能·学习·自然语言处理