问题
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