在 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:
- 作為給定序列類型的 Q_PROPERTY,通過索引通路序列中的任何值将導緻從 QObject 的屬性讀取序列資料,然後再讀取。同樣,修改序列中的任何值都會導緻讀取序列資料,然後執行修改并将修改後的序列寫回到QObject的屬性中
- 作為 Q_INVOKABLE 方法的傳回類型,通路和修改代價會低得多,因為不會發生 QObject 屬性讀取或寫入。
在 Q_PROPERTY 和從 Q_INVOKABLE 傳回的情況下,都會複制 std::vector 的元素。這種複制可能是一個昂貴的操作。
還可以通過使用 QJSEngine::newArray() 構造 QJSValue 來建立類似清單的資料結構。這樣的 JavaScript 數組在 QML 和 C++ 之間傳遞時不需要任何轉換。此類序列數組類型與預設 JavaScript 數組類型的語義之間存在一些細微差别,這是由于在實作中使用 C++ 存儲類型造成的。特别是:
- 從 Array 中删除元素将導緻替換該元素的預設構造值,而不是 Undefined 值。
- 将 Array 的 length 屬性設定為大于其目前值的值将導緻 Array 被填充到指定的長度,并使用預設構造的元素而不是 Undefined 的元素。
- 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)
}
}