Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Binding loop problem in an application with QML&C++



  • Hi, I am doing a project, basically what I want to do is to synchronize a property between 2 boxes in 2 QML files, I bind that property to a C++ Q_PROPERTY, so by binding the 2 boxes to the same C++ Q_PROPERTY, the synchronization can be achieved.
    Here is my code in Box A

                    Box_A {
                        id: box_A
                        // sth
     Binding { target:box_A; property: "theProperty"; value:model.CppModel.theProperty } 
                        onThePropertyChanged: {
                            model.CppModel.theProperty = theProperty
                        }
                    }
    

    In Box_B

                    Box_B {
                        id: box_B
                        // sth
     Binding { target:box_B; property: "theProperty"; value:model.CppModel.theProperty } 
                        onThePropertyChanged: {
                            model.CppModel.theProperty = theProperty
                        }
                    }
    

    The problem is that once I change theProperty in either box A or B, the qt creator complains that loop binding detected for value: model.CppModel.theProperty, is there a way of walking around this problem? Thank you very much



  • Does using Connections help?:

    import QtQuick 2.9
    import QtQuick.Window 2.2
    import QtQuick.Controls 2.5
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Sync Objects Testing")
    
        // can be C++ object in Connections source property
        // rectangle used as stand in
        Rectangle {
            id: source
    
            border.color: "red"
            border.width: 2
            width: 100
            height: 100
    
            property int theproperty: 0
    
            onThepropertyChanged: {
                console.log("source changed")
            }
    
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                text: parent.theproperty
            }
        }
        Rectangle {
            id: boxa
    
            anchors.top: source.bottom
            border.color: "green"
            border.width: 2
            width: 100
            height: 100
    
            property int theproperty: 0
    
            Connections {
                target: source
                onThepropertyChanged: {
                    boxa.theproperty = target.theproperty
                }
            }
    
            onThepropertyChanged: {
                console.log("boxa changed")
                source.theproperty = theproperty
            }
    
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                text: parent.theproperty
            }
        }
        Rectangle {
            id: boxb
    
            anchors.top: boxa.bottom
            border.color: "blue"
            border.width: 2
            width: 100
            height: 100
    
            property int theproperty: 0
    
            Connections {
                target: source
                onThepropertyChanged: {
                    boxb.theproperty = target.theproperty
                }
            }
    
            onThepropertyChanged: {
                console.log("boxb changed")
                source.theproperty = theproperty
            }
    
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                text: parent.theproperty
            }
        }
    
        Button {
            id: but_change
    
            anchors.top: boxb.bottom
            text: "change"
    
            onClicked: {
                var num = Math.round(Math.random() * 100)
                console.log(num)
                source.theproperty = num
            }
        }
        Button {
            id: but_boxa
    
            anchors.top: but_change.bottom
            text: "boxa change"
    
            onClicked: {
                var num = Math.round(Math.random() * 100)
                console.log(num)
                boxa.theproperty = num
            }
        }
        Button {
            id: but_boxb
    
            anchors.top: but_boxa.bottom
            text: "boxb change"
    
            onClicked: {
                var num = Math.round(Math.random() * 100)
                console.log(num)
                boxb.theproperty = num
            }
        }
    
    }
    

    I wanted to understand this a bit better myself. I am curious what happens when Connections is connected to an actual C++ signal. I could see the same loop happening if the update of the source blindly emits that signal.



  • I tested this with an actual C++ object.
    main.cpp:

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    class IntSource : public QObject
    {
        Q_OBJECT
    
    private:
        int value;
    public:
        Q_PROPERTY(int theproperty READ getValue WRITE setValue NOTIFY thepropertyChanged)
    
        int getValue(){return value;};
        void setValue(int invalue){
            value = invalue;
            emit thepropertyChanged(value);
        }
    
    signals:
        void thepropertyChanged(int thevalue);
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
    
        IntSource intsource;
        engine.rootContext()->setContextProperty("intsource", &intsource);
    
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    
    #include "main.moc"
    

    main.qml:

    import QtQuick 2.9
    import QtQuick.Window 2.2
    import QtQuick.Controls 2.5
    
    Window {
        visible: true
        width: 640
        height: 540
        title: qsTr("Sync Objects Testing")
    
        Component.onCompleted: {
            intsource.theproperty = 0
        }
    
        // can be C++ object in Connections source property
        // rectangle used as stand in
        Rectangle {
            id: source
    
            border.color: "red"
            border.width: 2
            width: 100
            height: 100
    
            property int theproperty: 0
    
            onThepropertyChanged: {
                console.log("source changed")
            }
    
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                text: parent.theproperty
            }
        }
        Rectangle {
            id: source2
    
            anchors.top: source.bottom
            border.color: "red"
            border.width: 2
            width: 100
            height: 100
    
            property int theproperty: 0
    
            Connections {
                target: intsource
                onThepropertyChanged: {
                    console.log("intsource changed")
                    source2.theproperty = target.theproperty
                }
            }
    
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                text: parent.theproperty
            }
        }
        Rectangle {
            id: boxa
    
            anchors.top: source2.bottom
            border.color: "green"
            border.width: 2
            width: 100
            height: 100
    
            property int theproperty: 0
    
            Connections {
                target: intsource
                onThepropertyChanged: {
                    boxa.theproperty = target.theproperty
                }
            }
    
            onThepropertyChanged: {
                console.log("boxa changed")
                intsource.theproperty = theproperty
            }
    
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                text: parent.theproperty
            }
        }
        Rectangle {
            id: boxb
    
            anchors.top: boxa.bottom
            border.color: "blue"
            border.width: 2
            width: 100
            height: 100
    
            property int theproperty: 0
    
            Connections {
                target: intsource
                onThepropertyChanged: {
                    boxb.theproperty = target.theproperty
                }
            }
    
            onThepropertyChanged: {
                console.log("boxb changed")
                intsource.theproperty = theproperty
            }
    
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
                text: parent.theproperty
            }
        }
    
        Button {
            id: but_change
    
            anchors.top: boxb.bottom
            text: "change"
    
            onClicked: {
                var num = Math.round(Math.random() * 100)
                console.log(num)
                intsource.theproperty = num
            }
        }
        Button {
            id: but_boxa
    
            anchors.top: but_change.bottom
            text: "boxa change"
    
            onClicked: {
                var num = Math.round(Math.random() * 100)
                console.log(num)
                boxa.theproperty = num
            }
        }
        Button {
            id: but_boxb
    
            anchors.top: but_boxa.bottom
            text: "boxb change"
    
            onClicked: {
                var num = Math.round(Math.random() * 100)
                console.log(num)
                boxb.theproperty = num
            }
        }
    
    }
    

    Why doesn't this form an endless loop? While the bind method does?



  • Okay, I get it. The onThepropertyChanged callbacks actually look to see if the property changed. If it didn't then it doesn't register a change and does not fire a signal. The C++ object however has no such checks and probably should. If 2 C++ objects as written were bound to each other like this they would indeed keep firing as there are no checks.

    The question is then: why the Binding would cause a loop? The property should have the same protections of only emitting a change when the value actually changes. Perhaps Binding is blind and needs the "when" defined:

    Binding {
      target: box_A 
      property: "theProperty"
      value: model.CppModel.theProperty
      when: theProperty !== model.CppModel.theProperty
    }
    

    Edit: I mean, it works, but prints out a binding loop message for some reason. The value does get updated properly.



  • @fcarney Thank you for your help, I can take a good look at your answer


  • Lifetime Qt Champion

    @fcarney said in Binding loop problem in an application with QML&C++:

    If 2 C++ objects as written were bound to each other like this they would indeed keep firing as there are no checks.

    Indeed they will and it even has a name: a signal storm :)


Log in to reply