Solved How to write a self contained editor in QML (like C++ implemented CheckBox is)
-
I try to write MyCheckBox in QML that can be used the same as a Controls2 CheckBox.
An obvious solution is something like:
Rectangle { id: root property bool checked color: checked ? 'black' : 'red' MouseArea { anchors.fill: parent onClicked: root.checked = !root.checked } }
But if the parent binds the "checked" property to anything, the binding breaks because of the assignment in the onClicked handler.
Item { property bool allow: true MyCheckBox { checked: allow onCheckedChanged: allow = checked } }
In the above code, using Controls2 CheckBox instead of MyCheckBox works as expected. (because on the c++ side, there is a better control how to manage bindings)
The only alternative I found is to define a new signal in MyCheckBox "checkedEdited(bool newChecked)", trigger that signal (instead of the assignment to "checked") and listen to it in the parent to update "allow", that will in turn update "checked".
I find this "solution" really bad:
- MyCheckBox is unable to update its own value now: it relies on the parent doing its job,
- The parent has to know about this alternate signal, instead of the obvious onCheckedChanged.
- Because Controls2 is used as well, one must figure every time what's the correct way of binding stuff, also making refactoring (replacing CheckBox by MyCheckBox) very frustrating.
Does anyone has a simple recipe, that does not add burden to the parent?
-
@monsieurgustav I think the problem is your
Item
definition.I would change it to:
Item { property alias allow: _chkBox.checked MyCheckBox { id: _chkBox checked: true } }
This will avoid binding loop
-
True. But it does not work if there are multiple "editors" for the same value.
Item { property alias allow: _chkBox.checked MyCheckBox { id: _chkBox checked: true } MyOtherKindOfCheckBox { ??? } }
-
hello, if you are trying to set from your parent went to allow the checked property to swap from true/false maybe you could try something like this:
Rectangle { id: root property bool checked property bool allow: true color: checked ? 'black' : 'red' MouseArea { anchors.fill: parent onClicked: { root.checked = root.allow ? !root.checked : root.checked } } } Item { id: parentRoot property bool allow: true MyCheckBox { id: checkBox checked: allow onCheckedChanged: parentRoot.allow = checkBox.allow } }
With this:
- MyCheckBox is unable to update its own value now: it relies on the parent doing its job
R/we no longer relies on the parent to update its own value. - The parent has to know about this alternate signal, instead of the obvious onCheckedChanged.
R/ We no longer need to use another signal and we can check onChckedChanged if needed. - Because Controls2 is used as well, one must figure every time what's the correct way of binding stuff, also making refactoring (replacing CheckBox by MyCheckBox) very frustrating.
R/ The allow property in the checkBox object will now only be modified if there is an special need on each object you require the checkBox and will no longer depend on any parent handling the check property.
- MyCheckBox is unable to update its own value now: it relies on the parent doing its job
-
@monsieurgustav said in How to write a self contained editor in QML (like C++ implemented CheckBox is):
True. But it does not work if there are multiple "editors" for the same value.
Okay, it seems I don't really understand what your use case is.
Can you explain what the final goal is? -
@GabrielRR said in How to write a self contained editor in QML (like C++ implemented CheckBox is):
root.checked = root.allow ? !root.checked : root.checked
I think the "checked " binding the broken after this assignment.
-
@KroMignon Currently, I try to write code that display/edit the same underlying data (double) to different editors in the same page. I managed to do it but it is not straight forward.
I'd like to reach the same simplicity achieved in:
property bool test: true CheckBox { anchors.centerIn: parent anchors.verticalCenterOffset: 0 checked: parent.test onCheckedChanged: parent.test = checked } CheckBox { anchors.centerIn: parent anchors.verticalCenterOffset: 50 checked: parent.test onCheckedChanged: parent.test = checked }
The above code works, no binding is broken at any time, and it is simple: only "checked" and the obvious "onCheckedChanged" are needed from the outside.
-
@monsieurgustav said in How to write a self contained editor in QML (like C++ implemented CheckBox is):
The above code works, no binding is broken at any time, and it is simple: only "checked" and the obvious "onCheckedChanged" are needed from the outside.
Okay, now I understand, but I have no idea how to do it in QML.
In C++ there is no problem to do this, because Q_PROPERTY gives you more freedom. -
OK, I found a way, the result is quite nice. But it's going to be a little bit dirty... :)
This works as I wanted, no binding is broken:
property bool test: true MyCheckBox { anchors.centerIn: parent anchors.verticalCenterOffset: 0 checked: parent.test onCheckedChanged: parent.test = checked } MyCheckBox { anchors.centerIn: parent anchors.verticalCenterOffset: 50 checked: parent.test onCheckedChanged: parent.test = checked }
MyCheckBox.qml
Rectangle { id: root property bool checked color: checked ? 'black' : 'red' MouseArea { anchors.fill: parent onClicked: QmlPropertyUtils.setValueKeepBinding(root, "checked", !root.checked) } }
In c++, declare a singleton (qmlRegisterSingletonInstance) with this function:
#include <QQmlProperty> #include <private/qqmlproperty_p.h> #include <private/qqmlpropertydata_p.h> void QmlPropertyUtils::setValueKeepBinding(QJSValue object, QString propertyName, QVariant value) { if (!object.isQObject()) { qWarning() << object.toString() << "is not an qobject"; return; } auto qobject = object.toQObject(); QQmlProperty property(qobject, propertyName); if (!property.isValid() || !property.isWritable()) { qWarning() << propertyName << "is not a valid and write property"; return; } if (QQmlPropertyPrivate::write(property, value, QQmlPropertyData::DontRemoveBinding)) { qDebug() << "setValueKeepBinding(" << object.toString() << propertyName << value.toString() << ") succeeded"; } else { qWarning() << "setValueKeepBinding(" << object.toString() << propertyName << value.toString() << ") failed"; } }
As you may have notice, it relies on private headers (hence the dirtiness...) so:
- you need to add them in the include search path.
- you better don't care about binary compatibility...
using cmake: include_directories(${Qt${QT_VERSION_MAJOR}Quick_PRIVATE_INCLUDE_DIRS})