Skip to main content

Raymii.org Raymii.org Logo

Quis custodiet ipsos custodes?
Home | About | All pages | Cluster Status | RSS Feed | Gopher

Qt/QML Property bindings break after a JavaScript assignment

Published: 19-05-2022 05:30 | Author: Remy van Elst | Text only version of this article


Table of Contents


Property bindings are one of the most powerful concepts in Qt/QML. Property bindings let you specify relationships between different object properties. When a properties dependencies change in value, the property is automatically updated according to the specified relationship. The QML engine monitors the properties dependencies (that is, the variables in the binding expression). When a change is detected, the QML engine re-evaluates the binding expression and applies the new result to the property. One little known caveat with property bindings is that they break after a static JavaScript assignment (property = value). This post shows you the different behaviors and how to use Qt.binding() to assign property bindings via JavaScript.

I'm developing a desktop monitoring app, Leaf Node Monitoring, open source, but paid. For Windows, Linux & Android, go check it out.

Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.

You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $100 credit for 60 days.

Do note that this behavior is intended, documented and can be useful in situations where you want to purposely want to break a property binding.

This post is intended to inform you of the different behaviors, as it can be confusing if you're unaware of what is happening. A colleague of mine was working on a bug, he was not aware of this behavior and it cost him two hours to figure out that it was the underlying issue. Could happen to me as well when you're knee-deep into a debugging session.

Demonstration

demo

The image above demonstrates the issue.

The top Button (id: boundButton) text property is bound to the TextField (id: bindableTextField) text property, so when you edit the text, the text on the button updates automatically.

The second button breaks the binding by doing a JavaScript assignment in it's onClicked function:

onClicked: boundButton.text = "Binding broken"

If you've clicked the button and then change the text in the TextField, the button's text no longer reflects the text you've typed.

The last button restores the property binding using the Qt.binding() function:

onClicked: boundButton.text = Qt.binding(function () {
            return bindableTextField.text
        })
    }

A bit convoluted syntax wise, but it gets the job done.

Qt.binding()

The documentation states the following:

Returns a JavaScript object representing a property binding, with a function that evaluates the binding. There are two main use-cases for the function: firstly, to apply a property binding imperatively from JavaScript code and secondly, to apply a property binding when initializing property values of dynamically constructed objects (via Component.createObject() or Loader.setSource()).

Confusingly, that page has examples for the second use case, not the first. Simpler examples are on the other doc page, transcribed below:

The Rectangle below initially ensures that its height is always twice its width. However, when the space key is pressed, the current value of width*3 will be assigned to height as a static value. After that, the height will remain fixed at this value, even if the width changes. The assignment of the static value removes the binding.

Rectangle {
    width: 100
    height: width * 2

    focus: true
    Keys.onSpacePressed: {
        height = width * 3
    }
}

If the intention is to give the rectangle a fixed height and stop automatic updates, then this is not a problem. However, if the intention is to establish a new relationship between width and height, then the new binding expression must be wrapped in the Qt.binding() function instead:

Rectangle {
    width: 100
    height: width * 2

    focus: true
    Keys.onSpacePressed: {
        height = Qt.binding(function() { return width * 3 })
    }
}

Now, after the space key is pressed, the rectangle's height will continue auto-updating to always be three times its width.


The other use case is, in my experience, not used often, or at least not in the projects I've worked on. Dynamically creating QML objects is fun at first, but you loose type safety and it's all strings, so changing one name will work fine at first, until you get runtime errors somewhere else. Maybe I'm spoiled by the compile-time safety, but such a small typo or rename has caught me more than I'd like to admit. Whereas, in C++ it'll just won't compile anymore.

Tags: articles , bindings , c++ , cpp , javascript , qml , qt , qt5 , qt6