QML 与 C++ 之间的数据类型转换
当 QML 与 C++ 之间交换数据值时,QML 引擎会对其进行转换,使其具有适合在 QML 或 C++ 中使用的正确数据类型。这要求交换的数据类型是引擎能够识别的。
Qt Qml 引擎为大量 Qt C++ 数据类型提供了内置支持。此外,还可以将自定义的 C++ 类型注册到 Qml 类型系统中,以便引擎能够使用它们。
有关 C++ 以及各种 QML 集成方法的更多信息,请参阅C++ 与 QML 集成概述页面。
本页介绍了 QML 引擎支持的数据类型,以及它们在 QML 和 C++ 之间的转换方式。
数据所有权
当数据从 C++ 传输到 QML 时,数据的所有权始终归 C++ 所有。 此规则的例外情况是,当从显式的 C++ 方法调用中返回一个 `QObject ` 时:在这种情况下,QML 引擎将接管该对象的所有权,除非通过调用 `QQmlEngine::setObjectOwnership()` 并指定 `QQmlEngine::CppOwnership` 来显式地将对象的所有权保留给 C++。
此外,QML 引擎遵循 Qt C++ 对象的常规QObject 父对象所有权语义,并且绝不会删除具有父对象的QObject 实例。
Qt 基本数据类型
默认情况下,Qml 识别以下 Qt 数据类型,当这些类型在 C++ 和 Qml 之间传递时,会自动转换为相应的Qml 值类型:
| Qt 类型 | QML 值类型 |
| bool | bool |
| 无符号整数、整数 | int |
| double | double |
| float、qreal | 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 ,只有在包含 Qt Quick 模块被引入时,才可在 QML 中使用。
为方便起见,其中许多类型可以在 QML 中通过字符串值,或通过QtQml::Qt 对象提供的相关方法来指定。例如,Image::sourceSize 属性的类型为size (该类型会自动转换为QSize 类型),可以通过格式为“widthxheight”的字符串值来指定,也可以通过 Qt 的size() 函数来指定:
有关更多信息,请参阅《QML 值类型》中关于每种具体类型的文档。
从 QObject 派生的类型
任何从QObject 派生的类均可作为QML与C++之间数据交换的类型,前提是该类已在QML类型系统中注册。
引擎允许注册可实例化和不可实例化的类型。一旦类被注册为 QML 类型,即可用作 QML 与 C++ 之间交换数据的数据类型。有关类型注册的更多详细信息,请参阅《在 QML 类型系统中注册 C++ 类型》。
Qt 与 JavaScript 类型之间的转换
在 Qml 与 C++ 之间传输数据时,Qml 引擎内置了将多种 Qt 类型转换为相关 JavaScript 类型(反之亦然)的支持。这使得可以在 C++ 或 JavaScript 中使用和接收这些类型,而无需实现自定义类型来访问数据值及其属性。
(请注意,QML 中的 JavaScript 环境会修改原生 JavaScript 对象的原型,包括String 、Date 和Number 的原型,以提供额外功能。有关更多详细信息,请参阅JavaScript 主机环境。)
QVariantList 和 QVariantMap 转换为 JavaScript 数组样式和对象
QML 引擎支持在QVariantList 与 JavaScript 数组样式之间,以及在QVariantMap 与 JavaScript 对象之间进行自动类型转换。
在 ECMAScript 中,“数组样式”是指像数组一样使用的对象。由QVariantList (及其他 C++ 顺序容器)转换而来的数组样式不仅具备这一特性,还提供了常见的数组方法,并会自动同步length属性。在任何实际应用中,它们都可以像 JavaScript 数组一样使用。 不过,Array.isArray()方法对它们仍会返回 false。
例如,下面在 QML 中定义的函数期望两个参数——一个数组和一个对象,并使用 JavaScript 标准语法(用于访问数组和对象的元素)打印它们的内容。下面的 C++ 代码调用了该函数,并传入一个 `QVariantList ` 和一个 `QVariantMap`,它们会被自动转换为 JavaScript 数组样式和对象值:
| QML | |
| C++ | |
这将产生如下输出:
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 起,QML 代码可以就地修改 C++ 类型的QVariantList 属性。自 Qt 6.9 起,QML 代码可以就地修改 C++ 类型的QVariantMap 属性。
QDateTime 转换为 JavaScript Date
QML 引擎支持在QDateTime 值与 JavaScriptDate 对象之间进行自动类型转换。
例如,下面在 QML 中定义的函数期望接收一个 JavaScriptDate 对象,并且返回一个包含当前日期和时间的新Date 对象。 下面的 C++ 代码调用了该函数,并传递了一个 `QDateTime ` 值。当该值被传递给 `readDate() ` 函数时,引擎会将其自动转换为 `Date ` 对象。随后,`readDate()` 函数返回一个 `Date ` 对象,当该对象被 C++ 接收时,会自动转换为 `QDateTime ` 值:
| QML | |
| C++ | |
同样地,如果某个 C++ 类型将QDateTime 用作属性类型或方法参数,该值可以在 QML 中作为 JavaScriptDate 对象创建,并在传递给 C++ 时自动转换为QDateTime 值。
注意:请 注意月份编号的差异:JavaScript 将 1 月至 12 月的编号为 0 至 11,而 Qt 的编号为 1 至 12,两者相差 1。
注意: 在 JavaScript 中将字符串用作 `Date ` 对象的值时, 请注意:不包含时间字段的字符串(即简单的日期)将被解释为该日期的 UTC 起始时间,而 `new Date(y, m, d) ` 则使用当地时间的起始时间。 在 JavaScript 中,构建Date 对象的大多数其他方法都会产生本地时间,除非使用名称中包含 UTC 的方法。 如果您的程序运行在比 UTC 晚的时区(名义上位于本初子午线以西),使用仅包含日期的字符串将生成一个Date 对象,其getDate() 值比字符串中的日期编号少 1;此时getHours() 的值通常会很大。 这些方法的 UTC 变体(getUTCDate() 和getUTCHours() )将针对此类Date 对象返回您预期的结果。另请参阅下一节。
QDate 与 JavaScript Date
QML引擎通过将日期表示为该日期的UTC起始时间,自动在QDate 与JavaScript的Date 类型之间进行转换。日期可通过QDateTime 映射回QDate ,该方法调用其date()方法,并使用日期的本地时间格式;除非该日期的UTC格式恰好与次日开始时间重合,在此情况下将使用UTC格式。
这种略显另类的安排是一种变通方案,旨在解决以下问题:正如上一节末尾的注释所讨论的,JavaScript 从仅包含日期的字符串构建Date 对象时采用 UTC 时间作为一天的开始,而new Date(y, m, d) 则采用指定日期的本地时间作为一天的开始。
因此,当QDate 的属性或参数暴露给QML时,读取其值时应格外谨慎:Date.getUTCFullYear() 、Date.getUTCMonth() 和Date.getUTCDate() 方法比名称中不包含UTC的相应方法更可能返回用户预期的结果。
因此,通常使用QDateTime 属性更为稳健。这使得在QDateTime 一侧能够控制日期(和时间)是按 UTC 还是本地时间指定的;只要 JavaScript 代码遵循同一标准编写,就应该能够避免问题。
QTime 与 JavaScript Date
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 注解。希望在 JavaScript 表示中可见的属性需要使用Q_PROPERTY 声明。同样,函数需要使用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 代码更改了 gadget 属性的某个属性,则会重新创建整个 gadget 并将其传递回 C++ 属性设置器。在 Qt 5 中,无法通过在 QML 中直接声明来实例化 gadget 类型。相比之下,可以声明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 宏来禁用无作用域注册。使用名称RegisterEnumClassesUnscoped 并设置值为false ,可防止有作用域的枚举合并到同一命名空间中。
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 中使用的任何枚举,您都应通过QML_ELEMENT 或QML_NAMED_ELEMENT 显式注册其外围类型,而不是依赖其枚举会被注入到其他类型中。
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 值类型也是如此。
作为信号和方法参数的枚举类型
只要枚举和信号或方法均在同一个类中声明,或者枚举值是Qt Namespace 中声明的值之一,则 QML 中可以使用具有枚举类型参数的 C++ 信号和方法。
此外,如果需要使用connect()函数将一个带有枚举参数的 C++ 信号与 QML 函数建立连接,则必须通过qRegisterMetaType() 注册该枚举类型。
对于 QML 信号,可以使用int 类型将枚举值作为信号参数传递:
Message {
signal someOtherSignal(int statusValue)
Component.onCompleted: {
someOtherSignal(Message.Loading)
}
}© 2026 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.