天天看點

QML和C++之間的資料類型轉換

在 QML 和 C++ 之間交換資料值時,QML 引擎會将它們轉換為适合在 QML 或 C++ 中使用的正确資料類型。 這要求交換的資料屬于引擎可識别的類型。

QML 引擎為大量 Qt C++ 資料類型提供内置支援。 此外,自定義 C++ 類型可以在 QML 類型系統中注冊,以使它們可用于引擎。

一、資料所有權

當資料從 C++ 傳輸到 QML 時,資料的所有權始終屬于 C++。唯一的例外是當從顯式 C++ 方法調用傳回 QObject 時:在這種情況下,QML 引擎假定對象的所有權,除非調用了QQmlEngine::setObjectOwnership(QQmlEngine::CppOwnership) 将對象的所有權顯式設定為保留在 C++ 中。

此外,QML 引擎尊重 Qt C++ 對象的正常 QObject 父所有權語義,并且永遠不會删除具有父對象的 QObject 執行個體。

二、基本 Qt 資料類型

預設情況下,QML 識别以下 Qt 資料類型,當從 C++ 傳遞到 QML 時,它們會自動轉換為相應的 QML 基本類型,反之亦然:

  • 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
  • 用 Q_ENUM() 或 Q_ENUMS() 聲明的枚舉 — enumeration

Qt GUI 子產品提供的類,例如 QColor、QFont、QQuaternion 和 QMatrix4x4,僅當包含 Qt Quick 子產品時才可從 QML 獲得。

為友善起見,許多這些類型可以在 QML 中通過字元串值或 QtQml::Qt 對象提供的相關方法指定。 例如,Image::sourceSize 屬性是 size 類型(它會自動轉換為 QSize 類型),并且可以由格式化為“widthxheight”的字元串值或 Qt.size() 函數指定:

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

三、QObject 派生類型

任何 QObject 派生類都可以用作 QML 和 C++ 之間資料交換的類型,前提是該類已在 QML 類型系統中注冊。

QML 引擎允許注冊可執行個體化和不可執行個體化的類型。一旦一個類被注冊為 QML 類型,它就可以用作在 QML 和 C++ 之間交換資料的資料類型。

四、Qt 和 JavaScript 類型之間的轉換

在 QML 和 C++ 之間傳輸資料時,QML 引擎内置支援将許多 Qt 類型轉換為相關的 JavaScript 類型,反之亦然。

4.1、QVariantList 和 QVariantMap 到 JavaScript 數組和對象

QML 引擎提供 QVariantList (QList<QVariant>)和 JavaScript 數組之間以及 QVariantMap(QMap<QString, QVariant>) 和 JavaScript 對象之間的自動類型轉換。

// 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++
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 代碼更改。隻能替換整個Map或List,而不能操作其内容。例,以下代碼不起作用: 

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

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

4.2、QDateTime 到 JavaScript 日期

QML 引擎提供 QDateTime 值和 JavaScript Date 對象之間的自動類型轉換。

// MyItem.qml
Item 
{
    function readDate(dt) 
    {
        console.log("The given date is:", dt.toUTCString());
        return new Date();
    }
}
           
// 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() << "readDate()傳回值:" << retValue;
           

上面的 C++ 代碼調用 QML 函數時傳遞一個 QDateTime 值,當它傳遞給 readDate() 函數時,引擎會自動将其轉換為 Date 對象。 反過來, readDate() 函數傳回一個 Date 對象,該對象在 C++ 中接收時會自動轉換為 QDateTime 值。

與之類似的還有 QDate 和 JavaScript 日期、QTime 和 JavaScript 日期。

4.3、序列類型到 JavaScript 數組

QML 支援某些 C++ 序列類型,使其表現得像 JavaScript 數組類型。

特别是,QML 目前支援:

  • QList<int>
  • QList<qreal>
  • QList<bool>
  • QList<QString> 和 QStringList
  • QVector<QString>
  • std::vector<QString>
  • QList<QUrl>
  • QVector<QUrl>
  • std::vector<QUrl>
  • QVector<int>
  • QVector<qreal>
  • QVector<bool>
  • std::vector<int>
  • std::vector<qreal>
  • std::vector<bool>

以及所有注冊的 QList、QVector、QQueue、QStack、QSet、std::list、std::vector 包含标記為 Q_DECLARE_METATYPE 的類型。

這些序列類型是根據底層 C++ 序列直接實作的。有兩種方式可以将此類序列暴露給 QML:

  1. 作為給定序列類型的 Q_PROPERTY,通過索引通路序列中的任何值将導緻從 QObject 的屬性讀取序列資料,然後再讀取。同樣,修改序列中的任何值都會導緻讀取序列資料,然後執行修改并将修改後的序列寫回到QObject的屬性中
  2. 作為 Q_INVOKABLE 方法的傳回類型,通路和修改代價會低得多,因為不會發生 QObject 屬性讀取或寫入。

在 Q_PROPERTY 和從 Q_INVOKABLE 傳回的情況下,都會複制 std::vector 的元素。這種複制可能是一個昂貴的操作。

還可以通過使用 QJSEngine::newArray() 構造 QJSValue 來建立類似清單的資料結構。這樣的 JavaScript 數組在 QML 和 C++ 之間傳遞時不需要任何轉換。此類序列數組類型與預設 JavaScript 數組類型的語義之間存在一些細微差别,這是由于在實作中使用 C++ 存儲類型造成的。特别是:

  1. 從 Array 中删除元素将導緻替換該元素的預設構造值,而不是 Undefined 值。
  2. 将 Array 的 length 屬性設定為大于其目前值的值将導緻 Array 被填充到指定的長度,并使用預設構造的元素而不是 Undefined 的元素。
  3. Qt 容器類支援有符号(而不是無符号)整數索引;是以,嘗試通路任何大于 INT_MAX 的索引都會失敗。

每個序列類型的預設構造值如下:

  • QList<int> - 0
  • QList<qreal> - 0.0
  • QList<bool> - false
  • QList<QString> 和 QStringList - 空 QString
  • QVector<QString> - 空 QString
  • std::vector<QString> - 空 QString
  • QList<QUrl> - 空 QUrl
  • QVector<QUrl> - 空 QUrl
  • std::vector<QUrl> - 空 QUrl
  • QVector<int> - 0
  • QVector<qreal> - 0.0
  • QVector<bool> - false
  • std::vector<int> - 0
  • std::vector<qreal> - 0.0
  • std::vector<bool> - false

如果希望從序列中删除元素而不是簡單地用預設構造值替換它們,請不要使用索引删除運算符(“delete sequence[i]”),而是使用 splice  函數(“sequence.splice(startIndex, deleteCount )”)。

4.4、QByteArray 到 JavaScript ArrayBuffer

QML 引擎提供 QByteArray 值和 JavaScript ArrayBuffer 對象之間的自動類型轉換。

4.5、值類型

Qt 中的某些值類型(例如 QPoint)在 JavaScript 中表示為具有與 C++ API 中相同的屬性和功能的對象。

自定義 C++ 值類型也可以進行相同的表示。要使用 QML 引擎啟用自定義值類型,類聲明需要使用 Q_GADGET  進行注釋。 要在 JavaScript 可見的屬性需要使用 Q_PROPERTY 進行聲明。函數需要用 Q_INVOKABLE 标記。這與基于 QObject 的 C++ API 相同。示例:

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)
           

以上面定義的 Actor 為例,一般使用 Actor 類作為屬性的類型,或者作為信号的參數。在這種情況下,Actor 執行個體在 C++ 和 QML 之間按值傳遞。如果 QML 代碼更改了 Actor 的屬性,則會重新建立整個 Actor 并将其傳遞回 C++ 屬性設定器。

五、枚舉類型

要将自定義枚舉用作資料類型,必須注冊其類,并且還必須使用 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 類型系統,則可以從 QML 使用其 Status 枚舉:

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

要将枚舉用作 QML 中的标志類型,需使用 Q_FLAG()。

注意:枚舉值的名稱必須以大寫字母開頭才能從 QML 通路。

5.1、枚舉類

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

枚舉類在 QML 中注冊為作用域屬性和無作用域屬性。

如 Ready 值将注冊為 Message.Status.Ready(作用域屬性) 和 Message.Ready(無作用域屬性) 。

5.2、名稱沖突

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

ScopedEnum 中的Value1将注冊為 Message.ScopedEnum.Value1和 Message.Value1

OtherValue 中的Value1将注冊為 Message.OtherValue .Value1和 Message.Value1

這樣就會起沖突。

可以通過使用 Q_CLASSINFO  宏注釋類來禁用無作用域注冊。設定RegisterEnumClassesUnscoped 為false以防止作用域枚舉合并到同一名稱空間中。

5.3、枚舉類型作為信号和方法參數

如果枚舉和信号或方法都在同一個類中聲明,或者枚舉值是在 Qt 命名空間中聲明的值之一,則可以從 QML 使用帶有枚舉類型參數的 C++ 信号和方法。

此外,如果帶有枚舉參數的 C++ 信号可以使用 connect() 函數連接配接到 QML 函數,則必須使用 qRegisterMetaType() 注冊枚舉類型。

對于 QML 信号,枚舉值可以使用 int 類型作為信号參數傳遞:

Message 
{
    signal someOtherSignal(int statusValue)

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