10

I'm learning QtQuick and I'm playing with data binding between C++ classes and QML properties.

In my C++ object Model, I have two properties :

Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
Q_PROPERTY(bool status READ getStatus WRITE setStatus NOTIFY statusChanged)

And in my .qml file :

TextEdit {
    placeholderText: "Enter your name"
    text: user.name
}

Checkbox {
    checked: user.status
}

When I change the user name with setName from my C++ code, it is automatically reflected in the view. When I check/uncheck the checkbox, or when I call setStatus() from my C++ code, nothing happens. It seems the property checked of checkboxes haven't the same behavior as TextEdit components.

I don't want to bind my properties in a declarative way. Doesn't Qt Quick support property binding ?

Thank you for your help.

Neozaru
  • 1,109
  • 1
  • 10
  • 25
  • 3
    The way you set up your binding between `checked` and `user.status`, it means that when `user.status` changes, the checkbox will be (un)checked. If the user clicks on the checkbox, the binding gets removed. – leemes May 25 '14 at 21:49
  • 1
    For the other way around, it *should* work: when you call `setStatus`, it should change the property `user.status` in QML and indeed trigger the checkbox *as long as you didn't click on it before*. And this only works if you emit the `statusChanged` signal within `setStatus`. – leemes May 25 '14 at 21:50
  • 1
    Thank you for your comment. I do want the behavior you described in your first comment (if the value is changed, the checkbox should change) but I want also the user be able to check/uncheck it. If I understand, triggering the checkbox destroys the bindings. Is it a QtQuick behavior, or Checkbox-specific ? If I create my own checkbox/switch component, can I disable this behavior ? – Neozaru May 25 '14 at 21:58
  • 1
    I double-checked emited signals, put a debug in the `setStatus` method, but even to "user clicks on checkbox" scenario doesn't work. I will test tomorrow with another property name (maybe "enabled" is used for internal stuff in QML) and will retry with a minimal code. Thank you for your help. – Neozaru May 25 '14 at 22:23
  • 1
    Well, if you want your property to be changed when the user clicks the check box you need to do something else. What you want is essentially a *bidirectional* binding which is not (directly) supported in QtQuick, in other words you need some code in C++ or QML (JavaScript) – leemes May 25 '14 at 23:11
  • Could you add your `setStatus()` c++ implementation? – Simon Warta Jun 02 '14 at 07:22

3 Answers3

12

As leemes points out, user clicking the check box breaks the binding you've created. So, don't create the binding, but instead connect to the change signal directly to handle the "get" case. Use "onClicked" to handle the "set" case. This solution requires you also initialize in Component.onCompleted(). For example...

CheckBox {
    id: myCheck
    onClicked: user.status = checked
    Component.onCompleted: checked = user.status
    Connections {
        target: user
        onStatusChanged: myCheck.checked = user.status
    }
}
Mike
  • 156
  • 3
  • 8
  • Mike you helped me a lot, even though it took a little bit for me to understand the reason why the binding breaks. It is probably because internally the implementation of the checkbox has something like `onCheckedChanged: checked = !checked`, isn't it? Until now I thought that [two way bindings](http://imaginativethinking.ca/bi-directional-data-binding-qt-quick/) worked for every QML component but this is not the case; now I'm wondering: how do I understand when I can use two way bindings and when I have to rely on connections? – Brutus Oct 01 '19 at 14:28
3

A way around this is to restore the binding (that gets removed by the user clicking the checkbox) in onClicked, by something like:

CheckBox {
    checked: user.status
    onClicked: {
        user.status = checked;
        checked = Qt.binding(function () { // restore the binding
            return user.status;
        });
    }
}

This avoids problems if you don't have the possibility to access your model at the time Component.onCompleted is invoked.

jco
  • 1,335
  • 3
  • 17
  • 29
1

I find it more natural to make checkbox only emit signal on click, not change its state:

// MyCheckBox.qml 
CheckBox {
    id: control

    property bool changeOnClick: true // or just emit clicked()
    
    MouseArea {
        anchors.fill: parent
        enabled: !control.changeOnClick
        onClicked: control.clicked();
    }
}

Then you can bind it once and request change of the source on click:

MyCheckBox {
    changeOnClick: false
    checked: user.state
    onClicked: {
        user.state = !user.state;
    }
}
Nuno André
  • 4,739
  • 1
  • 33
  • 46
rsht
  • 1,522
  • 12
  • 27