ImaginativeThinking.ca


A developers blog

How to do Bi-directional Data Binding in Qt Quick

By: Brad

In this tutorial I’m going to show you how property bindings work in Qt and how to setup a bi-directional data binding in Qt Quick.

Property binding is when you tell one property to mirror the value of another property; that is property A equals property B and when property B is updated so will property A, they are bounded to each other. Why do you want to have two properties have the same value? Well property B could be a property exposed by the back end (i.e. View Model) called isEnabled and you can bind it to the Visible property of an UI element so that its only shown when the back-end construct is enabled.

A framework that offers property binding also offers a mechanize to make sure binded properties stay in sync so you don’t have to manually check if property B changed in order to update property A; the framework takes care of all of that glue for you.

Property binding is a major component to Declariative UI Archetectures like WPF and Qt Quick

Property Binding vs. Property Assignment

In Qt Quick you can set the value of a property in two ways:
Property Assignment – This is where you assign a value to a property as a one off; you save a copy of the value in the property, if the original value is changed this property is unaffected.

property string myString: "Assigning this string value to the myString property"
property string myString2: model.getMyString // a Q_INVOKE method assigns a value to myString2
property string myString3
Component.onCompleted {
    myString3 = "javascript functions assign values to properties";
}

Property Binding – This is where two properties are binded together so if one updates its value the other automatically updates as well.

// myString is bounded to yourString; when yourString is updated myString will update as well
property string yourString: "Assigning this string value to the myString property"
property string myString: yourString 

property string myString2: model.myString // a Q_PROPERTY binds its value to myString2

// myString3 is binded to the result of the if statement; when model.myString is updated the if statement is
// re-evaluated and myString3 gets updated.
property string myString3: model.myString !== "" ? "Hello" : "World"

The above examples however are all one-way property binding; that is when the model.myString property is updated myString3 gets updated but if you try to set the value of myString3 manually you will break the binding. Not only will model.myString not get updated any future updates to model.myString will NOT be applied to myString3.

property string myString3: model.myString !== "" ? "Hello" : "World

Component.onCompleted {
    myString3 = "fuBar" // Binding has been broken!!!
}

Uni-Directional Property Binding

The above is Uni-Directional, that is properties are only bounded to in one direction; either property A is listening for changes to property B or property B is listening for changes to property A.

unidirectionaldatabinding

The above are examples of view elements being bounded to properties in the ViewModel such as binding back-end properties to labels or Boolean values used to enable/disable or show/hide UI elements but what if we’re implementing a user input form?

If the form is also one way, say a contact us form, then we can just revers the binding; instead of UI elements being bounded to properties in the ViewModel have the ViewModel properties bounded to data values in the UI.

TextInput {
    onTextChanged {
        // This is a property assignment but is triggered
        // each time the onTextChanged signal is emitted.
        model.myString = text
    }
}

OR

TextInput {
    id: myInput

    // This is a manually created property binding; now whenever
    // the text property of myInput is updated the myString property of model will
    // automatically be updated.
    Binding {
        target: model
        property: "myString"
        value: myInput.text
    }
}

But what if we’re talking about an edit form where the user loads up a previously saved set of data that they want to edit? In this case a combination of property assignment and property binding is probably the right approach.

TextInput {
    id: myInput

    // This is a manually created property binding; now whenever
    // the text property of myInput is updated the myString property of model will
    // automatically be updated.
    Binding {
        target: model
        property: "myString"
        value: myInput.text
    }

    Component.onCompleted {
        // One time property assignment to get the initial value.
        // This does not break the binding because we are assigning a value to the 
        // text property of myInput and the binding is on the myString property of model; that is
        // myString is listening to changes to myInput not the other way around.
        //
        // myString of model is bindinged to text of myInput so assigning values to 
        // myInput.text tells model.myString to update because its listening for the changes to myInput.
        myInput.text = model.myString
        // Note this line will trigger the above binding to go off updating the back-end. So you will have
        // one instance of the back-end updating the myString property to the same value but no recursion
        // will exist since myInput.text is the driver and does not respond to changes to model.myString.
    }
}

In this case the user will have to click a save button and we’ll send all input to the back end to be saved. However what if we have a form that needs to be updated in real time from multiple clients. In the above the default data was static once the user entered the edit mode they no longer got any updates from the back-end; if another user changed a value this user would overwrite it once they clicked save. What if we don’t have an edit mode and want all items to be updated in real time on all clients and let any client update any field (maybe we don’t have a save button and all changes are applied in real time). In this case we need the binding to be working in both directions (when I input a change in the UI my UI should update the back-end and if the back-end is updated the back-end should update the UI with the new data values).

For this we need bi-directional data binding.

Commercial break...


Bi-Directional Data Binding

Bi-Directional data binding is when we want both property A and property B to be bounded to each other so that if either changes both are updated.

bidirectionaldatabinding

Bi-Directional data binding isn’t really supported in Qt Quick but it is possible. What I mean by that is that in other declarative frameworks like WPF bi-directional (or Two-Way) data binding is supported, that is the framework is aware of it, so you can tell the framework that this biding is different (i.e. not One-Way) by setting an attribute in the XAML code. Qt Quick on the other hand is not aware of bi-directional data binding, it will treat it as a uni-directional data binding however you can declare two bindings on the same property so you can create two one-way data binding lanes effectively creating a bi-directional data binding.

TextInput {
    id: myInput
    text: model.myString // this is a uni-directional binding from viewmodel to view
    // whenever the viewmodel's myString property is changed the view is updated.

    // This is a uni-directional binding from view to viewmodel; whenever the myInput text
    // property is changed the viewmodel's myString property is updated.
    Binding {
        target: model
        property: "myString"
        value: myInput.text
    }
}

In the above we have two uni-directional data bindings; the first binds the text property of the UI element myInput to the myString property of the viewmodel model so that whenever the viewmodel’s myString property is updated the UI element’s text property will also get updated.

The second uni-directional data binding binds the viewmodel’s myString property to the UI element myInput’s text property so that whenever the UI element’s text property is updated the model’s myString property will be updated.

qtbidirectionaldatabinding

This allows other clients to make changes to the myString property and this clients input field will update and visa versa a user can input new data into the input field of this client and have it update the input field on all other clients; the data is being kept in sync in both directions.

The Gotcha

There is a gotcha however; remember Qt Quick is not aware of bi-directional data binding; it has no idea that we’ve hooked these two properties up to each other in opposing directions. The result is that we have a recursive binding loop.

If the above is run and lets say the viewmodel’s myString property is updated the first binding will cause the myInput’s text property to be updated with the new value of myString. But wait this will register as an update to the myInput’s text property, remember Qt Quick can’t tell the difference between an update caused by user input or by the view model, so the second binding is triggered causing the viewmodel’s myString property to be updated to the new value stored in the myInput’s text property. But oh wait, that will register as a change to the viewmodel’s myString property causing the first binding to get triggered again, rise-wash-repeat.

When the above occurs you will get a run-time warning that looks like this:

qrc:///main.qml:92:17: QML TextInput: Binding loop detected for property "text"

In languages like WPF we would have identified the binding as Two-Way and the framework would have taken care of the binding loop; in Qt Quick however we need to catch and deal with such binding loops.

To do this we need to go into the setter method for the myString Q_PROPERTY and add a check to see if the value being inputted matches the value we already have set in the property. If so then ignore this change request and do not emit the property changed signal which would engage the first binding thus breaking the recursion. Since the UI only needs to update when the value actually changes and here we know the value isn’t changing its perfectly safe not to emit the valueChanged() signal since there is no need for the UI to update, the value did not change.

void MyModel::setValue( QString value )
{
    if ( m_value != value )
    {
        m_value = value;
        emit valueChanged();
    }
}

If your using the Qt model instead of a Q_PROPERTY the same has to be done in the QAbstractModel::setData() method to ensure that the dataChanged() signal is not emitted when the data didn’t actually change.

So that is how you setup a Bi-Directional data binding in Qt Quick.
You can download a sample app that illustrates bi-directional data binding here.

Thank you I hope you have enjoyed and found this tutorial helpful. Feel free to leave any comments or questions you might have below and I’ll try to answer them as time permits.

Until next time think imaginatively and design creatively

Brad

My interest in computer programming started back in high school and Software Development has remained a hobby of mine ever since. I graduated as a Computer Engineering Technologist and have been working as a Software Developer for many years. I believe that software is crafted; understanding that how it is done is as important as getting it done. I enjoy the aesthetics in crafting elegant solutions to complex problems and revel in the knowledge that my code is maintainable and thus, will have longevity. I hold the designation Certified Technician (C.Tech.) with the Ontario Association of Computer Engineering Technicians and Technologists (OACETT), have been certified as a Professional Scrum Master level 1 (PSM I) and as a Professional Scrum Developer level 1 (PSD I) by Scrum.org as well as designated as an Officially Certified Qt Developer by the Qt Company. For more on my story check out the about page here

Feel free to write a reply or comment.

  • Dear Brad,

    thanks for your great tutorial!
    However I came across a situation, that can’t be solved your way, e.g.:

    CheckBox
    {
    id: cb1
    }

    CheckBox
    {
    id: cb2
    value: cb1.value
    Binding
    {
    target: cb1
    property: “checked”
    value: cb2.checked
    }
    }

    This will work well until the user checks/unchecks cb2, as the CheckBox will assign (!) a new value to cb2.checked. After that, cb2.value isn’t bound to cb1.value any more.

    Any hint, how to overcome this?

    • Your right, I ran into the same issue some time after writing this article when we started using the Qt Quick Controls. After using the Qt Quick Controls for a while my impression is that the designers of these controls did not expect them to have their values bounded to a model or other control but rather their values be set on load (assigned one time) then the control takes owner ship of the values, that is no binding. Most if not all Qt Quick Controls use assignments (flip the toggle switch and its code will assign a value to its value property) therefor making them incompatible with binding; well a non-Qt Quick Control can bind to the value of a Qt Quick Control but not via versa.

      Boo.

      You can’t blame them to much, I mean ya sure bi-directional binding on switches and check-boxes seems perfectly reasonable but bi-directional binding on text input fields gets messy when you think about it; what do you do if the user is typing and the model updates? Secondly if your just trying to make checkbox 1 un-check when you check checkbox 2 then you can set the exclusiveGroup property and the controls will do it themselves.

      In a recent project I’ve needed to support bi-directional binding on Qt Quick Controls where I have a switch that binds to a read/write property on my ViewModel but since the CheckBox control uses assignments when you tick it binding didn’t work. What I ended up doing was bi-directional assignments.

      CheckBox
      {
      id: cb1
      onCheckedChanged:
      {
      if (checked !== cb2.checked) {
      cb2.checked = checked
      }
      }
      }

      CheckBox
      {
      id: cb2
      onCheckedChanged:
      {
      if (checked !== cb1.checked) {
      cb1.checked = checked
      }
      }
      Component.onComplete: {
      cb2.checked = cb1.checked // I tend to do this vs. setting its checked property like you did to make it more clear that this is an assignment.
      }
      }

      Its clunky but so far is the only way I’ve figured out how to make this work.

      Hope that helps,
      Until next time think imaginatively and design creatively