Как стать автором
Обновить

Использование Loader в QML

Время на прочтение 6 мин
Количество просмотров 37K
Добрый день! В этой статье я расскажу про такой компонент из QML как Loader.

Он позволяет создать контейнер, в который затем можно вложить необходимый qml-элемент, использовать разные элементы в зависимости от состояния программы, а также сделать редко используемые части загружаемыми по требованию и сэкономить ресурсы. Loader является контейнером для QML-компонента и сам по себе не отображается.

Я рассматриваю компонент из QtQuick 2.0 которая входит в Qt пятой версии. В более ранней версии этот компонент также есть, но функционал немного меньше.

Задание содержимого для Loader

В качестве содержимого Loader можно задать либо путь к qml-файлу либо компонент. Рассмотрим эти способы поподробнее.

1. Путь к qml-файлу

Путь задается через параметр source. Это может быть как путь к локальному файлу, так и url файла из сети. Путь также может быть как абсолютным, так и относительным.

Рассмотрим пример, меняющий фон по клику мышью в области окна.

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"
}


Такой способ имеет одно преимущество. Перед созданием объекта из qml-компонента, движок qml должен сначала его скомпилировать. Если мы не установим source, ничего компилироваться не будет, соответственно для объектов, которые нужны не всегда и по умолчанию не создаются, такой способ подходит лучше всего.

2. Компонент

Можно определить компонент в области видимости Loader и указать его в качестве свойства sourceComponent. Поскольку мы определяем компонент, движок qml его скомпилирует, даже если мы за все время работы программы ни разу не создадим с него объект, что является недостатком. В то же время, поскольку компонент уже скомпилирован, нужно будет только создать объект, что быстрее. Если требуется часто переключать состояния между разными компонентами, то такой способ хорошо подходит. При этом, не нужно загружать файл компонента, что может быть еще одним ощутимым преимуществом, если компонент загружается не из локального файла на диске, а качается из сети.

Еще одной особенностью является то, что компонент определяется в контексте данного qml-файла, а значит в его видимости будет и содержимое файла и он может получить доступ к свойствам и функциям объектов из этого файла.

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
        }
    }
}


В этом примере из компонентов background_1 и background_2 виден родительский компонент (main) и его свойства.

3. setSource()

Описывая объект в qml, можно задать его начальные параметры, а так же использовать привязки (bindings). Загружая компонент при помощи Loader двумя вышеперечисленными способами, сделать этого нельзя.

В QtQuick 2.0 (Qt 5.0) появился метод setSource. Он позволяет задать свойства создаваемого объекта. Для этого, нужно в качестве второго аргумента функции передать обычный JavaScript-объект с необходимыми параметрами.

В качестве примера установим 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)
        }
    }
}


Синхронная и асинхронная загрузка

По умолчанию, компоненты, которым в качестве source задан путь локальный путь к файлу на диске загружаются синхронно. Если же указать url файла из сети, то статус меняется на Loader.Loading и загрузка ведется асинхронно.

Асинхронный режим загрузки можно активировать и вручную, установив свойству asynchronous значение true.

Если время загрузки может быть значительным, может пригодится информация о прогрессе загрузки. Свойство progress будет отображать, насколько загружен компонент (от 0 до 1).

Доступ к объекту

К созданному объекту можно получить доступ, используя свойство item. Таким образом, можно обращаться к свойствам объекта (если объект создавался не при помощи setSource, то это единственный способ задать нужные свойства объекту).

Можно не только задавать свойства объекту но и присоединить к его сигналам обработчики и вызывать его методы. Единственное, стоит дождаться, пока компонент загрузится. Нужно дождаться, пока параметр status не будет равен Loader.Ready либо поставит обработчик на сигнал loaded.

Рассмотрим небольшой пример.

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"
    }
}


Вот, что мы получим в качестве вывода:
status 2 item null
status 2 item null
status 1 item QQuickRectangle(0x95436f8)

Число 2 соответствует значению Loader.Loading, 1 — Loader.Ready. Так же видно, что item указывает на созданный объект, когда завершилась загрузка объекта. После завершения загрузки цвет фона изменится на зеленый.

Работа с сигналами

Для того, чтобы установить обработчик на сигнал объекта, загруженного в Loader, нужно вызвать метод connect() у этого сигнала и в качестве параметра передать-функцию обработчик. Обработчик можно убрать при помощи соответствующего метода disconnect().

Рассмотрим пример.

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()
    }
}


По клику на окне в консоли будет выводиться текст: «mouse clicked!»

Этот способ является мощным и позволяет устанавливать/убирать обработчики в зависимости от состояния программы. Ценой за это являются возрастающие сложность, количество кода и меньшая надежность. Когда ручное управление сигналами не нужно, мы можем использовать qml-компонент Connections. В качестве свойства target мы указываем item и устанавливаем обработчики так, как если бы мы их определяли в самом элементе.

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!")
    }
}


Как видим, этот способ проще, удобнее и менее склонен к ошибкам. Более того, такой способ сделан в декларативном стиле и является более естественным для qml, в отличие от императивного, когда мы соединяем сигналы с соответствующими обработчиками вручную.

Про декларативный и императивный подходы

В приведенных выше примерах ничего особо сложного нет, но если нужно соединять между собой объекты, загружающиеся асинхронно, то это может быть нетривиально. Не стоит рассчитывать, что движок qml загружает их в какой-то определенной последовательности, особенно, если они загружаются по сети.

По опыту, если есть возможность использовать декларативные методы, то лучше использовать именно их. Это касается не только сигналов, но и свойств объектов, которые можно устанавливать при помощи setSource, вместо того, чтобы дожидаться загрузки компонента и устанавливать их вручную через свойство item (в Qt четвертой версии этого метода нет, там только так).

Загрузка невизуальных элементов

В QtQuick 2.0 в Loader добавили возможность загружать не только визуальные элементы. Например, можно таким образом загрузить элемент, основанные на 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!"
}


При загрузке, в консоль выведется «hello!».

Небольшое резюме

Loader может как загружать qml-компоненты из других файлов, так и использовать компоненты, определенные в области текущего qml-файла. В зависимости от выбранного метода, можно отложить выполнить часть процесса создания однократно и тем самым ускорить создание объектов либо отложить все операции по созданию объекта до тех пор, пока он не понадобится.

Несмотря на то, что qml является декларативным языком, есть возможность делать многие вещи императивно, что может в отдельных ситуациях оказаться полезным, хотя и ценой некоторого усложнения и увеличения количества кода. В случае с не самой последней версией Qt такой способ может помочь обойти ограничения библиотеки.
Теги:
Хабы:
+25
Комментарии 10
Комментарии Комментарии 10

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн