动态连接到动态创建的对象的信号

时间:2017-03-30 15:14:39

标签: qt qml qtquick2

标题可能有点令人困惑,但我的意图非常简单:

我有一个[Repeater/Instantiator],可以创建任意委托的多个实例。我想对委托实例的所有属性更改(只有第一级,因此没有属性属性),调用函数

function update(index, propertyName)

这似乎很容易,但我失败了。这是我的代码

  

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
                }
            }
        }
    }
}

它成功地连接了某些东西,但我无法正确锁定key。无论我改变哪个属性,我总是得到信息,我在我的例子中更改了属性p3

如何锁定密钥,以便在使用相应名称更改属性时获得propertyName

3 个答案:

答案 0 :(得分:1)

你想要做的是内省对象&#39;属性在运行时更改。因此,多亏了Qt强大的元系统,使用QObject中的元对象更为内在。

主要思想是为QML使用自定义C ++扩展,它可以分析和收集QML对象中的所有属性,并将这些属性的notifySignals连接到我们的自定义插槽(如果有的话)。下面是一些代码片段,完整的演示可以在我的Github repo中找到:https://github.com/cjmdaixi/PropertySpy

  1. 定义PropertySpy

    class PropertySpy : public QObject
    {
        Q_OBJECT
    public:
        explicit PropertySpy(QObject *parent = 0);
    
        Q_INVOKABLE void spy(QObject * object);
    public slots:
        void onPropertyChanged();
    };
    
  2. 执行间谍:

    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. onPropertyChanged的实现。在演示中,我们只需打印属性及其值。实际上你可以做任何你想做的事:

    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. 将PropertySpy注册到main.cpp中的qml引擎

    qmlRegisterType<PropertySpy> ("PropertySpy", 1, 0, "PropertySpy");
    
  5. 在qml中使用PropertySpy,通常在您感兴趣的一些qml对象的Component.onCompleted中。例如,以下代码显示了如何反省MouseArea属性的更改:

    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. 然后,您可以在更改任何属性时收到通知,如下所示: enter image description here

答案 1 :(得分:0)

一种可能的解决方案是,创建一个具有该功能的对象,然后只分配此功能

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!*/}
    }
}

但也许有更好的解决方案等待披露?

答案 2 :(得分:0)

“键”始终为p3的原因是,所有创建的函数都引用相同的“键” 变量(它们是“键”的闭包),因此,当调用其中的任何一个时,他们在“ key”(OP原始代码中的p3)中找到了最近存储的值。

代码看起来就像它每次在循环中创建一个单独的本地 key var一样,但是在Javascript中不是这样,因为在JS中,所有var声明都是“悬挂”在其包含范围的顶部(这就是为什么您可以在声明var之前引用它的原因-声明是“为您”移动的);不幸的是,在Javascript中,除了 function 范围外,没有其他本地范围。在这种情况下,“ var key”声明被提升到onItemHandled块的顶部(qml编译为anon函数)。

可能的解决方案:在另一个函数中创建处理程序函数,以便处理程序引用的“键”是包装器函数的参数,包装器函数是 单独创建的每次通话:

item[key + 'Changed'].connect(
     (function(k,i) {
             return function() { console.log('Property',k,'of',i,'changed') }
                    })(key,index)
);