Using Loader in QML

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

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

I am considering a component from QtQuick 2.0 which is included in Qt fifth version. In an earlier version, this component is also there, but the functionality is slightly less.

Setting Content for Loader

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

1. The path to the qml file

The path is specified through 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 on the mouse in the window area.

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 the 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 scope of the Loader and specify it as the sourceComponent property. Since we define a component, the qml engine compiles 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 not downloaded from a local file on disk, but downloaded from the network.

Another feature is that the component is defined in the context of this qml-file, which means that the contents of the file will also be visible 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 ()

When describing an object in qml, you can set its initial parameters, as well as use bindings. When loading a component using Loader in the above two ways, this cannot be done.

QtQuick 2.0 (Qt 5.0) introduced the setSource method. It allows you to set the properties of the created object. 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 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 Download

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

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

If the download time may be significant, information about the download progress may come in handy. The progress property will display how loaded the component is (from 0 to 1).

Object Access

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 processors to its signals and call its methods. The only thing is to wait for the component to load. 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's what we get as output:
status 2 item null
status 2 item null
status 1 item QQuickRectangle(0x95436f8)

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 loading of the object was completed. After the download is complete, the background color will turn green.

Work with signals

In order to set the handler on the signal of the object loaded into Loader, you need to call the connect () method on 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()
    }
}

By clicking on a window in the console the text will be displayed: “mouse clicked!”

This method is powerful and allows you to set / 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 needed, we can use the Connections qml component. As the target property, we specify item and set the 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 simpler, more convenient and less error prone. Moreover, this method is made in a declarative style and is more natural for qml, as opposed to imperative, when we connect the signals to the corresponding handlers manually.

About declarative and imperative approaches

There is nothing particularly complicated in the above examples, but if you need to connect objects that load asynchronously, it can be nontrivial. You should not expect that the qml engine loads them in any particular sequence, especially if they are downloaded over the network.

From 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 properties of objects that can be set using setSource, instead of waiting for the component to load and manually setting them via the item property (in Qt there is no fourth version of this method, it’s only there).

Downloading non-visual items

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.

A small summary

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

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

Also popular now: