Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QML property temporary value without completely breaking binding

In QML (at least until version 5.61), property bindings are broken once a new value is assigned in a JavaScript context:

Item {
    property bool sourceBool: <some changing value e.g. from C++ code>
    property bool boundBool: sourceBool

    function reassignBool() {
        boundBool = true;
    }
}

Once reassignBool is called, boundBool will be true regardless of the state of sourceBool.

I would like to be able to assign a temporary value to a property that will not break the original binding; the temporary value would persist until any of the NOTIFY signals associated with the original binding are triggered, at which point the bound property would again reflect the value computed by the binding.

As an example use-case, suppose I have a button that I don't want the user to be able to press twice in a row, and which is enabled or disabled according to some set of rules:

MyButton {
    id: onceOnlyButton
    // Suppose the QML type `MyButton` provides a boolean property
    // called `enabled` that greys out the button and prevents it from
    // being clicked when `false`.
    enabled: <condition1> && <scondition2>
    onClicked: {
        <schedule some asynchronous work>
    }
}

Suppose that, when the button is clicked, <condition1> will eventually become false, so that the button would eventually be disabled appropriately--but not immediately.

So I would like to do something like the following:

....
onClicked: {
    enabled = false
    <schedule some asynchronous work>
    enabled = Qt.binding(function() {
        return <condition1> && <condition2>
    }
}

... but of course as soon as I re-establish the binding, since condition1 hasn't yet become false, enabled will become true again.

I suppose I could create some kind of Connections object that would connect the NOTIFY signal(s) associated with <condition1> to a handler that would invoke Qt.binding, then....delete the Connections object, I suppose. But this seems pretty complicated and inelegant.

Is there any way to ensure that enabled is set to false immediately, then re-bound to the NOTIFY signals for <condition1> and <condition2> as soon as any of those signals are emitted?

1 I'm not entirely sure how assignments affect bindings in 5.7, but I know that assigning a value to a property does not automatically break the binding. So this might just work automagically out-of-the-box in 5.7.

like image 241
Kyle Strand Avatar asked Aug 10 '16 18:08

Kyle Strand


1 Answers

This should be fairly straightfoward to achieve using QML Binding type, which is great for temporarily overriding a binding without destroying it (and it has been around since before version 5.6!)

You will need a variable or property which you can use in the when property of the Binding type. In the example below I have used timer.running because the asynchronous work scheduled here is just a timer.

import QtQuick 2.7
import QtQuick.Controls 2.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480

    property bool condition1: true
    property bool condition2: true

    Button {
        id: onceOnlyButton

        enabled: condition1 && condition2

        Binding on enabled {
            when: timer.running
            value: false
        }

        onClicked: {
            timer.start()
        }
        text: "onceOnlyButton"
    }

    // stuff below is just for demonstration
    Button {
        anchors.left: onceOnlyButton.right
        text: "toggle Condition1\n(to prove binding still intact)"
        onClicked: condition1 = !condition1
    }

    Timer {
        id: timer
        interval: 2000
        running: false
        repeat: false
        onTriggered: condition1 = false
    }
}

If you don't have a variable you can use to establish whether the asynchronous work is still going on (like my timer.running) then you will need to create one, as below property forceButtonDisable. This makes it look more complicated, so lets wrap it up inside a new re-usable component:

OnceOnlyButton.qml ##

import QtQuick 2.7
import QtQuick.Controls 2.0

Item {
    id: control
    property alias text: button.text
    property bool enabled
    property bool forceButtonDisable: false

    signal clicked()

    onEnabledChanged: forceButtonDisable = false

    width: button.implicitWidth
    height: button.implicitHeight

    Button {
        id: button

        width: control.width
        height: control.height
        enabled: control.enabled

        Binding on enabled {
            when: control.forceButtonDisable
            value: false
        }

        onClicked: {
            control.forceButtonDisable = true
            control.clicked()
        }
    }
}

main.qml

import QtQuick 2.7
import QtQuick.Controls 2.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480

    property bool condition1: true
    property bool condition2: true

    // once clicked, the button is temporarily disabled until original binding expression results in a different value
    OnceOnlyButton {
        id: onceOnlyButton

        text: "onceOnlyButton"
        enabled: condition1 && condition2
        onClicked: timer.start()
    }

    // stuff below is just for demonstration
    Button {
        anchors.left: onceOnlyButton.right
        text: "toggle Condition1\n(to prove binding still intact)"
        onClicked: condition1 = !condition1
    }

    Timer {
        id: timer
        interval: 2000
        running: false
        repeat: false
        onTriggered: condition1 = false
    }
}
like image 53
Mark Ch Avatar answered Nov 17 '22 18:11

Mark Ch