📜 ⬆️ ⬇️

Using Loader in QML

Good day! In this article I will talk about such a component from QML as a Loader.

It allows you to create a container in which you can then attach the necessary qml element, use different elements depending on the program state, and also make rarely used parts downloadable on demand and save resources. Loader is a container for a QML component and is not displayed by itself.

I am considering a component from QtQuick 2.0 which is included in Qt of the fifth version. In the earlier version, this component is also available, but the functionality is slightly less.
')
Setting Content for Loader

As the content of the Loader, you can specify either the path to the qml file or a component. Consider these methods in more detail.

1. Path to the qml file

The path is set via the source parameter. This can be either the path to the local file or the file url from the network. The path can also be both absolute and relative.

Consider an example that changes the background by clicking the mouse in the window.

main.qml:
import QtQuick 2.0 Item { id: main property int backgroundNumber: 1 width: 360 height: 360 Loader { id: background anchors.fill: parent source: "Background_1.qml" } MouseArea { anchors.fill: parent onClicked: { background.source = (backgroundNumber == 1 ? "Background_2.qml" : "Background_1.qml") backgroundNumber = (backgroundNumber == 1 ? 2 : 1) } } } 

Background_1.qml
 import QtQuick 2.0 Rectangle { color: "black" } 

Background_2 .qml:
 import QtQuick 2.0 Rectangle { color: "yellow" } 


This method has one advantage. Before creating an object from a qml component, the qml engine must first compile it. If we do not install source, nothing will be compiled, respectively, for objects that are not always needed and are not created by default, this method is best suited.

2. Component

You can define a component in the Loader scope and specify it as the sourceComponent property. Since we define a component, the qml engine will compile it, even if we have never created an object from it for the entire duration of the program, which is a drawback. At the same time, since the component is already compiled, you only need to create an object, which is faster. If you often need to switch states between different components, then this method is well suited. At the same time, you do not need to download the component file, which can be another tangible advantage if the component is downloaded not from the local file on disk, but is downloaded from the network.

Another feature is that the component is defined in the context of this qml-file, which means that its contents will contain the contents of the file and it can access the properties and functions of objects from this file.

main.qml:
 import QtQuick 2.0 Item { id: main property int backgroundNumber: 1 property color backgroundColor1: "black" property color backgroundColor2: "yellow" width: 360 height: 360 Loader { id: background anchors.fill: parent sourceComponent: background_1 } MouseArea { anchors.fill: parent onClicked: { background.sourceComponent = (backgroundNumber == 1 ? background_2 : background_1) backgroundNumber = (backgroundNumber == 1 ? 2 : 1) } } Component { id: background_1 Rectangle { color: main.backgroundColor1 } } Component { id: background_2 Rectangle { color: main.backgroundColor2 } } } 


In this example, the parent component (main) and its properties are visible from the background_1 and background_2 components.

3. setSource ()

Describing an object in qml, you can set its initial parameters, as well as use bindings. Downloading a component using the Loader in the two ways listed above cannot be done.

In QtQuick 2.0 (Qt 5.0), the setSource method has been introduced. It allows you to set the properties of the object being created. To do this, you need to pass a regular JavaScript object with the necessary parameters as the second argument to the function.

As an example, set the opacity, i.e. transparency (more precisely, opacity).

main.qml:
 import QtQuick 2.0 Item { id: main property int backgroundNumber: 1 width: 360 height: 360 Loader { id: background anchors.fill: parent Component.onCompleted: setSource("Background_1.qml", { "opacity": 0.5 }) } MouseArea { anchors.fill: parent onClicked: { background.setSource(backgroundNumber == 1 ? "Background_2.qml" : "Background_1.qml", { "opacity": 0.5 }) backgroundNumber = (backgroundNumber == 1 ? 2 : 1) } } } 


Synchronous and asynchronous loading

By default, components that set the source path to the local path to the file on disk are loaded synchronously. If you specify the url of the file from the network, the status changes to Loader.Loading and the download is asynchronous.

Asynchronous loading can also be activated manually by setting the asynchronous property to true.

If the download time can be significant, it can be useful information about the progress of the download. The progress property will display how loaded the component is (from 0 to 1).

Access to the object

The created object can be accessed using the item property. Thus, you can access the properties of the object (if the object was not created using setSource, then this is the only way to set the desired properties for the object).

You can not only set properties for an object, but also attach handlers to its signals and call its methods. The only thing is to wait until the component is loaded. You need to wait until the status parameter is equal to Loader.Ready, or put the handler on the loaded signal.

Consider a small example.

main.qml:
 import QtQuick 2.0 Item { id: main width: 360 height: 360 Loader { id: background anchors.fill: parent asynchronous: true source: "Background_1.qml" onStatusChanged: console.log("status", status, "item", item) onLoaded: item.color = "green" } } 


Here is what we get as output:
status 2 item null
status 2 item null
status 1 item QQuickRectangle(0x95436f8)

The number 2 corresponds to the value of Loader.Loading, 1 - Loader.Ready. You can also see that item points to the created object when the object has finished loading. After the download is complete, the background color changes to green.

Work with signals

In order to set a handler for the signal of an object loaded into the Loader, call the connect () method of this signal and pass the handler function as a parameter. The handler can be removed using the corresponding disconnect () method.

Consider an example.

main.qml:
 import QtQuick 2.0 Item { id: main width: 360 height: 360 Loader { id: container anchors.fill: parent source: "Mouse.qml" onLoaded: item.mouseClicked.connect(processMouse) Component.onDestruction: { if (item) { item.mouseClicked.disconnect(processMouse) } } } function processMouse() { console.log("mouse clicked!") } } 


Mouse.qml:
 import QtQuick 2.0 Item { id: m signal mouseClicked() MouseArea { anchors.fill: parent onClicked: m.mouseClicked() } } 

Clicking on the window will display the text in the console: “mouse clicked!”

This method is powerful and allows you to install / remove handlers depending on the state of the program. The price for this is increasing complexity, amount of code, and less reliability. When manual control of signals is not necessary, we can use the Connections qml component. We specify item as the target property and set handlers as if we defined them in the element itself.

main.qml:
 import QtQuick 2.0 Item { id: main width: 360 height: 360 Loader { id: container anchors.fill: parent source: "Mouse.qml" } Connections { target: container.item onMouseClicked: processMouse() } function processMouse() { console.log("mouse clicked!") } } 


As you can see, this method is easier, more convenient and less prone to errors. Moreover, this method is made in a declarative style and is more natural for qml, in contrast to the imperative one, when we connect the signals with the corresponding handlers manually.

About declarative and imperative approaches

In the examples above, there is nothing particularly complicated, but if you need to interconnect objects that are loaded asynchronously, this can be nontrivial. You should not expect that the qml engine loads them in a certain sequence, especially if they are loaded over the network.

By experience, if it is possible to use declarative methods, then it is better to use them. This applies not only to signals, but also to the properties of objects that can be set using setSource, instead of waiting for the component to load and set them manually via the item property (in Qt, the fourth version of this method does not exist, it’s just that).

Loading non-visual elements

In QtQuick 2.0, Loader added the ability to load not only visual elements. For example, you can thus load an element based on QtObject.

main.qml:
 import QtQuick 2.0 Item { id: main width: 360 height: 360 Loader { id: container anchors.fill: parent source: "Element.qml" onLoaded: console.log(item.text) } } 


Element.qml:
 import QtQuick 2.0 QtObject { property string text: "hello!" } 


When loading, “hello!” Will be displayed in the console.

Small summary

Loader can either download qml components from other files or use components defined in the current qml file area. Depending on the method chosen, it is possible to postpone a part of the creation process once and thereby accelerate the creation of objects or to postpone all operations to create an object until it is needed.

Although qml is a declarative language, it is possible to do many things imperatively, which may be useful in some situations, although at the cost of some complication and an increase in the amount of code. In the case of a non-latest version of Qt, this method can help bypass the limitations of the library.

Source: https://habr.com/ru/post/172509/


All Articles