Создание и уничтожение объектов

Добавлено 18 мая 2022 в 05:17

Элемент Loader позволяет динамически заполнять часть пользовательского интерфейса. Однако общая структура интерфейса по-прежнему статична. С помощью JavaScript можно сделать еще один шаг и полностью динамически создавать экземпляры элементов QML.

Прежде чем мы углубимся в детали динамического создания элементов, нам нужно понять рабочий процесс. При загрузке фрагмента QML из файла или даже через интернет создается компонент. Этот компонент инкапсулирует интерпретируемый код QML и может использоваться для создания элементов. Это означает, что загрузка фрагмента кода QML и создание экземпляров из него – это двухэтапный процесс. Сначала код QML разбирается на компонент. Затем этот компонент используется для создания экземпляров реальных объектов элементов.

Помимо создания элементов из кода QML, хранящегося в файлах или на серверах, также возможно создавать объекты QML непосредственно из текстовых строк, содержащих код QML. Затем эти динамически созданные элементы после их создания обрабатываются аналогичным образом.

Динамическая загрузка и создание экземпляров элементов

При загрузке фрагмента QML он сначала интерпретируется как компонент. Это включает в себя загрузку зависимостей и проверку кода. Местоположение загружаемого QML может быть локальным файлом, ресурсом Qt или даже удаленным сетевым расположением, указанным URL-адресом. Это означает, что время загрузки может быть любым: от мгновенного, например, ресурса Qt, расположенного в ОЗУ без каких-либо незагруженных зависимостей, до очень долгого, то есть фрагмента кода, расположенного на медленном сервере с несколькими зависимостями, которые необходимо загрузить.

Статус создаваемого компонента можно отслеживать по его свойству status. Доступные значения для этого свойства: Component.Null, Component.Loading, Component.Ready и Component.Error. Обычный путь: NullLoadingReady. На любом этапе status может измениться на Error. В этом случае компонент нельзя использовать для создания новых экземпляров объектов. Для получения понятного пользователю описания ошибки можно использовать функцию Component.errorString().

При загрузке компонентов через медленное соединение может быть полезно свойство progress. Оно варьируется от 0.0, что означает, что ничего не загружено, до 1.0, что означает, что всё загружено. Когда status компонента изменится на Ready, компонент можно использовать для создания экземпляров объектов. В приведенном ниже коде показано, как этого можно достичь, принимая во внимание событие, когда компонент становится готовым или не может быть создан напрямую, а также случай, когда компонент готов чуть позже.

var component;

function createImageObject() {
    component = Qt.createComponent("dynamic-image.qml");
    if (component.status === Component.Ready || component.status === Component.Error) {
        finishCreation();
    } else {
        component.statusChanged.connect(finishCreation);
    }
}

function finishCreation() {
    if (component.status === Component.Ready) {
        var image = component.createObject(root, {"x": 100, "y": 100});
        if (image === null) {
            console.log("Error creating image");
        }
    } else if (component.status === Component.Error) {
        console.log("Error loading component:", component.errorString());
    }
}

Приведенный выше код хранится в отдельном исходном файле JavaScript, на который ссылается основной файл QML.

import QtQuick
import "create-component.js" as ImageCreator

Item {
    id: root

    width: 1024
    height: 600

    Component.onCompleted: ImageCreator.createImageObject();
}

Функция createObject компонента используется для создания экземпляров объекта, как показано выше. Это относится не только к динамически загружаемым компонентам, но и к элементам Component, встроенным в код QML. Полученный объект можно использовать в сцене QML, как и любой другой объект. Разница лишь в том, что у него нет id.

Функция createObject принимает два аргумента. Первый – это родительский объект типа Item. Второй – это список свойств и значений в формате {"имя": значение, "имя": значение}. Это продемонстрировано в примере ниже. Обратите внимание, что аргумент свойств является необязательным.

var image = component.createObject(root, {"x": 100, "y": 100});

Подсказка

Динамически созданный экземпляр компонента не отличается от встроенного (inline) элемента Component. Встроенный элемент Component также предоставляет функции для динамического создания экземпляров объектов.

Инкубация компонентов

Когда компоненты создаются с помощью createObject, создание объектного компонента блокируется. Это означает, что создание сложного элемента может заблокировать основной поток, что вызовет видимый сбой. В качестве альтернативы, сложные компоненты могут быть разбиты на части и загружены поэтапно с использованием элементов Loader.

Чтобы решить эту проблему, можно создать экземпляр компонента с помощью метода incubateObject. Он может работать так же, как createObject, и немедленно возвращать экземпляр, или он может использовать обратный вызов (callback), когда компонент будет готов. В зависимости от вашей конкретной ситуации, это может быть или не быть хорошим способом решения сбоев анимации, связанных с созданием экземпляров.

Чтобы использовать инкубатор, просто используйте его как createComponent. Однако возвращаемый объект является инкубатором, а не самим экземпляром объекта. Когда инкубатор находится в состоянии Component.Ready, объект доступен через свойство объекта инкубатора. Всё это показано на примере ниже:

function finishCreate() {
    if (component.status === Component.Ready) {
        var incubator = component.incubateObject(root, {"x": 100, "y": 100});
        if (incubator.status === Component.Ready) {
            var image = incubator.object; // создается сразу
        } else {
            incubator.onStatusChanged = function(status) {
                if (status === Component.Ready) {
                    var image = incubator.object; // создается асинхронно
                }
            };
        }
    }
}

Динамическое создание элементов из текста

Иногда бывает удобно создать экземпляр объекта из текстовой строки QML. По крайней мере, это быстрее, чем помещать код в отдельный исходный файл. Для этого используется функция Qt.createQmlObject.

Эта функция принимает три аргумента: qml, parent и filepath. Аргумент qml содержит строку кода QML для создания экземпляра. Аргумент parent предоставляет родительский объект вновь созданному объекту. Аргумент filepath используется при сообщении о любых ошибках при создании объекта. Результат, возвращаемый функцией, является либо новым объектом, либо значением null.

Предупреждение

Функция createQmlObject всегда возвращает результат немедленно. Для успешного выполнения этой функции должны быть загружены все зависимости вызова. Это означает, что если код, переданный функции, ссылается на незагруженный компонент, вызов завершится ошибкой и вернет значение null. Чтобы лучше справиться с этим, необходимо использовать подход createComponent/createObject.

Объект, созданный с помощью функции Qt.createQmlObject, похож на любой другой динамически создаваемый объект. Это означает, что он идентичен любому другому объекту QML, за исключением отсутствия id. В приведенном ниже примере новый элемент Rectangle создается из встроенного кода QML после создания элемента root.

import QtQuick

Item {
    id: root

    width: 1024
    height: 600

    function createItem() {
        Qt.createQmlObject("import QtQuick 2.5; Rectangle { x: 100; y: 100; width: 100; height: 100; color: \"blue\" }", root, "dynamicItem")
    }

    Component.onCompleted: root.createItem()
}

Управление динамически созданными элементами

Динамически созданные объекты можно рассматривать как любые другие объекты в сцене QML. Однако есть некоторые подводные камни, о которых мы должны знать. Наиболее важной является концепция контекстов создания.

Контекст создания динамически создаваемого объекта – это создаваемый внутри него контекст. Это не обязательно тот же контекст, в котором существует родитель. Когда контекст создания уничтожается, то же самое происходит и с привязками, относящимися к объекту. Это означает, что важно реализовать создание динамических объектов в том месте кода, которое будет существовать в течение всего времени жизни этих объектов.

Динамически созданные объекты также могут быть динамически уничтожены. При этом существует эмпирическое правило: никогда не пытайтесь уничтожить объект, который вы не создали. Сюда также входят элементы, созданные вами, но без использования динамического механизма, такого как Component.createObject или createQmlObject.

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

item = Qt.createQmlObject(...)
...
item.destroy()

Подсказка

Можно уничтожить объект изнутри, что позволяет, например, создавать самоуничтожающиеся всплывающие окна.

Теги

GUI / Графический интерфейс пользователяJavaScriptQMLQtQtQuickПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.