New QML language features in Qt 5.15

While big changes are on their way for Qt 6.0, QML got some new language features already in 5.15. Read on to to learn about required properties, inline components and nullish coalescing.

Required Properties

Sometimes, you have components which require some property to be set, and do not really have good default values. You might for instance care about accessibility of your buttons, so you create an AccessibleButton, which should always have a description.


// AccessibleButton.qml
Button {
    property string description
    Accessible.description: description
}

However, the fact that the button has a description property, doesn't imply that anyone will set it. So you or your colleague might at some point instantiate the component with:


AccessibleButton {
    onClicked: (mouse)  => { /* fancy business logic here */ }
}

So much for accessibility: The description is now simply an empty string! You could of course give the property a default value, but which one? "Button"? Hardly helpful. "You should not hear this"? Well, at least QA might catch it now. But wouldn't it be more useful if the QML engine would know that this property need to be set?
Before Qt 5.15, there was unfortunately no way to enforce that the description gets set. But starting with Qt 5.15, this becomes possible:


Button {
    required property string description
    Accessible.description: description
}

Now, if an AccessibleButton is created, and description is not set, the whole application will fail to start. This can, however, not be done if the component gets created dynamically, for example via a Loader. In that case, there will only be a runtime warning.
We also plan to add further tooling support to qmllint and QtCreator to show a warning if required properties are not set.

Required Properties and Delegates

Additionally, required properties play a special role in delegates. As you might know, delegates can access the model roles of the provided model directly by name, as well as some further properties, like model and index.


ListView {
    model: root.myModel
    delegate: Text {
        id: delegate
        color: index % 2 ? "gray" : "black"
        text: description
    }
}

And if your delegates do not contain required properties, nothing changes here. However, if they contain at least one required property, those names are not accessible anymore. Instead, you have to explicitly opt in by specifying them as required properties.


ListView {
    model: root.myModel
    delegate: Text {
        id: delegate
        required property int index
        required property string description
        color: index % 2 ? "gray" : "black"
        text: description
    }
}

The QML engine will then set the required properties accordingly. Note that there is one significant difference between the new approach and the old one in case your model was editable: With the old approach, you could write


Text {
    id: delegate
    Component.onCompleted: description = "My fancy new text"
}

and the model would have been updated accordingly. But if you do


Text {
    id: delegate
    required property string description
    Component.onCompleted: delegate.description = "My fancy new text"
}

then the binding to description gets broken (and the QML engine will print a warning), and the model will not be updated. We decided on this behavior to ensure that components do not behave too differently when used in delegates and when used outsied of them. Furthermore, we did not want not encourage anyone to do imperative assignments to properties (as this breaks bindings in general).
If you want to actually update the model value, there is of course still a way to achieve this: Make model a required property and write


Component.onCompleted: model.description= "My fancy new text"

We recommend that you always use required properties in delegates. This avoids unqualified lookups, which are problematic for tooling, and tend to be slower.

Inline Components

Another new feature in 5.15 are inline components. As the name suggests, they allow you to define a new component inside a file. The basic syntax is


  component <component name> : BaseType {
    // declare properties and bindings here
}
 

Inside the file, you can then refer to the new component by its name, just like if it were defined in its own file. Let's consider a LabeledImage component as an example to show how this works:


// Images.qml
import QtQuick 2.15

Item {
    component LabeledImage: Column {
        property alias source: image.source
        property alias caption: text.text

        Image {
            id: image
            width: 50
            height: 50
        }
        Text {
            id: text
            font.bold: true
        }
    }

    Row {
        LabeledImage {
            id: before
            source: "before.png"
            caption: "Before"
        }
        LabeledImage {
            id: after
            source: "after.png"
            caption: "After"
        }
    }
    property LabeledImage selectedImage: before
}

It is also possible to refer to the component in other files. In that case you need to prefix its name by the name of the containing component:


// LabeledImageBox.qml
import QtQuick 2.15

Rectangle {
    property alias caption: image.caption
    property alias source: image.source
    border.width: 2
    border.color: "black"
    Images.LabeledImage {
        id: image
    }
}

You might wonder at this point why we need inline components when QML already has the Component type. Looking at the previous examples, we can see that inline components allow you to do the following things which Component doesn't:

  • You can create an instance of the component, without the overhead of using a Loader.
  • You can use the component type in property declarations.
  • You can refer to the component in other files than the one it is defined in.

We hope that you find inline components as convenient as we do!

Nullish Coalescing

The last new language feature was implemented by our intern Maximilian Goldstein. While QML generally only supports EcmaScript 6, Max added supported for one upcoming language feature that is currently in the process of being added to the latest EcmaScript standard: nullish coalescing. Quoting MDN:

The nullish coalescing operator (??) is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand.

See the the MDN page for the details. Here's an example how it can be used in QML to set properties from JSON and provide sane defaults in case they were not provided.


Item {
    property var settings
    property int brightness: settings.brightness ?? 100
    property color color: settings.color ?? "blue"
    Component.onCompleted: settings = JSON.parse(settingsString)
}

Note that we couldn't have used || instead of ?? for brightness, as settings.brightness might have been 0, in which case we would have gotten the default value.

Looking forward

With Qt 6 on the horizon, many more changes are bound to land in QML. Besides revamping the internals of the QML engine, we want to leverage static typing to both generate faster code (including compilation to C++) and to improve our tooling. Moreover, while we focus on those large topics for the initial 6.0 release, we keep an open mind regarding small quality of life improvements: optional chaining or adding support for the fetch API are but two examples for feature requests from the community which we consider for the larger 6.x timeline (though not the initial 6.0 release). Do you have a feature that you want to see in QML? Feel free to post a comment, or even better, create a suggestion in our bugtracker. The future is written with Qt!


Blog Topics:

Comments