We’ve got a guest post from Mario Boikov to show you a great tip for QML! – Ed.
Today’s tip will demonstrate a QML-language feature: the ability to add new properties to an already-existing component. The example is based on the Slider control in Cascades™ and gives a hint on how the feature can be used, at least for this particular situation.
If you read the Slider control documentation, you’ll find a property named value of type “float” — which, of course, holds a Slider’s current value. If you bind the value property to a Label’s text property, you’d expect the text to change as soon as you start to drag the Slider’s handle, but that is not how the Slider is implemented in Cascades. The slider will only notify value changes when the user removes the finger from the Slider. If you want to track changes to the Slider’s value while the user is moving the handle, you need to connect to the valueChanging signal.
The following is a typical Slider scenario where the Slider’s value is tracked. The code isn’t complete in the following two examples because I only want to highlight the relevant parts. You’ll find a complete example at the end of the article.
ImageView {
id: imageView
}
Slider {
id: imageScaleSlider
fromValue: 0.5
toValue: 2.5
value: 1.0
onValueChanging: {
scaleValueLabel.text = "Scale: " + value.toFixed(2)
imageView.scaleX = value
imageView.scaleY = value
}
}
Label {
id: scaleValueLabel
text: "Scale: " + imageScaleSlider.value.toFixed(2)
}
The example above does the job. The Slider will update the label’s text property and also the ImageView’s scale factor, but personally, I don’t think this is a good solution. First of all, the code doesn’t utilize property bindings well which is one of the major advantages with QML. Secondly, the dependencies are inverted. The Slider has a strong coupling to both imageView and the scaleValueLabel. It should be the other way around — imageView and scaleValueLabel should bind to a property on imageScaleSlider.
So let’s take a look on how we can re-factor the code above, and try creating a more QMLified solution which makes use of proper property binding by adding a new property to the Slider control.
ImageView {
scaleX: imageScaleSlider.scaleValue
scaleY: imageScaleSlider.scaleValue
}
Slider {
id: imageScaleSlider
// Introducing the trackable scale value property
property real scaleValue: value
fromValue: 0.5
toValue: 2.5
value: 1.0
onValueChanging: scaleValue = value
}
Label {
text: "Scale: " + imageScaleSlider.scaleValue.toFixed(2)
}
So what happened? Well, we actually managed to remove imageScaleSlider’s dependency to the ImageView and the Label and we only have one line left of imperative code in the onValueChanging callback. Now the responsibility of how the scale value is used is given back to ImageView and Label. For example, the formatting of two decimals is handled by the expression in the Label’s text property on not in the onValueChanging callback.
I’m always trying to use property bindings as much as possible because it really fits well with the declarative QML language. Also, it often results in a more maintainable code and it’s easier to tell where a component gets its value from.
Finally, here’s the complete example:
import bb.cascades 1.0
Page {
id: mainPage
content: Container {
background: Color.create ("#262626")
layout: StackLayout {
leftPadding: 16
rightPadding: 16
}
Container {
layoutProperties: StackLayoutProperties {
horizontalAlignment: HorizontalAlignment.Fill
spaceQuota: 1
}
clipContentToBounds: true
ImageView {
imageSource: "asset:///image.jpg"
layoutProperties: StackLayoutProperties {
verticalAlignment: VerticalAlignment.Center
horizontalAlignment: HorizontalAlignment.Center
spaceQuota: 1
}
scaleX: imageScaleSlider.scaleValue
scaleY: imageScaleSlider.scaleValue
scalingMethod: ScalingMethod.AspectFit
}
}
Divider {}
Container {
Slider {
id: imageScaleSlider
property real scaleValue: value
fromValue: 0.5
toValue: 4
value: 1.0
onValueChanging: scaleValue = value
}
Label {
id: label
layoutProperties: StackLayoutProperties {
horizontalAlignment: HorizontalAlignment.Center
}
text: "Scale: " + imageScaleSlider.scaleValue.toFixed(2)
}
}
}
}
And that’s it! Let me know if you have any questions in the comments.
