QML与C++之间的数据类型转换

当数据值在QML和C++之间交换时,它们将由QML引擎转换为在QML或C++中使用的数据类型。这需要交换的数据要由引擎可识别的类型。

QML引擎为大量Qt C++数据类型提供了内置支持。此外,可以通过将自定义C++类型注册到QML类型系统中,使它们对引擎可用。

有关C++和不同QML集成方法的信息,请参阅C++和QML集成概述页面。

本页讨论了QML引擎支持的数据类型以及它们如何在QML和C++之间进行转换。

数据拥有权

当数据从C++传输到QML时,数据的所有权总是属于C++。此规则的例外是在显式C++方法调用返回QObject时:在这种情况下,除非通过调用QQmlEngine::setObjectOwnership()且指定QQmlEngine::CppOwnership将对象所有权明确保留在C++,否则QML引擎假定对象所有权。

此外,QML引擎尊重Qt C++对象的正常QObject父级所有权语义,决不会删除具有父级的QObject实例。

基本Qt数据类型

默认情况下,QML识别以下Qt数据类型,当从C++传递到QML或反向传递时,它们会自动转换为对应的QML值类型

注意:Qt GUI模块提供的类,例如QColorQFontQQuaternionQMatrix4x4,只有在包含Qt Quick模块时才在QML中可用。

为了方便,这些类型中的许多可以在QML中以字符串值或通过由QtQml::Qt对象提供的相关方法来指定。例如,Image::sourceSize属性的类型为size(自动转换为QSize类型),可以指定为"宽度x高度"的格式字符串,也可以使用Qt.size()函数

Item {
    Image { sourceSize: "100x200" }
    Image { sourceSize: Qt.size(100, 200) }
}

有关更多信息,请参阅QML值类型下每个单独类型的文档。

QObject派生类型

任何QObject派生类都可以用作QML和C++之间数据交换的类型,前提是这个类已在QML类型系统中注册。

引擎允许注册可实例化和不可实例化的类型。一旦一个类被注册为QML类型,它就可以用作在QML和C++之间交换数据的数据类型。有关类型注册的更多详细信息,请参阅将C++类型注册到QML类型系统

Qt和JavaScript类型之间的转换

在QML和C++之间传输数据时,QML引擎内置了对将许多Qt类型转换为相关JavaScript类型以及逆转换的支持。这使得使用这些类型并在C++或JavaScript中接收它们成为可能,而无需实现提供对数据值及其属性的访问的自定义类型。

(注意,QML中的JavaScript环境修改了原生JavaScript对象原型,包括String、Date和Number,以提供额外功能。有关详细信息,请参阅JavaScript宿主环境。)

将QVariantList和QVariantMap转换为JavaScript数组和对象

QML引擎提供了在QVariantList和JavaScript数组之间以及QVariantMap和JavaScript对象之间进行自动类型转换的功能。

例如,下面定义的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++类型使用QVariantListQVariantMap类型作为属性类型或方法参数,则该值可以作为JavaScript数组或对象在QML中创建,并在传递给C++时自动转换为QVariantListQVariantMap

请注意,C++类型的QVariantListQVariantMap属性以值的形式存储,并且不能通过QML代码原地修改。您只能替换整个映射或列表,而不能操作其内容。如果属性l是一个QVariantList,下面的代码将不起作用。

MyListExposingItem {
   l: [1, 2, 3]
   Component.onCompleted: l[0] = 10
}

下面的代码是有效的

MyListExposingItem {
   l: [1, 2, 3]
   Component.onCompleted: l = [10, 2, 3]
}

QDateTime转JavaScript Date

QML引擎在QDateTime值和JavaScript Date对象之间提供自动类型转换。

例如,以下QML中定义的函数期望一个JavaScript Date对象,并且也返回一个包含当前日期和时间的新的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++
QQuickView view(QUrl::fromLocalFile("MyItem.qml"));

QDateTime dateTime = QDateTime::currentDateTime();
QDateTime retValue;

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中以JavaScript Date对象的形式创建,并且当它传递到C++时自动转换为QDateTime值。

注意:注意月份编号的差异:JavaScript将1月编号为0到11(对于12月),与Qt中将1月编号为1到12(对于12月)的编号方式相差一个月。

注意:当使用JavaScript中的字符串作为Date对象的值时,请注意,没有时间字段(因此是一个简单的日期)的字符串解释为相关天的UTC起点,与new Date(y, m, d)相比,后者使用的是本地区时间的起点。JavaScript中构建Date对象的大部分其他方法都会产生本地时间,除非使用带有UTC名称的方法。如果您的程序在UTC后面的区域(大致在子午线以西)中运行,则使用只有日期的字符串将导致Date对象,其getDate()比字符串中的日期号少一个;它通常对getHours()有一个很大的值。这些方法的UTC变体(getUTCDate()getUTCHours())将给出您期望的结果。也请参阅下一节。

QDate和JavaScript Date

QML引擎通过将日期表示为该天的UTC起点来自动在QDate和JavaScript Date类型之间转换。通过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 Date

QML引擎提供从QTime值到JavaScript Date对象的自动类型转换。由于QTime值不包含日期组件,为了转换只创建一个。因此,您不应依赖于结果Date对象的日期组件。

在底层,将JavaScript中的Date对象转换为QTime,是通过将它们转换为QDateTime对象(使用本地时间)并调用其time()方法来完成的。

序列类型到JavaScript数组的转换

有关序列类型的概述,请参见QML序列类型。QtQml模块包含一些你可能希望使用的序列类型。

您也可以通过使用QJSEngine::newArray来构造QJSValue来创建类似列表的数据结构。这种JavaScript数组在传递于QML和C++之间时不需要任何转换。请参见QJSValue#使用数组,了解如何从C++操作JavaScript数组。

QByteArray到JavaScript ArrayBuffer

QML引擎提供在QByteArray值和JavaScript ArrayBuffer对象之间的自动类型转换。

值类型

Qt中的一些值类型,如QPoint,在JavaScript中以具有相同属性和函数的对象形式表示。同样,也可以使用自定义的C++值类型。为了使自定义值类型可用QML引擎,需要对类声明添加Q_GADGET注释。应使用Q_PROPERTY声明预计在JavaScript表示中可见的属性。类似地,函数需要标记为Q_INVOKABLE。这与基于QObject的C++ API相同。以下是一个示例Actor类,它被注释为工具和具有属性

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 中将枚举类型用作 标志 类型,请参阅 Q_FLAG()。

注意:枚举值名称需要以大写字母开头,才能从 QML 中访问。

...
enum class Status {
          Ready,
          Loading,
          Error
}
Q_ENUM(Status)
...

枚举类在 QML 中注册为作用域和未作用域属性。Ready 值将在 Message.Status.ReadyMessage.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_ELEMENTQML_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 命名空间 中声明的值之一,C++ 信号的枚举类型参数和方法就可以从 QML 中使用。

此外,如果C++信号具有枚举参数,且应使用connect()函数将其连接到QML函数,则必须使用qRegisterMetaType()注册枚举类型。

对于QML信号,枚举值可以作为信号参数使用int类型传递。

Message {
    signal someOtherSignal(int statusValue)

    Component.onCompleted: {
        someOtherSignal(Message.Loading)
    }
}

© 2024 Qt公司。本文件所包含的文档贡献归属于各自的版权所有者。所提供的文档受GNU自由文档许可证第1.3版许可,由自由软件基金会出版。Qt及其相关标志是芬兰及其它国家和地区Qt公司的商标。所有其他商标均为其各自所有者的财产。