-
Notifications
You must be signed in to change notification settings - Fork 0
2.08
Explanation of the contents of a topic page @ Topic reference page
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
- What does a QML file define?
- 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.
Comment: What does q QML file define? (Answer a type AKA component)
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 }
}
}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.
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.
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.