Qt Property
Table of Contents
1. Qt Property
1.1. Q_PROPERTY
class MessageBox : public QObject { Q_OBJECT Q_PROPERTY(int height MEMBER mHeight NOTIFY heightChanged) Q_PROPERTY(int width READ width WRITE setWidth) public: explicit MessageBox(QObject *parent = 0); signals: void heightChanged(); void changedTwoTimes(); void oopsWidthChanged(); public slots: void onTextChanged(QString); private: int mHeight; int mWidth; int width(); void setWidth(int w); };
对应的 moc_message_box 的代码:
void MessageBox::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { // ... } else if (_c == QMetaObject::IndexOfMethod) { // ... } else if (_c == QMetaObject::ReadProperty) { auto *_t = static_cast<MessageBox *>(_o); Q_UNUSED(_t) void *_v = _a[0]; switch (_id) { // height 使用了 MEMBER, 所以读写直接对应 mHeight case 0: *reinterpret_cast< int*>(_v) = _t->mHeight; break; // width 使用了 READ, 所以读操作对应 width() case 1: *reinterpret_cast< int*>(_v) = _t->width(); break; default: break; } } else if (_c == QMetaObject::WriteProperty) { auto *_t = static_cast<MessageBox *>(_o); Q_UNUSED(_t) void *_v = _a[0]; switch (_id) { case 0: // height 使用了 MEMBER, 所以读写直接对应 mHeight, 但 // MEMBER 会在修改 height 后自动 emit NOTIFY 对应的 signal if (_t->mHeight != *reinterpret_cast< int*>(_v)) { _t->mHeight = *reinterpret_cast< int*>(_v); Q_EMIT _t->heightChanged(); } break; // width 写操作对应 setWidth(), 但并不自动 emit NOTIFY 对应的 signal // 需要 setWidth() 中自己去 emit case 1: _t->setWidth(*reinterpret_cast< int*>(_v)); break; default: break; } } else if (_c == QMetaObject::ResetProperty) { } }
1.1.1. READ
moc 负责把 QObject.property("width") 转换为 width() 函数
1.1.2. WRITE
moc 负责把 QObject.setProperty("width",1 ) 转换为 setWidth(1) 函数
1.1.3. MEMBER
moc 负责把 QObject.property("height") 转换为对 mHeight 的读操作, 把 QObject.setProperty("height",1) 转换为对 mHeight 的写操作
1.1.4. NOTIFY
有了 NOTIFY 声明, MEMBER 在 write 后会自动 emit 相应的 signal. 同时 QML 中可以 connect 相应的 onXXXChanged signal.
`oopsWidthChanged` 但并没有通过 NOTIFY 指定, 所以 onWidthChanged 在 QML 中是不可用的. 但 `onOopsWidthChanged` 是可以用的 (参考下面的命名约定)
1.2. QML 对 signal 的命名约定
Window { MessageBox { id: message height: 1 width: 1 /* QML connect signal 时有固定的命名约定: * xxx property 对应的 signal 固定为 onXxxChanged * onHeightChanged 对应于 MessageBox 的 heightChanged signal, * 但它的名字与 `heightChanged` 并没有关系 */ onHeightChanged: console.log("height changed "+height) } Connections { target: message /* QML connect signal 时还有另一种固定的命名约定: * 对于某个 signal xxx, QML connnect 时使用 onXxx * 例如 MessageBox 有一个 signal 为 changedTwoTimes, * QML 中需要写成 onChangedTwoTimes (注意大小写的变化) **/ onChangedTwoTimes: console.log("height changed 2 times") } Connections { target: message /* 对于 property 对应的 signal, 使用 on[Property]Changed 和 on[Signal] 都可以 */ onOopsWidthChanged: console.log("oops width changed") } /* ... */ function update_with_js () { b.text=a.text /* QML 可以对 property 进行读写, * 最终会由 moc_message_box 中的 ReadProperty, WriteProperty 处理 */ message.height += 1 message.width += 1 if (message.height == 2) { message.changedTwoTimes() } } }
1.3. QML property binding
QML 可以通过 property binding 保持不同的 property 的值同时变化.
binding 有三种写法:
1.3.1. property 直接通过 ":" 赋值
Item { Item { id:a x:10 y:10 } Item { id:b /* 可以 binding 到 property 或一段 js 代码 */ x:a.x y:if (a.y>10) return a.y else return 10 } }
1.3.2. Qt.binding
Item { TextInput { id:a text:"a" } TextInput { id:b text:"b" MouseArea { Anchros.fill:parent onClicked: { // 在 slot 中的 javascript 代码中, 通过 = 赋值, 需要返回 // Qt.binding, 否则只会赋一个静态值而非 binding parent.text=Qt.binding(function() {return a.text}) } } } }
1.3.3. Binding
TextEdit { id: d } Binding { target: d property: "text" value: { if (c.text.length > 2) {return c.text} else {return c.text+c.text} } }
1.4. inheretence
- 子类会 "继承" 父类的 property
- 通过把 READ, WRITE 函数设为虚函数可以在子类中 override 父类的 property
property 的 "继承" 是通过 meta object 实现的:
int ComplexMessageBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = MessageBox::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty || _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) { qt_static_metacall(this, _c, _id, _a); _id -= 1; ... } } void ComplexMessageBox::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::ReadProperty) { auto *_t = static_cast<ComplexMessageBox *>(_o); Q_UNUSED(_t) void *_v = _a[0]; switch (_id) { case 0: *reinterpret_cast< int*>(_v) = _t->mOpacity; break; default: break; } } else if (_c == QMetaObject::WriteProperty) { auto *_t = static_cast<ComplexMessageBox *>(_o); Q_UNUSED(_t) void *_v = _a[0]; switch (_id) { case 0: if (_t->mOpacity != *reinterpret_cast< int*>(_v)) { _t->mOpacity = *reinterpret_cast< int*>(_v); } break; default: break; } } else if (_c == QMetaObject::ResetProperty) { } }
1.5. why property
metaObject 提供的 introspection 功能是 {QML, UI1} 与 c++ 的桥梁:
QML, UI 使用的符号通过 introspection 与 c++ 对象发生关联, 具体的, 包括 property 和 signal. 所以 QML, UI 中可以直接操作 property 和 signal, 但对 c++ 对象的普通成员和函数是没有办法访问的.
1.6. Q&A
1.6.1. qml property binding 如果有递归或者是循环的绑定会怎么样
import QtQuick 2 Item { Text { y:10 id:a text:b.text+"a" } Text { y:40 id:b text:a.text+"b" } MouseArea { anchors.fill:parent onClicked:b.text="click" } } // $> qmlscene test.qml // file:///home/ANT.AMAZON.COM/waysun/download/test.qml:10:5: QML Text: Binding loop detected for property "text"
1.6.2. 为什么需要 connections 而不是直接在 element 里写 onXXX?
有时 element 是写好的, 从别的地方 import 过来或通过 loader 动态加载, 无法直接给这些 element 添加 onXXX, 所以需要用额外的 connections 来声明
Footnotes:
运行时通过 QUiLoader 加载的 ui, 而非 uic 的方式