Динамическая загрузка компонентов
Самый простой способ динамически загружать различные части 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
не передаются промежуточные значения.