Динамическое создание объектов QML из JavaScript

Добавлено 11 мая 2022 в 04:12

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

Для демонстрации концепций, обсуждаемых в этом разделе странице, посмотрите пример Dynamic Scene example.

Динамическое создание объектов

Есть два способа динамического создания объектов из 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 в эту функцию.
  • Второй является необязательным и представляет собой словарь (map) пар свойство-значение, которые определяют начальные значения любых свойств объекта. Значения свойств, указанные этим аргументом, применяются к объекту до того, как его создание будет завершено, что позволяет избежать ошибок привязки, которые могут возникнуть, если необходимо инициализировать определенные свойства, чтобы включить привязки других свойств. Кроме того, в этом есть небольшой выигрыш в производительности по сравнению с определением значений свойств и привязок после создания объекта.

Вот пример. Во-первых, это Sprite.qml, который определяет простой компонент QML:

import QtQuick 2.0

Rectangle { width: 80; height: 50; color: "red" }

Наш основной файл приложения, main.qml, импортирует файл JavaScript componentCreation.js, который будет создавать объекты Sprite:

import QtQuick 2.0
import "componentCreation.js" as MyScript

Rectangle {
    id: appWindow
    width: 300; height: 300

    Component.onCompleted: MyScript.createSpriteObjects();
}

Вот componentCreation.js. Обратите внимание, что перед вызовом createObject() он проверяет, является ли status компонента Component.Ready, на случае, если файл 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) {
            // Обработка ошибок
            console.log("Error creating object");
        }
    } else if (component.status == Component.Error) {
        // Обработка ошибок
        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) {
        // Обработка ошибок
        console.log("Error creating object");
    }
}

Обратите внимание, что в обоих случаях createObject() вызывается с appWindow, переданным в качестве аргумента родителя, поскольку динамически создаваемый объект является визуальным (Qt Quick) объектом. Созданный объект станет потомком объекта appWindow в main.qml и появится на сцене.

При использовании файлов с относительными путями путь должен указываться относительно файла, в котором выполняется Qt.createComponent().

Для подключения сигналов к динамически создаваемым объектам (или получения сигналов от них) используйте метод сигнала connect(). Для получения дополнительной информации смотрите раздел «Подключение сигналов к методам и сигналам».

Также можно создавать экземпляры компонентов без блокировки с помощью функции incubateObject().

Создание объекта из строки QML

Если QML не определен до времени выполнения, вы можете создать объект QML из строки QML с помощью функции Qt.createQmlObject(), как в следующем примере:

const newObject = Qt.createQmlObject(`
    import QtQuick 2.0

    Rectangle {
        color: "red"
        width: 20
        height: 20
    }
    `,
    parentItem,
    "myDynamicSnippet"
);

Первый аргумент – это строка 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() аналогичным образом:

const newObject = Qt.createQmlObject(`
    import QtQuick 2.0

    Rectangle {
        color: "red"
        width: 20
        height: 20
    }
    `,
    parentItem,
    "myDynamicSnippet"
);
newObject.destroy(1000);

Теги

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

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

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