Controls appear to break bindings when interacted with
-
For example, a basic checkbox:
CheckBox { checked: model.value onClicked: model.maybe_change_value_maybe_not_depends_on_complex_business_logic(); }What I expect this means is
checkedis now bound tomodel.value, and must never deviate frommodel.value. Unfortunately it can. Note my verbosely named function: sometimes the value is changed, but when it isnt the property cannot notify and thus the checkbox becomes desynced. In this state,checkedis observably different frommodel.value!! I cannot just dochecked = model.valuein the slot foronClickedbecause that breaks the binding completely. I instead have to do janky stuff like this:CheckBox { property int evil_hacky_hack: 0 checked: evil_hacky_hack, model.value onClicked: { model.maybe_change_value_maybe_not_depends_on_complex_business_logic(); ++evil_hacky_hack // Force a re-evaluate } }Note that I've observed this happening with other stateful control types, like
TextField(witheditingFinished). This leads me to believe that it's a conscious design choice instead of a bug.So I guess my question here: Am I missing a better supported, more scalable way to do this that's applicable to all controls? Is there a way to make
CheckBox(and Qml's controls in general) a pure view for my model data without significant skulduggery? It's infeasible (and inadvisable, imo) to change my model to notify when a change doesn't happen.Qt 5.9 if it matters; I'm not sure what to search for in changelogs to check for fixes (or how to try this in Qt6). Also my first post here, lmk if this belongs elsewhere.
-
I dont know if this helps you now, since noone has responded... but the way i solve this (and i've had to do it on multiple ui elements) is as follows:
- Create custom qml type where the base item is the same as the original (CheckBox in this case)
- Add
property var checkedBinding - Add
Component.onCompleted:{if(typeof checkedBinding==='function' checked=Qt.binding(checkedBinding)} - Add
onClicked:{if(typeof checkedBinding==='function' checked=Qt.binding(checkedBinding)}
Then when using the component, instead of setting
checked:model.valueyou can usecheckedBinding:function(){return model.value}This is really annoying behavior and i wish qt would fix it. it's not as bad in this simple case, but it can cause a lot of issues when you are doing multiple components put together with custom behavior. The example that bothers me the most is making a DoubleSpinbox item , since the act of changing the binding itself to handle doubles automatically conflicts with using the element itself (even ignoring outside bindings) and will thus automatically unbind itself from the original implementation of the double spin box.
-
I strongly agree that this behaviour should be regarded as a bug. If a component (QtQuick.Controls.Button in this case) exposes a mutable property then this implicitly communicates that a client should be able to bind to that property without the binding being overwritten by internal behaviour of the control.
(Short of Qt fixing this bug) the more idiomatic fix is probably to reimplement Button yourself, by deriving QtQuick.Templates.Control and ensuring that no public bindings are ever internally overwritten. The problem then is that many other controls in the QtQuick.Controls module expect a QtQuick.Controls.Button in their API and therefore would also need reimplementing to accept your new well behaved button type.. before you know it you're reimplimenting the whole module ! Yeah.. not great.
-
By default, Qt Quick Controls CheckBox updates its checkState when the user interacts with it
(and cycles through states when tristate is enabled). That write breaks achecked: model.value
binding, so if your business logic rejects the change and does not updatemodel.value, the UI can
stay desynced.Option 1: handle
onToggledand re-establish the binding (and optionally snap back immediately):CheckBox { id: cb checked: model.value onToggled: function(wanted) { model.maybe_change_value_maybe_not_depends_on_complex_business_logic(wanted) if (model.value !== wanted) { cb.checked = Qt.binding(function() { return model.value }) } } }Option 2: override
nextCheckStateso the control never toggles itself:CheckBox { checked: model.value onClicked: model.maybe_change_value_maybe_not_depends_on_complex_business_logic() nextCheckState: function() { return model.value ? Qt.Checked : Qt.Unchecked } }