Connect dynamically to signals of dynamically created objects

我的梦境 提交于 2019-12-12 01:48:32

问题


The title might be a bit confusing, but my intent is pretty simple:

I have a [Repeater/Instantiator] which creates multiple instances of a arbitrary delegate. I want to react to all changes of the properties (only first-level, so no properties of properties) of the instances of the delegate, calling a function

function update(index, propertyName)

This seems to be easy, but I fail. This is my code

TestObj.qml

Repeater {
    onItemAdded: {
        var keys = Object.keys(item)
        console.log(keys)
        for (var k = 0; k < keys.length; k++) {
            if (item[keys[k] + 'Changed'] !== undefined  && keys[k] !== 'objectName') {
                var key = keys[k]
                var str = "function() { console.log('Property, " + key + " of " + index + " changed') }"
                console.log(str)
                item[key + 'Changed'].connect(function() { console.log('Property', key, 'of', index, 'changed') })
            }
        }
    }
}

main.qml:

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQml 2.2
import QtQuick.Controls 1.4 as Ctrl
import '.'
import 'test.js' as Test

ApplicationWindow {
    id: root
    visible: true
    minimumWidth: 500
    minimumHeight: 500
    property var blub: []

    Column {
        spacing: 5
        TestObj {
            model: 5
            delegate: Row {
                spacing: 2
                property int p1: 0
                property int p2: 2
                property int p3: 4

                Button {
                    text: parent.p1
                    onClicked: parent.p1++
                }
                Button {
                    text: parent.p2
                    onClicked: parent.p2 *= 2
                }
                Button {
                    text: parent.p3
                    onClicked: parent.p3 *= parent.p3
                }
            }
        }
    }
}

It succesfully connects something, but I can't get the key properly locked. Whichever property I change, I always get the info, I had changed property p3 in my example.

How can I lock the key, so that I get propertyName when I change the the property with the corresponding name?


回答1:


What you want to do is to introspect objects' property changing at runtime. Therefore thanks to Qt's powerful meta-system, it is more intrinsic to do this using the metaobjects in the QObject.

The main idea is to use a custom C++ extension for QML which can analyze and gather all the properties in a QML object, and connect the notifySignals of these properties to our custom slots if any. Below are some code snippets, and the complete demo can be found in my Github repo: https://github.com/cjmdaixi/PropertySpy

  1. define PropertySpy

    class PropertySpy : public QObject
    {
        Q_OBJECT
    public:
        explicit PropertySpy(QObject *parent = 0);
    
        Q_INVOKABLE void spy(QObject * object);
    public slots:
        void onPropertyChanged();
    };
    
  2. the implementation of spy:

    void PropertySpy::spy(QObject *object)
    {
        auto slotIndex = metaObject()->indexOfSlot("onPropertyChanged()");
        if(slotIndex == -1){
            qDebug()<<"The index of onPropertyChanged is invalid!";
            return;
        }
        auto slotMethod = metaObject()->method(slotIndex);
        if(!slotMethod.isValid()){
            qDebug()<<"cannot find onPropertyChanged!";
            return;
        }
        for(auto i = 0; i != object->metaObject()->propertyCount(); ++i){
            auto prop = object->metaObject()->property(i);
            auto sig = prop.notifySignal();
            if(!sig.isValid()) continue;
            connect(object, sig, this, slotMethod);
        }
    }
    
  3. the implementation of onPropertyChanged. Here in the demo, we simply print the property and it's value. Actually you can do whatever you want:

    void PropertySpy::onPropertyChanged()
    {
        auto senderObj = sender();
        auto signalIndex = senderSignalIndex();
        for(auto i = 0; i != senderObj->metaObject()->propertyCount(); ++i){
            auto prop = senderObj->metaObject()->property(i);
            if(prop.notifySignalIndex() == signalIndex){
                qDebug()<<prop.name()<<prop.read(senderObj);
            }
        }
    }
    
  4. register PropertySpy to the qml engine in main.cpp

    qmlRegisterType<PropertySpy> ("PropertySpy", 1, 0, "PropertySpy");
    
  5. use PropertySpy in qml, usually in Component.onCompleted of some qml objects that you are interested. For example, the following codes show how to introspect the changes of MouseArea's properties:

    PropertySpy{
        id: propSpy
    }
    
    Rectangle{
        id: rect
        anchors.centerIn: parent
        color: "red"
        width: 100
        height: 50
        radius: 8
    
        MouseArea{
            anchors.fill: parent
            id: mouseArea
    
            Component.onCompleted: propSpy.spy(mouseArea)
        }
    }
    
  6. Then you can get notified when any property is changed like below:




回答2:


One possible solution is, to create an object that has the function, and then just assign this function

Repeater {
    onItemAdded: {
        var keys = Object.keys(item)
        for (var k = 0; k < keys.length; k++) {
            if (item[keys[k] + 'Changed'] !== undefined  && keys[k] !== 'objectName') {
                var key = keys[k]
                var f = __funproto.createObject(this, { i: index, n: key })
                item[key + 'Changed'].connect(f.fun)
            }
        }
    }
}

Component {
    id: __funproto
    QtObject {
        property int i
        property string n
        function fun() { /*do something with it!*/}
    }
}

But maybe there is a even better solution waiting to be disclosed?



来源:https://stackoverflow.com/questions/43121850/connect-dynamically-to-signals-of-dynamically-created-objects

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!