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:
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:
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:
With this variable set, the above example:
now prints this debug message:
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!
Trusted software excellence across embedded and desktop platforms
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
13 Comments
30 - Aug - 2017
Sandro F
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 ;-).
31 - Oct - 2017
derM
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?
19 - Jun - 2019
Faisal
Is it possible to set QT_LOGGING_RULES="qt.qml.binding.removal.info=true" in QtCreator?
19 - Jun - 2019
Giuseppe D'Angelo
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.
15 - Jun - 2020
Arun
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
6 - Jul - 2020
Giuseppe D'Angelo
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.
30 - Aug - 2020
Arun
Thanks. yea that's true. Sure will log a bug.
1 - Dec - 2020
Kmarconi
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 ?
15 - Feb - 2021
Giuseppe D'Angelo
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.
15 - Feb - 2021
Pavel
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.
15 - Feb - 2021
Giuseppe D'Angelo
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.
18 - Apr - 2021
Pavel
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
18 - Apr - 2021
Giuseppe D'Angelo
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...