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

When data values are exchanged between QML and C++, they are converted by the QML engine to have the correct data types as appropriate for use in QML or C++. This requires the exchanged data to be of a type that is recognizable by the engine.

The QML engine provides built-in support for a large number of Qt C++ data types. Additionally, custom C++ types may be registered with the QML type system to make them available to the engine.

For more information about C++ and the different QML integration methods, see the C++ 和 QML 集成概述 页面。

This page discusses the data types supported by the QML engine and how they are converted between QML and C++.

数据所有权

When data is transferred from C++ to QML, the ownership of the data always remains with C++. The exception to this rule is when a QObject is returned from an explicit C++ method call: in this case, the QML engine assumes ownership of the object, unless the ownership of the object has explicitly been set to remain with C++ by invoking QQmlEngine::setObjectOwnership () with QQmlEngine::CppOwnership specified.

Additionally, the QML engine respects the normal QObject parent ownership semantics of Qt C++ objects, and will never delete a QObject instance which has a parent.

基本 Qt 数据类型

By default, QML recognizes the following Qt data types, which are automatically converted to a corresponding QML value type when passed from C++ to QML and vice-versa:

Qt 类型 QML Value Type
bool bool
unsigned int, int 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
Enums declared with Q_ENUM () enumeration

注意: Classes provided by the Qt GUI module, such as QColor , QFont , QQuaternion and QMatrix4x4 , are only available from QML when the Qt Quick module is included.

As a convenience, many of these types can be specified in QML by string values, or by a related method provided by the QtQml::Qt object. For example, the Image::sourceSize property is of type size (which automatically translates to the QSize type) and can be specified by a string value formatted as "width x height", or by the Qt. size() 函数:

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

See documentation for each individual type under QML 值类型 了解更多信息。

QObject-derived Types

任何 QObject -derived class may be used as a type for the exchange of data between QML and C++, providing the class has been registered with the QML type system.

The engine allows the registration of both instantiable and non-instantiable types. Once a class is registered as a QML type, it can be used as a data type for exchanging data between QML and C++. See Registering C++ types with the QML type system for further details on type registration.

Conversion Between Qt and JavaScript Types

The QML engine has built-in support for converting a number of Qt types to related JavaScript types, and vice-versa, when transferring data between QML and C++. This makes it possible to use these types and receive them in C++ or JavaScript without needing to implement custom types that provide access to the data values and their attributes.

(Note that the JavaScript environment in QML modifies native JavaScript object prototypes, including those of 字符串 , Date and Number , to provide additional features. See the JavaScript 主机环境 for further details.)

QVariantList and QVariantMap to JavaScript Array and Object

The QML engine provides automatic type conversion between QVariantList and JavaScript arrays, and between QVariantMap and JavaScript objects.

For example, the function defined in QML below expects two arguments, an array and an object, and prints their contents using the standard JavaScript syntax for array and object item access. The C++ code below calls this function, passing a QVariantList QVariantMap , which are automatically converted to JavaScript array and object values, repectively:

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)));
								

This produces output like:

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)
					

Similarly, if a C++ type uses a QVariantList or QVariantMap type for a property type or method parameter, the value can be created as a JavaScript array or object in QML, and is automatically converted to a QVariantList or QVariantMap when it is passed to C++.

Mind that QVariantList and QVariantMap properties of C++ types are stored as values and cannot be changed in place by QML code. You can only replace the whole map or list, but not manipulate its contents. The following code does not work if the property l QVariantList :

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

The following code does work:

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

QDateTime to JavaScript Date

The QML engine provides automatic type conversion between QDateTime values and JavaScript Date 对象。

For example, the function defined in QML below expects a JavaScript Date object, and also returns a new Date object with the current date and time. The C++ code below calls this function, passing a QDateTime value that is automatically converted by the engine into a Date object when it is passed to the readDate() function. In turn, the readDate() function returns a Date object that is automatically converted into a QDateTime value when it is received in C++:

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;
								

Similarly, if a C++ type uses a QDateTime for a property type or method parameter, the value can be created as a JavaScript Date object in QML, and is automatically converted to a QDateTime value when it is passed to C++.

注意: Watch out for the difference in month numbering: JavaScript numbers January as 0 through 11 for December, off by one from Qt's numbering of January as 1 through 12 for December.

注意: When using a string in JavaScript as the value of a Date object, note that a string with no time fields (so a simple date) is interpreted as the UTC start of the relevant day, in contrast to new Date(y, m, d) which uses the local time start of the day. Most other ways of constructing a Date object in JavaScript produce a local time, unless methods with UTC in their names are used. If your program is run in a zone behind UTC (nominally west of The Prime Meridian), use of a date-only string will lead to a Date object whose getDate() is one less than the day-number in your string; it will typically have a large value for getHours() . The UTC variants of these methods, getUTCDate() and getUTCHours() , will give the results you expect for such a Date objects. See also the next section.

QDate and JavaScript Date

The QML engine converts automatically between QDate and the JavaScript Date type by representing the date by the UTC start of its day. A date is mapped back to QDate 凭借 QDateTime , selecting its date () method, using the local time form of the date unless the UTC form of it coincides with the start of the next day, in which case the UTC form is used.

This slighly eccentric arrangement is a work-around for the fact that JavaScript's construction of a Date object from a date-only string uses the UTC start of the day, but new Date(y, m, d) uses the local time start of the indicated date, as discussed in a note at the end of the previous section.

As a result, where a QDate property or parameter is exposed to QML, care should be taken in reading its value: the Date.getUTCFullYear() , Date.getUTCMonth() and Date.getUTCDate() methods are more likely to deliver the results users expect than the corresponding methods without UTC in their names.

It is thus commonly more robust to use a QDateTime property. This makes it possible to take control, on the QDateTime side, of whether the date (and time) is specified in terms of UTC or local time; as long as the JavaScript code is written to work with the same standard, it should be possible to avoid trouble.

QTime and JavaScript Date

The QML engine provides automatic type conversion from QTime values to JavaScript Date objects. As QTime values do not contain a date component, one is created for the conversion only. Thus, you should not rely on the date component of the resulting Date object.

Under the hood, conversion from a JavaScript Date 对象到 QTime is done by converting to a QDateTime object (using local time) and calling its time () 方法。

Sequence Type to JavaScript Array

QML 序列类型 for a general description of sequence types. The QtQml module contains a few sequence types you may want to use.

You can also create a list-like data structure by constructing a QJSValue 使用 QJSEngine::newArray (). Such a JavaScript array does not need any conversion when passing it between QML and C++. See QJSValue#Working With Arrays for details on how to manipulate JavaScript arrays from C++.

QByteArray to JavaScript ArrayBuffer

The QML engine provides automatic type conversion between QByteArray values and JavaScript ArrayBuffer 对象。

值类型

Some value types in Qt such as QPoint are represented in JavaScript as objects that have the same properties and functions like in the C++ API. The same representation is possible with custom C++ value types. To enable a custom value type with the QML engine, the class declaration needs to be annotated with Q_GADGET . Properties that are intended to be visible in the JavaScript representation need to be declared with Q_PROPERTY . Similarly functions need to be marked with Q_INVOKABLE . This is the same with QObject based C++ APIs. For example, the Actor class below is annotated as gadget and has properties:

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)
					

The usual pattern is to use a gadget class as the type of a property, or to emit a gadget as a signal argument. In such cases, the gadget instance is passed by value between C++ and QML (because it's a value type). If QML code changes a property of a gadget property, the entire gadget is re-created and passed back to the C++ property setter. In Qt 5, gadget types cannot be instantiated by direct declaration in QML. In contrast, a QObject instance can be declared; and QObject instances are always passed by pointer from C++ to QML.

枚举类型

To use a custom enumeration as a data type, its class must be registered and the enumeration must also be declared with Q_ENUM () to register it with Qt's meta object system. For example, the Message class below has a 状态 枚举:

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 class has been registered with the QML type system, its 状态 enum can be used from QML:

Message {
     onStatusChanged: {
         if (status == Message.Ready)
             console.log("Message is loaded!")
     }
 }
					

To use an enum as a flags type in QML, see Q_FLAG ().

注意: The names of enum values must begin with a capital letter in order to be accessible from QML.

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

Enum classes are registered in QML as scoped and unscoped properties. The Ready value will be registered at Message.Status.Ready and Message.Ready .

When using enum classes, there can be multiple enums using the same identifiers. The unscoped registration will be overwriten by the last registered enum. For classes that contain such name conficts it is possible to disable the unscoped registration by annotating your class with a special Q_CLASSINFO macro. Use the name RegisterEnumClassesUnscoped 采用值 false to prevent scoped enums from being merged into the same name space.

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
        };
    };
					

Enums from related types are usually registered in the scope of the relating type. For example any enum from a different type used in a Q_PROPERTY declaration causes all enums from that type to be made available in QML. This is usually more of a liability than a feature. In order to prevent it from happening, annotate your class with a special Q_CLASSINFO macro. Use the name RegisterEnumsFromRelatedTypes 采用值 false to prevent enums from related types from being registered in this type.

You should explicitly register the enclosing types of any enums you want to use in QML, using QML_ELEMENT or QML_NAMED_ELEMENT , rather than rely on their enums to be injected into other types.

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; }
};
					

The important difference is the scope for the enums in QML. If an enum from a related class is automatically registered, the scope is the type it is imported into. In the above case, without the extra Q_CLASSINFO , you would use Message.A , for example. If C++ type holding the enums is explicitly registered, and the registration of enums from related types is suppressed, the QML type for the C++ type holding the enums is the scope for all of its enums. You would use OtherType.A 而不是 Message.A in QML.

Mind that you can use QML_FOREIGN to register a type you cannot modify. You can also use QML_FOREIGN_NAMESPACE to register the enumerators of a C++ type into a QML namespace of any upper-case name, even if the same C++ type is also registered as a QML value type.

枚举类型作为信号和方法参数

C++ signals and methods with enumeration-type parameters can be used from QML provided that the enumeration and the signal or method are both declared within the same class, or that the enumeration value is one of those declared in the Qt 名称空间 .

Additionally, if a C++ signal with an enum parameter should be connectable to a QML function using the connect() function, the enum type must be registered using qRegisterMetaType ().

For QML signals, enum values may be passed as signal parameters using the int 类型:

Message {
    signal someOtherSignal(int statusValue)
    Component.onCompleted: {
        someOtherSignal(Message.Loading)
    }
}