QML/Cascades tip of the day: Managing Properties of an Existing Component

Cascades

We’ve got a guest post from Mario Boikov to show you a great tip for QML! – Ed.

TITLE_IMAGE

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.

About Alex Kinsella

Promoter of apps. Connoisseur of tacos. By day, I'm a PR and Social Media Manager at BlackBerry.

Join the conversation

Show comments Hide comments
+ -
blog comments powered by Disqus