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

  1. 子类会 "继承" 父类的 property
  2. 通过把 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:

1

运行时通过 QUiLoader 加载的 ui, 而非 uic 的方式

Author: [email protected]
Date: 2019-12-30 Mon 00:00
Last updated: 2019-12-31 Tue 18:10

知识共享许可协议