Javascript assignment destroys property binding
-
Lets have following QML code snippet:
@
import QtQuick 2.0Column {
width: 200
height: 200Rectangle { 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.0Rectangle {
property bool status: falsewidth: 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.0Column {
width: 200
height: 200
spacing: 10ToggleButton { 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. -
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?
-
Ah sorry, I misunderstood your original posts.
[quote author="flobe" date="1404393682"]
@
ToggleButton {
id: btn1status: 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: falseToggleButton {
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.
-
Refer to my previous post again: Which behaviour are you expecting when you click the button? #1, #2 or #3?
-
Behavior #3.
-
[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 externalFlagToggleButton {
id: myButton
status: parent.externalFlag
}
@@
// Ok: externalFlag toggles when myButton is toggled
property bool externalFlag: myButton.statusToggleButton {
id: myButton
}
@@
// Error: Binding loop, also known as "circular dependency"
property bool externalFlag: myButton.statusToggleButton {
id: myButton
status: parent.externalFlag
}
@ -
[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.