QML学习笔记(四十八)QML与C++交互:QML中可实例化C++对象
前言
在之前的学习中,无论是上下文属性、上下文对象、还是Q_PROPERTY宏,我们都是需要现在C++侧创建好一个实例化对象,然后将它暴露给QML引擎。也就是:
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类,因为和之前一样,只是暴露了两个字符串属性,直接粘贴代码:
#ifndef MOVIE_H
#define MOVIE_H#include <QObject>
#include <QtQml>class Movie : public QObject
{Q_OBJECTQ_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
#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引擎注册这个类型:
#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();
}
咱们重点关注这一句:
qmlRegisterType<Movie>("com.mycompany", 1, 0, "Movie");
com.mycompany是你自定义的模块名,而前面的Movie是该类的本名,后面的Movie是暴露给qml的名字,事实上你可以取一个别名,都没有关系的。
最后在qml侧:
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.12
import com.mycompany 1.0Window {visible: truewidth: 640height: 480title: qsTr("Hello World")Movie{id: movieIdtitle: "Titanic"mainCharacter: "Leonardo D"}Button {text: "Now"onClicked: {console.log("Now: " + movieId.title + " , " + movieId.mainCharacter);}}Button {y:50text: "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侧就可以识别这个类了,算是一个优化。
不过我没安装,所以尝试不了,先记录一下。
三、总结
-
上下文属性/对象 + Q_PROPERTY
= C++ 先实例、QML 只引用
→ 生命周期由 C++ 控制,适合 全局单例、工具、配置。 -
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 {}原理完全一样。
