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

Добавлено 17 мая 2022 в 23:32

Самый простой способ динамически загружать различные части QML – использовать элемент загрузчик, Loader. Он служит заполнителем для загружаемого элемента. Загружаемый элемент управляется либо через свойство source, либо через свойство sourceComponent. Первый загружает элемент с заданного URL, а второй создает экземпляр компонента.

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

Пример, описанный ниже, демонстрирует, как две отдельные части пользовательского интерфейса могут быть загружены в одно и то же пространство с помощью элемента Loader. Идея состоит в том, чтобы иметь спидометр, который может быть как цифровым, так и аналоговым, как показано на рисунке ниже. Код вокруг спидометра не зависит от того, какой элемент загружен в данный момент.

аналоговый спидометр
цифровой спидометр

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

Loader {
    id: dialLoader

    anchors.fill: parent
}

В свойстве states родителя dialLoader набор элементов PropertyChanges управляет загрузкой различных файлов QML в зависимости от состояния. Свойство source в этом примере представляет относительный путь к файлу, но оно также может быть полным URL, извлекающим элемент из Интернета.

states: [
    State {
        name: "analog"
        PropertyChanges { target: analogButton; color: "green"; }
        PropertyChanges { target: dialLoader; source: "Analog.qml"; }
    },
    State {
        name: "digital"
        PropertyChanges { target: digitalButton; color: "green"; }
        PropertyChanges { target: dialLoader; source: "Digital.qml"; }
    }
]

Чтобы загруженный элемент ожил, его свойство speed должно быть привязано к корневому свойству speed. Это нельзя сделать как прямую привязку, так как элемент не всегда загружается и меняется со временем. Вместо этого необходимо использовать элемент Binding. Целевое свойство привязки изменяется каждый раз, когда Loader выдает сигнал onLoaded.

Loader {
    id: dialLoader

    anchors.left: parent.left
    anchors.right: parent.right
    anchors.top: parent.top
    anchors.bottom: analogButton.top

    onLoaded: {
        binder.target = dialLoader.item;
    }
}

Binding {
    id: binder

    property: "speed"
    value: root.speed
}

Сигнал onLoaded позволяет загрузочному QML действовать, когда элемент загружен. Аналогичным образом загружаемый QML может полагаться на сигнал Component.onCompleted. Этот сигнал доступен для всех компонентов, независимо от того, как они загружены. Например, корневой компонент всего приложения может использовать его для самозапуска, когда весь пользовательский интерфейс загружен.

Непрямое подключение

При динамическом создании элементов QML вы не можете подключиться к сигналам, используя подход onSignalName, используемый для статического случая. Вместо этого необходимо использовать элемент Connections. Он подключается к любому количеству сигналов целевого элемента.

Установив свойство target элемента Connections, сигналы можно соединять, как обычно, то есть используя подход onSignalName. Однако, изменяя свойство target, можно в разное время отслеживать разные элементы.

пример

В приведенном выше примере пользователю предоставляется пользовательский интерфейс, состоящий из двух интерактивных областей. При нажатии на любую область она мигает с помощью анимации. Левая область показана во фрагменте кода ниже. В MouseArea срабатывает leftClickedAnimation, вызывая мигание области.

Rectangle {
    id: leftRectangle

    width: 290
    height: 200

    color: "green"

    MouseArea {
        id: leftMouseArea
        anchors.fill: parent
        onClicked: leftClickedAnimation.start()
    }

    Text {
        anchors.centerIn: parent
        font.pixelSize: 30
        color: "white"
        text: "Click me!"
    }
}

В дополнение к двум кликабельным областям используется элемент Connections. Это запускает третью анимацию при нажатии на активный элемент, то есть на элемент target.

Connections {
    id: connections
    function onClicked() { activeClickedAnimation.start() }
}

Чтобы определить, на какую MouseArea ориентироваться, определяются два состояния. Обратите внимание, что мы не можем установить свойство target с помощью элемента PropertyChanges, так как он уже содержит свойство target. Вместо этого используется StateChangeScript.

states: [
    State {
        name: "left"
        StateChangeScript {
            script: connections.target = leftMouseArea
        }
    },
    State {
        name: "right"
        StateChangeScript {
            script: connections.target = rightMouseArea
        }
    }
]

При опробовании примера стоит заметить, что при использовании нескольких обработчиков сигналов вызываются все. Однако порядок их выполнения не определен.

При создании элемента Connections без установки свойства target это свойство по умолчанию имеет значение parent. Это означает, что оно должно быть явно установлено равным null, чтобы избежать перехвата сигналов от родителя, пока target не будет установлено. Такое поведение позволяет создавать пользовательские компоненты обработчиков сигналов на основе элемента Connections. Таким образом, код, реагирующий на сигналы, можно инкапсулировать и использовать повторно.

В приведенном ниже примере компонент Flasher можно поместить внутрь любого объекта MouseArea. При нажатии на него запускается анимация, в результате чего родитель мигает. В том же самом MouseArea может выполняться настоящая запускаемая задача. Это отделяет стандартную обратную связь с пользователем, то есть мигание, от реального действия.

// Flasher.qml
import QtQuick

Connections {
	function onClicked() {
		// Автоматически нацеливается на родителя
	}
}

Чтобы использовать Flasher, просто создайте экземпляр Flasher в каждой области MouseArea, и всё заработает.

// main.qml
import QtQuick

Item {
	// Фоновый Flasher, который подсвечивает фон
    // любой родительской области MouseArea
}

При использовании элемента Connections для мониторинга сигналов нескольких типов целевых элементов вы иногда оказываетесь в ситуации, когда доступные сигналы у целевых объектов различаются. Это приводит к тому, что элемент Connections выводит ошибки времени выполнения из-за пропуска сигналов. Чтобы избежать этого, для свойства ignoreUnknownSignal можно установить значение true. Это проигнорирует все подобные ошибки.

Совет

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

Косвенная привязка

Точно так же, как невозможно подключиться к сигналам динамически созданных элементов напрямую, невозможно привязать свойства динамически созданного элемента, не используя промежуточный элемент. Для привязки свойства любого элемента, включая динамически создаваемые элементы, используется элемент Binding.

Элемент Binding позволяет указать целевой элемент (target), свойство для привязки (property) и значение для привязки (value). С помощью элемента Binding можно, например, привязать свойства динамически загружаемого элемента. Это было продемонстрировано в первом примере этого раздела, как показано ниже.

Loader {
    id: dialLoader

    anchors.left: parent.left
    anchors.right: parent.right
    anchors.top: parent.top
    anchors.bottom: analogButton.top

    onLoaded: {
        binder.target = dialLoader.item;
    }
}

Binding {
    id: binder

    property: "speed"
    value: root.speed
}

Поскольку элемент target в Binding не всегда установлен и, возможно, не всегда имеет заданное свойство, то для ограничения времени активности привязки можно использовать свойство when элемента Binding. Например, это может быть ограничено определенными режимами в пользовательском интерфейсе.

Элемент Binding также имеет свойство delayed. Если для этого свойства задано значение true, привязка не распространяется на цель до тех пор, пока очередь событий не будет очищена. В ситуациях с высокой нагрузкой это может служить оптимизацией, поскольку элементу target не передаются промежуточные значения.

Теги

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

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

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