Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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.


  • @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})
    

Log in to reply