New in Qt 5.10: Diagnostics when breaking QML bindings
Property bindings are one of the most interesting features of the QML language. In QML, when we set a value on a property, the right hand side expression isn’t evaluated just once to produce a value, like in a ordinary imperative language.
In particular, if the expression involves other properties, then the property we’re setting becomes bound to the properties in the expression: whenever they change their values, then the expression is automatically evaluated again, and the target property value updated.
For instance:
import QtQuick 2.0 Rectangle { width: 100; height: 100 // this is a binding: color: mouseArea.containsMouse ? "red" : "blue" MouseArea { id: mouseArea; anchors.fill: parent; hoverEnabled: true } }
In the snippet above, the color property of the Rectangle is the target of a binding: it is bound to the containsMouse property of the internal MouseArea. When the mouse moves inside or outside of the mouse area, its containsMouse property will change, causing the expression for color to be re-evaluated.
In layman’s terms: the rectangle will be red if the mouse cursor is hovering over it, and blue otherwise:
Property bindings allow to build our UIs in a very declarative way: we don’t need to write boilerplate “slots” to update our UI elements when some other property changes value. The expression that we can write on the right hand side can be as complex as we want, even involving function calls; the engine will do all the work for us, and will make the binding work as expected.
Breaking property bindings
Sounds too good to be true? Well, property bindings have a limitation: if we use an imperative assignment to set a value on a property, its binding will be lost. For instance, suppose we want the rectangle to turn green when we click over it. This is a possible implementation:
import QtQuick 2.0 Rectangle { width: 100; height: 100 // this is still a binding color: mouseArea.containsMouse ? "red" : "blue" MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true onClicked: { parent.color = "green" // oops! breaks binding } } }
In the onClicked signal handler we set the color property to green using an ordinary assignment from imperative JavaScript code. With this code, if we click on the rectangle, and then move the mouse over it and outside it, we’ll see that the rectangle doesn’t change color any more — it stays green. This happens because the imperative assignment destroyed the binding, and set the color property to one specific value.
Are broken bindings a problem?
In general, the above example is 100% correct QML code, following the documented QML semantics. Therefore, one cannot claim that such code should be rejected by the QML engine or unconditionally raise warnings.
However, in my experience at KDAB, I have never encountered one single occasion when breaking a binding was intended by the developer. Broken bindings were always caused by accident (during refactorings, not realizing that the original property was bound to something, etc.), or by excessive usage of JavaScript to manage state, instead of using more declarative approaches.
On some occasions, a silent overwriting of a binding with a value did actually introduce a runtime bug, which would then stay latent, maybe for a long time. Debugging such issues could be very challenging, as it is not normally clear what the problem is when reading the QML source code — the bug would manifest itself only when the JavaScript code containing an imperative assignment is run.
Debugging broken bindings
In order for a developer to be able to more easily track down these cases, KDAB contributed a new feature to the QML engine that will appear in Qt 5.10: the QML engine can now print debugging information whenever a binding is broken.
In order to enable the debug output you just need to enable output for the qt.qml.binding.removal logging category, for instance by exporting the QT_LOGGING_RULES environment variable:
export QT_LOGGING_RULES="qt.qml.binding.removal.info=true"
With this variable set, the above example:
import QtQuick 2.0 Rectangle { width: 100; height: 100 color: mouseArea.containsMouse ? "red" : "blue" MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true onClicked: { parent.color = "green" } } }
now prints this debug message:
qt.qml.binding.removal: Overwriting binding on QQuickRectangle::color at file:///example.qml:13 that was initially bound at file:///example.qml:6:12
As you can see, the message tells us which property was bound, where the binding had been established, and where the imperative statement is that breaks the binding. Pretty much everything we need to debug any issues with broken bindings!
About KDAB
KDAB is a consulting company offering a wide variety of expert services in Qt, C++ and 3D/OpenGL and providing training courses in:
KDAB believes that it is critical for our business to contribute to the Qt framework and C++ thinking, to keep pushing these technologies forward to ensure they remain competitive.
Yeah…looks really good. But I think you should not try to use declarative approach at all costs. Sometimes a simple imperative call saves a lot of debugging unwanted behaviour after adjusting a complex property binding.
However, can`t wait to try it with our complex application. Hopefully there are not too much “qt.qml.binding.removal:” prints ;-).
> Hopefully there are not too much “qt.qml.binding.removal:” prints ;-).
Hopefully there are not too much breaking bindings in the libraries, so you can focus on the points where you break the bindings your self.
It would be awesome if you could add some annotation to disable this (and other warnings, such as binding loop warnings) if there are few specific points where it is intended.
How will it behave if I *override (possibly temporarily)* the binding, using a Binding object?
Is it possible to set QT_LOGGING_RULES=”qt.qml.binding.removal.info=true” in QtCreator?
Yes. Click on the left side on “Projects”, then from the leftmost list select “Run” under your active kit(s), then in the bottom you can change the environment variables of the running binary. See https://doc.qt.io/qtcreator/creator-run-settings.html#selecting-the-run-environment for more info.
Is there an option to filter out the Binding Removal inside the Qt Modules.
I get a lot of binding issues like those below
_qt.qml.binding.removal: Overwriting binding on QQuickShaderEffect_QML_98::gwts_
Hi,
Well, you could install a custom message logger (see qInstallMessageHandler) and filter out messages like that one. I would also recommend to file a bug against Qt, as Qt itself shouldn’t really be breaking bindings.
Thanks. yea that’s true. Sure will log a bug.
HI, do you have any ideas on why after using “qputenv(“QT_LOGGING_RULES”, “qt.qml.binding.removal.info=true”);” in my main.cpp file, I still do not have more information on my binding Loop error. Do I need to do something more ?
Hi, I’m not sure. The logging rule is for binding being broken, not about binding loops. For those, I’d recommend start peeking at the bindings with GammaRay.
It make impossible to debug QML heaving components. Each component have its own default values for each of properties. While ovverriding them break, break, break, break… No way to debug something at all. It looks as if each default value assumed as a “binding”.
I get too much breaks but no any logs.
Hi,
Yes, it’s a double edged sword: there’s no way to distinguish a “benign” binding being broken, and a “malign” one. An easy workaround can for instance be disabling the logging category when the application is starting up, and only enable it “later”.
However, I’m not sure what you mean with “default values”. If you have a component that has an initial value for a property, and then you instantiate that component with another value for that property, there’s no binding broken there — the “default” is never set in the first place, only the value specified on instaitation. If you see a broken binding there, I’d suggest to reduce to a testcase and submit a bug report.
Thanx a lot for reply. It is a real unsolved problem what stop my job – I cannot debug. The debugger stops, show me a random QML number and Qt creator indicate “Current debugger location for QML” – and no any more info. I don’t know why it stop.
https://forum.qt.io/topic/125708/qml-debugging-stops-not-on-breakpoints
Hi,
I’m afraid I don’t know much about your specific problem with Qt Creator and QML debugging. It usually “just works” here. If you have any means of creating a reproducible testcase, sounds like a bug somewhere, and should be reported upstream…