-
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 Item
s 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.