Skip to content
Kimmo edited this page May 29, 2018 · 10 revisions

Components: Properties, Signals (P0 | 4h)

Explanation of the contents of a topic page @ Topic reference page

Back to Week 2

Objective: Creating components and component interfaces

NB! Topic 2.12 was omitted as a topic of it's own and included in this topic. Added the two hours from there to here

Beginner

  • What does a QML file define?

Intermediate

  • What four rules define your property scope (included from 2.12)?
  • How can I instantiate a type that has been defined in a QML file?
  • How do you define a component API? Done?
  • What is a visibility of an item id? Done?
  • When to use alias with a property? Done?
  • When to use readonly with a property? Done
  • when to use default with a property? Done
  • How to define a private API?

Comment: How can I instantiate a type, defined in a QML file (must be in the same folder or imported), How to define a component API? (properties in the root are visible, necessary child properties must be exported with the property keyword), What is a visibility of an item id? When to use alias, readonly, default properties? How to define a private API (properties with QtObject)?

*K: This is very WIP still, very verbose and I am not sure if this walkthrough thing is interesting or not. Hard to explain without showing concrete things. Maybe a smaller use case would be better or less verbose code.

Expert

Omitted


Course material content

Comment: What does q QML file define? (Answer a type AKA component)

QML Components

We have been previously using components in the material and exercises, but have not gone through how and why components are used in general. Component is a instantiable QML object, a QML type. It is typically contained inside its own .qml file. We have imported types from QtQuick 2.0 library with the directive import QtQuick 2.0. If the component's .qml is in the same directory it does not need to be imported.

For example, a Button component is defined in a file Button.qml. We can instantiate this Button component to create Button objects without importing it inside main.qml.

The Button definition may contain other components. The Button component could use a Text element, a MouseArea and other elements to implement the internal functions. This compounding of different components to form new components (and effectively new interfaces) is key in good, reusable UI components.

Let's walk through an example of a color picker for choosing the color of a text element. Our picker is currently made of four cells with different colors. A very naive approach is to write multiple Items with Rectangle and MouseArea items inside the main application.

Rectangle {
    id: page
    width: 500
    height: 200
    color: "black"

    Text {
        id: helloText
        text: "Hello world!"
        y: 30
        anchors.horizontalCenter: page.horizontalCenter
    }

    Grid {
        id: colorPicker
        x: 4; anchors.bottom: page.bottom; anchors.bottomMargin: 4
        rows: 2; columns: 3; spacing: 3

        Item {
            width: 40
            height: 25

            Rectangle {
                id: rectangle
                color: "red"
                anchors.fill: parent
            }

            MouseArea {
                anchors.fill: parent
                onClicked: helloText.color = "red"
           }
       }
       
       // Imagine more duplicate lines of Items with different color values defined ...
    }
}

Smart people will probably notice that we have lots of duplicate code here! Additionally, to add more colors, we need to define more elements with just the color values changed. We also depend on the helloText item id. To avoid writing the same code over and over again for each color, we can extract a Cell component from the duplicate code to Cell.qml.

Here is our component definition with Rectangle and MouseArea items extracted:

Item {
     id: container
     width: 40; height: 25

     Rectangle {
         id: rectangle
         color: "red"
         anchors.fill: parent
    }

     MouseArea {
         anchors.fill: parent
         onClicked: console.log("clicked?")
     }
}

Usually, components are defined with the Item type as the visual root item. Item is a lightweight basic type and it is not rendered itself. A component may also be defined inside a Component item. Now we have insatiable component, but currently it is a component without outside effects and it has a hardcoded color value. This is not very reusable, isn't it? How to make it reusable and interact with other items?

Tino: Why Item? It should be noted that it's a good candidate for a visual item root, as it's lightweight, as it's not rendered itself.

We cannot access the text property of our Text item helloText inside our component and we cannot access the rectangle.color property outside of the Cell component. To do this, we need to expose an interface for other components to use. We can use the property keyword alias and the attribute signal to expose functionality and export properties.

Item {
     id: container

     property alias cellColor: rectangle.color
     signal clicked(color cellColor)

     width: 40; height: 25

     Rectangle {
         id: rectangle
         anchors.fill: parent
     }

     MouseArea {
         anchors.fill: parent
         onClicked: container.clicked(container.cellColor)
     }
}

Now we can write to the rectangle.color property outside from the component with the property cellColor, and we can install a signal handler for the clicked signal with the onClicked property:

Cell {
    cellColor: "red"
    onClicked: helloText.color = cellColor
}

Now we can easily add more colors with just the Cell definition:

Rectangle {
     id: page
     width: 500
     height: 200
     color: "gray"

     Text {
         id: helloText
         text: "Hello world!"
         y: 30
         anchors.horizontalCenter: page.horizontalCenter
     }

     Grid {
         id: colorPicker
         x: 4; anchors.bottom: page.bottom; anchors.bottomMargin: 4
         rows: 2; columns: 2; spacing: 3

         Cell { cellColor: "red"; onClicked: helloText.color = cellColor }
         Cell { cellColor: "green"; onClicked: helloText.color = cellColor }
         Cell { cellColor: "blue"; onClicked: helloText.color = cellColor }
         Cell { cellColor: "yellow"; onClicked: helloText.color = cellColor }
     }
}

Default properties

K: now for something completely different... need to bridge this with something

Tino: just the sub-title: Default properties. The context is anyway the component interface and properties.

Similar to the automatic data property assignment for all declared children objects, those objects can be assigned to a specific property with the default keyword. This is less verbose than explicitly assigning children to a property and makes the use of the component more intuitive

For example, we can have a Button type in which we want to assign arbitrary objects as the contents. We define a placeholder item and alias the placeholder.data property:

Item {
    id: button
    anchors.fill: parent
    default property alias content: placeholder.data

    Rectangle {
        anchors.fill: parent
        radius: 20
        color: "gray"

        Item {
            id: placeholder
        }
    }
}

With the default keyword, objects can be assigned to the default content property without specifying the content keyword explicitly:

Label {
    Image {
        source: "https://doc.qt.io/qt-5/images/declarative-qtlogo.png"
    }
}

Very neat.

Private properties

Exposing properties is fun and all, but what to do when you want to hide functionality? QML does not have private properties like in C++, but similar effect can be achieved with Qt Quick component QtObject. This encapsulates component properties and functions from outside access.

Item {
    property int radius: internal.a() + internal.b
    QtObject {
        id: internal
        function a() {
            return 42
        }

        property int b: 2
    }
}

Now we have a private implementation with just the radius attribute exported.

Read-only properties

The property implementation can be made read-only with the readonly keyword:

Item {
    property int radius: internal.a() + internal.b
    QtObject {
        id: internal
        function a() {
            return 42
        }

        property int b: 2
    }
}

This can be used to just expose internal state like focus, activity state or just colors that the user does not need to change.

Instructions and description for the exercise of the topic


Exhaustive reference material mentioned in this topic

Further reading topics/links:

Clone this wiki locally