QML 和 C++ 之间的数据类型转换
当数据值在 QML 和 C++ 之间交换时,它们会被 QML 引擎转换为适合 QML 或 C++ 使用的正确数据类型。这就要求交换的数据必须是引擎可识别的类型。
QML 引擎为大量 Qt C++ 数据类型提供内置支持。此外,定制的 C++ 类型可在 QML 类型系统中注册,以便引擎使用。
有关 C++ 和不同 QML 集成方法的更多信息,请参阅C++ 和 QML 集成概述页面。
本页讨论 QML 引擎支持的数据类型,以及它们如何在 QML 和 C++ 之间转换。
数据所有权
当数据从 C++ 传输到 QML 时,数据的所有权始终属于 C++。这个规则的例外是当一个QObject 从一个明确的 C++ 方法调用返回时:在这种情况下,QML 引擎承担对象的所有权,除非对象的所有权已明确设置为保留在 C++ 中,通过调用QQmlEngine::setObjectOwnership() 并指定了 QQmlEngine::CppOwnership。
此外,QML 引擎尊重 Qt C++ 对象的正常QObject parent ownership 语义,绝不会删除有父对象的QObject 实例。
基本 Qt 数据类型
默认情况下,QML 识别以下 Qt 数据类型,当从 C++ 传递到 QML 时,它们会自动转换为相应的QML 值类型,反之亦然:
Qt 类型 | QML 值类型 |
bool | bool |
无符号 int, int | int |
双 | double |
浮点数 | real |
QString | string |
QUrl | url |
QColor | color |
QFont | font |
QDateTime | date |
QPoint,QPointF | point |
QSize,QSizeF | size |
QRect,QRectF | rect |
QMatrix4x4 | matrix4x4 |
QQuaternion | quaternion |
QVector2D,QVector3D 、QVector4D | vector2d,vector3d 、vector4d |
注: 由 Qt GUI模块提供的类,如QColor,QFont,QQuaternion 和QMatrix4x4 ,只有当 QML Qt Quick模块时,才能从 QML 获取。
为方便起见,QML 可通过字符串值或QtQml::Qt 对象提供的相关方法指定这些类型中的许多类型。例如,Image::sourceSize 属性的类型是size (自动转换为QSize 类型),可通过格式化为 "widthx
height "的字符串值指定,或通过 Qt.size() 函数指定:
更多信息,请参阅QML 值类型下各类型的文档。
QObject 派生类型
任何QObject 衍生类都可用作 QML 和 C++ 之间的数据交换类型,前提是该类已在 QML 类型系统中注册。
该引擎允许注册可实例化和不可实例化的类型。一旦类注册为 QML 类型,它就可用作 QML 和 C++ 之间交换数据的数据类型。有关类型注册的更多详情,请参阅Registering C++ types with the QML type system(在 QML 类型系统中注册 C++ 类型)。
Qt 和 JavaScript 类型之间的转换
在 QML 和 C++ 之间传输数据时,QML 引擎内置支持将一些 Qt 类型转换为相关的 JavaScript 类型,反之亦然。这使得使用这些类型并在 C++ 或 JavaScript 中接收它们成为可能,而无需实现自定义类型来提供对数据值及其属性的访问。
(请注意,QML 中的 JavaScript 环境修改了本地 JavaScript 对象原型,包括String
,Date
和Number
的原型,以提供额外的功能。详情请参阅JavaScript 主机环境)。
从 QVariantList 和 QVariantMap 到 JavaScript Array-like 和 Object
QML 引擎提供QVariantList 和 JavaScript 类似数组之间,以及QVariantMap 和 JavaScript 对象之间的自动类型转换。
用 ECMAScript 术语来说,类数组就是像数组一样使用的对象。从QVariantList (以及其他 C++ 序列容器)转换而来的类数组不仅如此,还提供了常用的数组方法,并自动同步长度属性。它们可以像 JavaScript 数组一样用于任何实际用途。不过Array.isArray()方法仍会返回 false。
例如,下面 QML 中定义的函数需要两个参数,一个数组和一个对象,并使用数组和对象项访问的标准 JavaScript 语法打印它们的内容。下面的 C++ 代码调用了这个函数,传入了QVariantList 和QVariantMap ,它们分别被自动转换为 JavaScript 数组类和对象类的值:
QML | // MyItem.qml Item { function readValues(anArray, anObject) { for (var i=0; i<anArray.length; i++) console.log("Array item:", anArray[i]) for (var prop in anObject) { console.log("Object item:", prop, "=", anObject[prop]) } } } |
C++ | // C++ QQuickView view(QUrl::fromLocalFile("MyItem.qml")); QVariantList list; list << 10 << QColor(Qt::green) << "bottles"; QVariantMap map; map.insert("language", "QML"); map.insert("released", QDate(2010, 9, 21)); QMetaObject::invokeMethod(view.rootObject(), "readValues", Q_ARG(QVariant, QVariant::fromValue(list)), Q_ARG(QVariant, QVariant::fromValue(map))); |
输出结果如下
Array item: 10 Array item: #00ff00 Array item: bottles Object item: language = QML Object item: released = Tue Sep 21 2010 00:00:00 GMT+1000 (EST)
同样,如果 C++ 类型的属性类型或方法参数使用了QVariantList 或QVariantMap 类型,则该值可在 QML 中创建为 JavaScript 数组或对象,并在传入 C++ 时自动转换为QVariantList 或QVariantMap 。
自 Qt 6.5 起,C++ 类型的QVariantList 属性可由 Qt Qml 代码就地更改。自 Qt 6.9 起,C++ 类型的QVariantMap 属性可由 QML 代码就地更改。
将 QDateTime 转换为 JavaScript Date
QML 引擎提供QDateTime 值和 JavaScriptDate
对象之间的自动类型转换。
例如,下面 QML 中定义的函数期望得到一个 JavaScriptDate
对象,并返回一个包含当前日期和时间的新Date
对象。下面的 C++ 代码会调用该函数,并传递一个QDateTime 值,当该值被传递到readDate()
函数时,引擎会自动将其转换为Date
对象。反过来,readDate() 函数会返回一个Date
对象,C++ 收到该对象后会自动将其转换为QDateTime 值:
QML | // MyItem.qml Item { function readDate(dt) { console.log("The given date is:", dt.toUTCString()); return new Date(); } } |
C++ | // C++QQuickViewview(QUrl::fromLocalFile("MyItem.qml"));QDateTimedateTime=QDateTime::currentDateTime();QDateTimeretValue;QMetaObject::invokeMethod(view.rootObject(), "readDate",Q_RETURN_ARG(QVariant,retValue),Q_ARG(QVariant,QVariant::fromValue(dateTime))); qDebug() << "Value returned from readDate():" << retValue; |
同样,如果 C++ 类型的属性类型或方法参数使用了QDateTime ,该值可在 QML 中创建为 JavaScriptDate
对象,并在传递给 C++ 时自动转换为QDateTime 值。
注: 注意月份编号的不同:JavaScript 将 1 月编号为 0 至 11(12 月),与 Qt 将 1 月编号为 1 至 12(12 月)相差 1 个月。
注意: 在 JavaScript 中使用字符串作为Date
对象的值时,请注意没有时间字段的字符串(因此是一个简单的日期)会被解释为相关日期的 UTC 开始时间,而new Date(y, m, d)
则使用当地时间的开始时间。在 JavaScript 中构建Date
对象的大多数其他方法都会生成本地时间,除非使用了名称中包含 UTC 的方法。如果您的程序运行在 UTC 之后的区域(名义上在本初子午线以西),那么使用纯日期字符串将导致Date
对象的getDate()
比字符串中的日数少一个;它通常会有一个较大的getHours()
值。这些方法的UTC 变体getUTCDate()
和getUTCHours()
将为这样的Date
对象提供您所期望的结果。另请参阅下一节。
QDate 和 JavaScript 日期
QML 引擎会自动在QDate 和 JavaScriptDate
类型之间转换,用 UTC 日的起点表示日期。日期通过QDateTime 映射回QDate ,选择date() 方法,使用日期的当地时间形式,除非它的 UTC 形式与下一天的开始时间重合,在这种情况下,使用 UTC 形式。
这种略显偏心的安排是为了解决以下问题:JavaScript 通过纯日期字符串构建Date
对象时使用的是 UTC 日期的起始时间,而new Date(y, m, d)
则使用的是指定日期的当地时间起始时间,这一点在上一节末尾的注释中已经讨论过。
因此,当QDate 属性或参数暴露给 QML 时,读取其值时应小心:与名称中没有 UTC 的相应方法相比,Date.getUTCFullYear()
、Date.getUTCMonth()
和Date.getUTCDate()
方法更有可能提供用户期望的结果。
因此,使用QDateTime 属性通常更稳健。这样就可以在QDateTime 端控制日期(和时间)是以 UTC 还是以本地时间指定的;只要 JavaScript 代码是按照相同的标准编写的,就可以避免麻烦。
QTime 和 JavaScript 日期
QML 引擎提供从QTime 值到 JavaScriptDate
对象的自动类型转换。由于QTime 值不包含日期组件,因此只为转换创建了一个日期组件。因此,您不应依赖转换后 Date 对象的日期组件。
在引擎盖下,从 JavaScriptDate
对象到QTime 的转换是通过转换为QDateTime 对象(使用本地时间)并调用其time() 方法完成的。
序列类型到 JavaScript 数组
有关序列类型的一般描述,请参阅QML 序列类型。QtQml module 包含一些您可能想使用的序列类型。
你也可以通过使用QJSEngine::newArray() 构造QJSValue 来创建类似列表的数据结构。这样的 JavaScript 数组在 QML 和 C++ 之间传递时不需要任何转换。有关如何从 C++ 操作 JavaScript 数组的详情,请参阅QJSValue#Working With Arrays 。
从 QByteArray 到 JavaScript ArrayBuffer
QML 引擎提供QByteArray 值和 JavaScriptArrayBuffer
对象之间的自动类型转换。
值类型
Qt 中的一些值类型(如QPoint )在 JavaScript 中表示为对象,这些对象具有与 C++ API 中相同的属性和功能。自定义 C++ 值类型也能实现相同的表示。要在 QML 引擎中启用自定义值类型,需要在类声明中注释Q_GADGET
。需要用Q_PROPERTY
声明在 JavaScript 表示法中可见的属性。同样,函数也需要用Q_INVOKABLE
标注。这与基于QObject 的 C++ API 相同。例如,下面的Actor
类被注释为 gadget 并具有属性:
class Actor { Q_GADGET Q_PROPERTY(QString name READ name WRITE setName) public: QString name() const { return m_name; } void setName(const QString &name) { m_name = name; } private: QString m_name; }; Q_DECLARE_METATYPE(Actor)
通常的模式是使用一个小工具类作为属性的类型,或发射一个小工具作为信号参数。在这种情况下,小工具实例在 C++ 和 QML 之间以值传递(因为它是值类型)。如果 QML 代码改变了小工具属性的一个属性,整个小工具就会重新创建并传回 C++ 属性设置器。在 Qt 5 中,小工具类型不能通过在 QML 中直接声明来实例化。相反,可以声明QObject 实例;而且QObject 实例总是通过指针从 C++ 传递到 QML。
枚举类型
要使用自定义枚举作为数据类型,必须注册它的类,还必须用Q_ENUM() 声明枚举,以便在 Qt 的元对象系统中注册。例如,下面的Message
类有一个Status
枚举:
class Message : public QObject { Q_OBJECT Q_PROPERTY(Status status READ status NOTIFY statusChanged) public: enum Status { Ready, Loading, Error }; Q_ENUM(Status) Status status() const; signals: void statusChanged(); };
如果Message
类已在 QML 类型系统中注册,它的Status
枚举就可以在 QML 中使用:
Message { onStatusChanged: { if (status == Message.Ready) console.log("Message is loaded!") } }
要在 QML 中使用枚举作为flags 类型,请参阅Q_FLAG() 。
注意: 枚举值的名称必须以大写字母开头,才能从 QML 访问。
... enum class Status { Ready, Loading, Error } Q_ENUM(Status) ...
枚举类在 QML 中注册为作用域和非作用域属性。Ready
值将在Message.Status.Ready
和Message.Ready
注册。
使用枚举类时,可以有多个枚举使用相同的标识符。非作用域注册将被最后注册的枚举覆盖。对于包含此类名称冲突的类,可以通过使用特殊的Q_CLASSINFO 宏注释类来禁用非作用域注册。使用值为false
的名称RegisterEnumClassesUnscoped
,可防止作用域枚举合并到同一名称空间。
class Message : public QObject { Q_OBJECT Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") Q_ENUM(ScopedEnum) Q_ENUM(OtherValue) public: enum class ScopedEnum { Value1, Value2, OtherValue }; enum class OtherValue { Value1, Value2 }; };
相关类型的枚举通常在相关类型的作用域中注册。例如,在Q_PROPERTY 声明中使用不同类型的枚举,会导致该类型的所有枚举在 QML 中可用。这通常是一种责任而不是功能。为了防止这种情况发生,请用一个特殊的Q_CLASSINFO 宏注解您的类。使用RegisterEnumsFromRelatedTypes
和false
来防止相关类型的枚举在该类型中注册。
您应该使用QML_ELEMENT 或QML_NAMED_ELEMENT 明确注册您想在 QML 中使用的枚举的外层类型,而不是依赖于它们的枚举被注入到其他类型中。
class OtherType : public QObject { Q_OBJECT QML_ELEMENT public: enum SomeEnum { A, B, C }; Q_ENUM(SomeEnum) enum AnotherEnum { D, E, F }; Q_ENUM(AnotherEnum) }; class Message : public QObject { Q_OBJECT QML_ELEMENT // This would usually cause all enums from OtherType to be registered // as members of Message ... Q_PROPERTY(OtherType::SomeEnum someEnum READ someEnum CONSTANT) // ... but this way it doesn't. Q_CLASSINFO("RegisterEnumsFromRelatedTypes", "false") public: OtherType::SomeEnum someEnum() const { return OtherType::B; } };
重要的区别在于 QML 中枚举的作用域。如果相关类中的枚举被自动注册,其作用域就是它被导入的类型。在上面的例子中,如果没有额外的Q_CLASSINFO ,你会使用Message.A
,例如。如果持有枚举的 C++ 类型是显式注册的,而相关类型的枚举注册被抑制,那么持有枚举的 C++ 类型的 QML 类型就是它所有枚举的作用域。在 QML 中,您可以使用OtherType.A
而不是Message.A
。
请注意,您可以使用QML_FOREIGN 注册您不能修改的类型。您也可以使用QML_FOREIGN_NAMESPACE 将 C++ 类型的枚举器注册到任何大写名称的 QML 命名空间,即使同一 C++ 类型也注册为 QML 值类型。
作为信号和方法参数的枚举类型
带有枚举类型参数的 C++ 信号和方法可以在 QML 中使用,前提是枚举和信号或方法都在同一个类中声明,或者枚举值是Qt Namespace 中声明的值之一。
此外,如果带有枚举参数的 C++ 信号可使用connect()函数连接到 QML 函数,则必须使用qRegisterMetaType() 注册枚举类型。
对于 QML 信号,枚举值可以使用int
类型作为信号参数传递:
Message { signal someOtherSignal(int statusValue) Component.onCompleted: { someOtherSignal(Message.Loading) } }
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.