How do I have declarative, bidirectional bindings involving QML MouseAreas?

走远了吗. 提交于 2020-01-06 07:44:46

问题


I've created a QML UI that has a dial and a custom control. The custom control is basically a progress bar with a MouseArea to allow the user to set the value by clicking it. As Qt's property binding docs point out, as soon as I assign to the custom control's value from Javascript in the MouseArea click handler, I lose the declarative binding between it and the dial.

Is it possible to make this binding bidirectional, or even better, to link the values of both controls to a single value above both of them in the QML hierarchy? And is it possible to do this with declarative syntax so I don't have complex event handler code in every control?

import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import QtQuick.Window 2.2
import QtQuick.Shapes 1.0


Window {
    id: window
    visible: true
    width: 800
    height: 200

    readonly property int range: 10

    RowLayout {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
        spacing: 5

        Dial {
            id: dial1
            live: true
            from: 0
            to: window.range
            stepSize: 1
            snapMode: Dial.SnapAlways
        }

        Control {
            id: dut

            implicitWidth: 200
            implicitHeight: 50

            property int range: window.range
            property int value: dial1.value

            onValueChanged: {
                console.log("New value: " + value);
            }

            Rectangle {
                width: parent.width
                height: parent.height
                color: Qt.rgba(0,0,0,0)
                border.color: Qt.rgba(0,0,0,1)
                border.width: 1
            }

            Rectangle {
                width: parent.width * dut.value/dut.range
                height: parent.height
                color: Qt.rgba(0,0,0,1)
            }

            MouseArea {
                anchors.fill: parent

                onClicked: {
                    dut.value = Math.round(mouseX/width * dut.range);
                }
            }
        }
    }
}

Note that if I reverse the relationship ie. have dial1.value: dut.value, then the binding isn't broken (although it's not quite bidirectional).

I realise that this example basically reinvents the scrollbar, but I'm trying to work my way up to more complex controls, for which declarative relationships between values would make life much easier.

Elaboration from a comment: What I don't understand, but want to, is how it's done for other QML components. For example, with a Dial I can set its value property to be bound to some other component's property, and clicking on the dial doesn't remove that binding. I don't have to hook into its mouse events to do that. Despite looking through the source for how this is done, I'm not really any closer to understanding it.

There are other questions about bidirectional property bindings in QML, but I haven't been able to apply them to my problem because (a) I really, really want something declarative, and (b) the MouseArea properties and events don't seem to work well with Binding objects (as in, I can't figure out how to integrate the two things).


回答1:


I would have done this:

import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import QtQuick.Window 2.2
import QtQuick.Shapes 1.0


Window {
    id: window
    visible: true
    width: 800
    height: 200

    readonly property int range: 10
    property int commonValue

    RowLayout {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
        spacing: 5

        Dial {
            id: dial1
            live: true
            from: 0
            to: window.range
            stepSize: 1
            snapMode: Dial.SnapAlways
            onValueChanged: {
                commonValue = dial1.value
                console.log("New value: " + value);
            }
        }
        Rectangle {
            width: 200
            height: 50
            color: Qt.rgba(0,0,0,0)
            border.color: Qt.rgba(0,0,0,1)
            border.width: 1

            MouseArea {
                anchors.fill: parent

                onClicked: {
                    commonValue = Math.round(mouseX/width * window.range)
                    dial1.value = commonValue
                }
            }

            Rectangle {
                width: parent.width * window.commonValue/window.range
                height: parent.height
                color: Qt.rgba(0,0,0,1)
            }
        }
    }
}



回答2:


Use a Binding QML Type:

MouseArea {
    id: mouseArea
    anchors.fill: dut
}

Binding {
    target: dut
    property: 'value'
    value: Math.round(mouseArea.mouseX/mouseArea.width * dut.range);
    when: mouseArea.pressed && mouseArea.containsMouse
}

Note that the when property on the Binding means it's only active as a binding when those conditions are fulfilled ie. the mouse is over the area and one of the "accepted buttons" is pressed.

This does not mean that the value reverts when the conditions aren't met, just that the value stops updating when they're not met. However, if you have another binding active somewhere else, that one may cause the the value to "snap back" because it will "take over" when this Binding ceases to apply.

Depending on the other components you use, this might not even be enough, and you might need to implement your properties in C++ to get them to work as you expect.



来源:https://stackoverflow.com/questions/48898364/how-do-i-have-declarative-bidirectional-bindings-involving-qml-mouseareas

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!