Unsolved 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 ABox_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
-
@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 :)