Javascript assignment destroys property binding



  • Lets have following QML code snippet:

    @
    import QtQuick 2.0

    Column {
    width: 200
    height: 200

    Rectangle {
        id: redRect
    
        color: "red"
        width: 100; height: 100
    
        NumberAnimation on width {
            duration: 1000
            loops: Animation.Infinite
            from: 100; to: 200
        }
    }
    
    Rectangle {
        color: "green"
        width: redRect.width; height: 100
    
        MouseArea {
            anchors.fill: parent
            onClicked: parent.width = 100
        }
    }
    

    }
    @

    The widths of the red and green rectangles are bound to each other by property binding. As soon as the Javascript assignment in MouseArea.onClicked is executed, the mentioned property binding is destroyed silently.

    I understand that an assignment to a property that is target of a property binding could be considerd bad code but is it intentional that this goes silently without any error or warning?

    P.S.: Sorry if this is a duplicate. I didn't find a similar thread.



  • I can ask my question slighty differently: Is there any way in QML to do something like this:

    @
    // ToggleButton.qml
    import QtQuick 2.0

    Rectangle {
    property bool status: false

    width: childrenRect.width
    height: childrenRect.height
    
    Text {
        text: parent.status ? "On" : "Off"
    }
    
    MouseArea {
        anchors.fill: parent
        onClicked: parent.status = !parent.status
    }
    

    }
    @

    @
    // main.qml
    import QtQuick 2.0

    Column {
    width: 200
    height: 200
    spacing: 10

    ToggleButton {
        id: btn1
    
        status: btn2.status
    }
    
    ToggleButton {
        id: btn2
    
        status: btn1.status
    }
    

    }
    @

    As long is I only press one button everything works fine. But as soon as I press the second button one of the bindings get destroyed.

    Is there any way in QML to implement that behavior without the binding getting destroyed while having ToggleButton as a reusable QML component?

    Of course I can use onStatusChanged signals instead of property bindings on the status properties directly. But then I need to inform the component's user to not use property bindings on that property which is not very user-friendly and error-prone.



  • You can use the States and Animations framework to do this, as the State change will cause the appropriate binding to be set.
    Alternatively, you can assign the result of Qt.binding() to assign a binding imperatively.


  • Moderators

    Hi, your situation and Chris' solution are both described in detail in this document: "Creating Property Bindings in JavaScript":http://qt-project.org/doc/qt-5/qtqml-syntax-propertybinding.html#creating-property-bindings-from-javascript



  • Hm, I don't quite understand your solution. The reference document doesn't help so much as it only describes the fact that the binding disappears on a Javascript assignment. Setting the binding imperatively in ToggleButton.qml is not possible because it is not known at that place.

    How can I create a reusable component that has properties that can be bound to from the outside while also being set inside by a Javascript expression?


  • Moderators

    Ah sorry, I misunderstood your original posts.
    [quote author="flobe" date="1404393682"]
    @
    ToggleButton {
    id: btn1

        status: btn2.status
    }
    
    ToggleButton {
        id: btn2
    
        status: btn1.status
    }
    

    @
    [/quote]First, you need to understand that your above code has a binding loop, which is illegal. You can't have btn1.status and btn2.status depend on each other; one of them needs to be independent. I'm surprised that your bindings actually worked in the beginning, as the QML engine should complain about binding loops.

    Here's another example of a binding loop:
    @
    Rectangle {
    // What values should width and height be?
    width: height
    height: width
    }
    @

    [quote author="flobe" date="1405086712"]How can I create a reusable component that has properties that can be bound to from the outside while also being set inside by a Javascript expression?[/quote]Hmm... I'm unsure how you want your component to behave.

    When you use property bindings, you make one property dependent on another.

    Consider this example:

    @
    property bool externalFlag: false

    ToggleButton {
    id: myButton
    status: parent.externalFlag
    }
    @

    The above snippet says: myButton.status should always copy the value of externalFlag. In other words, whenever externalFlag changes, myButton.status will change too to stay synchronized.

    So at startup, myButton.status copies the value of externalFlag. But, what should happen when you click myButton? There are 3 possibilities:

    myButton.status should toggle, becoming the opposite of externalFlag

    myButton.status should remain the same as externalFlag (in other words, the click should be ignored).

    myButton.status and externalFlag should both toggle

    Which do you want? (If you want #3, then your binding is the wrong way round -- externalFlag should depend on myButton.status instead)



  • Well, I'm looking for a reusable toggle button component which state (on or off) can be controlled by a mouse click (which results in - as far as I see - a Javascript assignment) as well as from a state property of another QML item.

    Currently I use the onStatusChanged signals to assign the new state to the toggle button and vice versa:

    @
    onStatusChanged: btn2.status = status
    @

    I consider this solution as not very maintainable because the user of the toggle button component must know about implementation details in order to use the onStatusChanged signal instead of a property binding.

    I have just checked the CheckBox implementation of QtQuick.Controls which show the same problem. It is not possible to keep two CheckBox items in sync by using property bindings. But using the onCheckedChanged signals it works.

    From my point of view the former approach should at least lead to a runtime warning when destroying the property binding in order for the component's user to detect such a subtle error.


  • Moderators

    Refer to my previous post again: Which behaviour are you expecting when you click the button? #1, #2 or #3?



  • Behavior #3.


  • Moderators

    [quote author="flobe" date="1405166984"]Behavior #3.[/quote]Then your property binding needs to be the other way round.

    @
    // Error: Dependency is the wrong way round
    property bool externalFlag

    ToggleButton {
    id: myButton
    status: parent.externalFlag
    }
    @

    @
    // Ok: externalFlag toggles when myButton is toggled
    property bool externalFlag: myButton.status

    ToggleButton {
    id: myButton
    }
    @

    @
    // Error: Binding loop, also known as "circular dependency"
    property bool externalFlag: myButton.status

    ToggleButton {
    id: myButton
    status: parent.externalFlag
    }
    @


  • Moderators

    [quote author="flobe" date="1405152382"]Well, I'm looking for a reusable toggle button component which state (on or off) can be controlled by a mouse click (which results in - as far as I see - a Javascript assignment) as well as from a state property of another QML item.

    ...

    I have just checked the CheckBox implementation of QtQuick.Controls which show the same problem. It is not possible to keep two CheckBox items in sync by using property bindings.[/quote]I think that makes sense.

    A CheckBox and ToggleButton have a boolean property. Usually, the mouse controls that property.

    If you bind that property to an external boolean variable, then the external variable will also try to control that property. You now have two different and unsynchronized things trying to write to that property -- this can create conflicts, as you've discovered.

    I think your onStatusChanged solution is the only way to achieve what you want.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.