前言
在之前的学习中,无论是上下文属性、上下文对象、还是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侧就可以识别这个类了,算是一个优化。
不过我没安装,所以尝试不了,先记录一下。
三、总结
-
上下文属性/对象 + 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 {}原理完全一样。