目的
本文简介QML与C++ QObject 对象的属性(Property)、方法(Method)与信号(Signal)互操作[1],并举例说明。
基于上一篇文章中介绍的QML对象与C++对象绑定后,QML可以更进一步的与C++对象的属性(Attribute)、方法(Method)与信号(Signal)进行互操作。
属性
如下的例子使用 Q_PROPERTY()宏定义 author属性(Property)的类型为(QString),以及auhor属性对应的 set(setAuthor)方法, get(author)方法以及notify信号槽(authorChanged)。
class Message : public QObject
{
Q_OBJECT
Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
public:
void setAuthor(const QString &a) {
if (a != m_author) {
m_author = a;
emit authorChanged();
}
}
QString author() const {
return m_author;
}
signals:
void authorChanged();
private:
QString m_author;
};
在QML中分别调用了author属性的get方法与set方法,与JS属性操作相同,非常方便。
在获取msg.author时,QML engine会自动调用C++中Method::author()方法。
在对msg.author赋值时,QML engine会自动调用C++中Method::setAuthor()方法。
// MyItem.qml
import QtQuick 2.0
Text {
width: 100; height: 100
text: msg.author // invokes Message::author() to get this value
Component.onCompleted: {
msg.author = "Jonah" // invokes Message::setAuthor()
}
}
QML与C++互操作的前提是必须要和C++的QObject 对象建立绑定关系,如下使用setContextProperty()建立MyItem.qml中的msg对象与C++的Message对象的绑定关系后,本例便可正常运行。
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQuickView view;
Message msg;
view.engine()->rootContext()->setContextProperty("msg", &msg);
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
return app.exec();
}
数据类型转换
QML与C++对象的属性相互访问时,会引用到QML与C++的自动数据类型转换[2]。举例来说,如QML中js的int类型会自动转换成C++中的int或者unsigned int类型。下图是常用的QML与C++的数据类型转换表:
List与Map
List与Map类型在QML与C++的数据类型转换中也经常使用。比如QML中ListViewMode就是一个List类型。通常来说,C++中的QVariantList类型会被自动转换成QML JavaScript中的Array类型,而QVariantMap会被自动转换成QML JavaScript中的Object类型,反之亦然。
List与Map在C++与QML中的相互转换是传值的,而非传址。这意味这对C++中QVariantList或QVariantMap中内容的修改,并不会影响QML中的对象内容。因此,如果需要将C++中List或Map的修改传递到QML中,需要将整个List或Map都传递给QML。
其他数据类型
QML与C++之间的数据类型转换还支持一些其他类型,如枚举类型(Enum),QDateTime类型等。更多内容可以参考引用1中的说明。
方法
QML与C++之间可以相互调用彼此的方法。如果是QML调用C++的类成员方法,则此方法需要使用Qt的slot或者使用Q_INVOKABLE修饰。如上文中就分别演示了这两种方法。
如果C++需要调用QML的方法,则需要使用C++ QObject::invokeMethod()方法,如下例中的C++代码主动调用了QML代码中的readValues()方法,c++传递的参数分别是QVariant和QVariantMap,调用到JS代码时会自动转换成JS中的array和object对象。
// 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)));
// 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])
}
}
}
信号
QML中可以接收到C++中的信号槽(signal)触发事件。如下面例子中C++定义的 newMessagePosted()事件。
class MessageBoard : public QObject
{
Q_OBJECT
public:
// ...
signals:
void newMessagePosted(const QString &subject);
};
下面的QML中只需要定义onNewMessagePosted,则C++触发了newMessagePosted()事件后,QML中的方法就会被自动调用。前提是需要将MessageBoard通过qmlRegisterType()等方法注册到QML engine中。
MessageBoard {
onNewMessagePosted: console.log("New message received:", subject)
}
生命周期
C++中的对象销毁了,QML能保证正常工作吗?
QML对C++对象的管理遵循如下几个原则:
- 谁分配谁管理。如果C++中new的对象,c++自己管理,反之亦然。
- QQmlEngine::setObjectOwnership() 与 QQmlEngine::CppOwnership()可以改变对象的Ownership。
- 如果C++中的QML有parent, 则QML不会主动销毁此对象。
引用
[1] https://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html#properties-with-object-list-types
[2] https://doc.qt.io/qt-5/qtqml-cppintegration-data.html