Динамическое создание QML-объектов из JavaScript
QML поддерживает динамическое создание объектов из JavaScript. Это полезно для отсрочки инициализации объектов до необходимости, тем самым улучшая время запуска приложения. Также это позволяет динамически создавать визуальные объекты и добавлять их в сцену в ответ на пользовательский ввод или другие события.
См. пример динамической сцены для демонстрации обсуждаемых на этой странице концепций.
Динамическое создание объектов
Существует два способа динамического создания объектов из JavaScript. Вы можете вызвать Qt.createComponent() для динамического создания объекта Component или использовать Qt.createQmlObject() для создания объекта из строки QML. Создание компонента предпочтительнее, если у вас уже определен компонент в QML-документе и вы хотите динамически создавать экземпляры этого компонента. В противном случае, создание объекта из строки QML полезно, когда сам QML-объект генерируется во время выполнения.
Динамическое создание компонента
Для динамической загрузки компонента, определенного в QML-файле, вызовите функцию Qt.createComponent() в объекте Qt. Эта функция принимает в качестве единственного аргумента URL QML-файла и создает объект Component из этого URL.
После получения объекта Component, вы можете вызвать его метод createObject() для создания экземпляра компонента. Эта функция может принимать один или два аргумента:
- Первый — родительский объект для нового объекта. Родительским объектом может быть графический объект (например, типа Item) или неграфический объект (например, типа QtObject или C++ QObject). Только графические объекты с графическими родительскими объектами будут отрисованы на визуальном холсте Qt Quick. Если вы хотите установить родителя позже, вы можете безопасно передать
nullв эту функцию. - Второй — необязательный аргумент, представляющий собой карту пар «свойство-значение», определяющих начальные значения свойств объекта. Значения свойств, заданные этим аргументом, применяются к объекту до завершения его создания, избегая ошибок привязки, которые могут возникнуть, если определённые свойства должны быть инициализированы для активации других привязок свойств. Кроме того, существует небольшая выгода в производительности по сравнению с определением значений свойств и привязок после создания объекта.
Вот пример. Сначала у нас есть Sprite.qml, который определяет простой QML-компонент:
import QtQuick 2.0
Rectangle { width: 80; height: 50; color: "red" } Наш основной файл приложения, main.qml, импортирует componentCreation.js JavaScript-файл, который будет создавать Sprite объекты:
import QtQuick 2.0
import "componentCreation.js" as MyScript
Rectangle {
id: appWindow
width: 300; height: 300
Component.onCompleted: MyScript.createSpriteObjects();
} Вот componentCreation.js. Обратите внимание, что он проверяет, является ли состояние компонента status Component.Ready перед вызовом createObject(), на случай, если QML-файл загружается через сеть и поэтому не готов сразу.
var component;
var sprite;
function createSpriteObjects() {
component = Qt.createComponent("Sprite.qml");
if (component.status == Component.Ready)
finishCreation();
else
component.statusChanged.connect(finishCreation);
}
function finishCreation() {
if (component.status == Component.Ready) {
sprite = component.createObject(appWindow, {x: 100, y: 100});
if (sprite == null) {
// Error Handling
console.log("Error creating object");
}
} else if (component.status == Component.Error) {
// Error Handling
console.log("Error loading component:", component.errorString());
}
} Если вы уверены, что загружаемый QML-файл — это локальный файл, вы можете опустить функцию finishCreation() и вызвать createObject() сразу:
function createSpriteObjects() {
component = Qt.createComponent("Sprite.qml");
sprite = component.createObject(appWindow, {x: 100, y: 100});
if (sprite == null) {
// Error Handling
console.log("Error creating object");
}
} Обратите внимание, что в обоих случаях createObject() вызывается с appWindow в качестве аргумента родителя, так как динамически создаваемый объект — это визуальный (Qt Quick) объект. Созданный объект станет дочерним объектом объекта appWindow в main.qml, и отобразится в сцене.
При использовании файлов с относительными путями путь должен быть относительным к файлу, где выполняется Qt.createComponent().
Для подключения сигналов к (или получения сигналов от) динамически созданных объектов используйте метод сигнала connect(). См. Подключение сигналов к методам и сигналам для получения дополнительной информации.
Также возможно создание экземпляров компонентов без блокировки с помощью функции incubateObject().
Создание объекта из строки QML
Если QML не определен до времени выполнения, вы можете создать QML-объект из строки QML с помощью функции Qt.createQmlObject(), как в следующем примере:
var newObject = Qt.createQmlObject('import QtQuick 2.0; Rectangle {color: "red"; width: 20; height: 20}',
parentItem,
"dynamicSnippet1"); Первый аргумент — строка QML для создания. Как и в новом файле, вам нужно импортировать любые типы, которые вы хотите использовать. Второй аргумент — родительский объект для нового объекта, и семантика аргумента родителя, которая применяется к компонентам, аналогично применима к createQmlObject(). Третий аргумент — путь к файлу, ассоциируемому с новым объектом; он используется для отчёта об ошибках.
Если строка QML импортирует файлы с использованием относительных путей, путь должен быть относительным к файлу, в котором определен родительский объект (второй аргумент метода).
Важно: При построении статических QML-приложений QML-файлы сканируются для обнаружения зависимостей импорта. Таким образом, все необходимые плагины и ресурсы разрешаются во время компиляции. Однако учитываются только явные операторы импорта (те, которые находятся в начале QML-файла), а не операторы импорта, заключённые в строковые литералы. Чтобы поддерживать статическую сборку, вам необходимо убедиться, что QML-файлы, использующие Qt.createQmlObject(), явно содержат все необходимые импорты в начале файла в дополнение к строковым литералам.
Поддержание динамически созданных объектов
При управлении динамически созданными объектами необходимо убедиться, что контекст создания существует дольше, чем созданный объект. В противном случае, если контекст создания будет уничтожен раньше, привязки и обработчики сигналов в динамическом объекте больше не будут работать.
Фактический контекст создания зависит от способа создания объекта:
- Если используется Qt.createComponent(), контекст создания — это QQmlContext, в котором вызвался этот метод.
- Если вызван Qt.createQmlObject(), контекст создания — это контекст родительского объекта, переданного в этот метод.
- Если определен объект
Component{}, и вызван createObject() или incubateObject() на этом объекте, контекст создания — это контекст, в котором определенComponent
Также обратите внимание, что хотя динамически созданные объекты могут использоваться так же, как и другие объекты, у них нет id в QML.
Удаление объектов динамически
Во многих пользовательских интерфейсах достаточно установить непрозрачность визуального объекта в 0 или переместить визуальный объект за пределы экрана вместо удаления. Однако, если у вас много динамически созданных объектов, вы можете получить значительную выгоду в производительности, если удалять неиспользуемые объекты.
Обратите внимание, что вы никогда не должны вручную удалять объекты, которые были динамически созданы удобными фабриками QML-объектов (такими как Loader и Repeater). Также следует избегать удаления объектов, которые вы не создавали динамически.
Объекты можно удалить, используя метод destroy(). Этот метод имеет необязательный аргумент (по умолчанию 0), который указывает приблизительную задержку в миллисекундах перед уничтожением объекта.
Вот пример. application.qml создает пять экземпляров компонента SelfDestroyingRect.qml. Каждый экземпляр выполняет NumberAnimation, и когда анимация завершается, вызывает destroy() на своем корневом объекте, чтобы уничтожить себя:
application.qml |
import QtQuick 2.0
Item {
id: container
width: 500; height: 100
Component.onCompleted: {
var component = Qt.createComponent("SelfDestroyingRect.qml");
for (var i=0; i<5; i++) {
var object = component.createObject(container);
object.x = (object.width + 10) * i;
}
}
} |
SelfDestroyingRect.qml |
import QtQuick 2.0
Rectangle {
id: rect
width: 80; height: 80
color: "red"
NumberAnimation on opacity {
to: 0
duration: 1000
onRunningChanged: {
if (!running) {
console.log("Destroying...")
rect.destroy();
}
}
}
} |
В качестве альтернативы application.qml мог бы уничтожить созданный объект, вызвав object.destroy().
Обратите внимание, что безопасно вызывать destroy() на объекте внутри этого объекта. Объекты не уничтожаются мгновенно после вызова destroy(), а очищаются в какой-то момент между окончанием этого блока скрипта и следующим кадром (если не была указана задержка отличная от нуля).
Также обратите внимание, что если экземпляр SelfDestroyingRect был создан статически, как это:
Item {
SelfDestroyingRect {
// ...
}
} Это приведет к ошибке, поскольку объекты могут быть уничтожены динамически только в том случае, если они были созданы динамически.
Объекты, созданные с помощью Qt.createQmlObject(), также могут быть уничтожены с помощью destroy():
var newObject = Qt.createQmlObject('import QtQuick 2.0; Rectangle {color: "red"; width: 20; height: 20}',
parentItem,
"dynamicSnippet1");
newObject.destroy(1000);
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.13/qtqml-javascript-dynamicobjectcreation.html