Система сигналов и обработчиков событий в QML
Компоненты приложения и пользовательского интерфейса должны взаимодействовать друг с другом. Например, кнопка должна знать, что пользователь нажал на нее. Кнопка может менять цвет, чтобы показать свое состояние, или выполнить некоторую логику. Кроме того, приложение должно знать, нажимает ли пользователь кнопку. Приложению может потребоваться передать это событие клика другим приложениям.
QML содержит механизм сигналов и обработчиков, где сигнал представляет собой событие, а ответ на этот сигнал поступает через обработчик сигнала. Когда сигнал испускается, вызывается соответствующий обработчик сигнала. Размещение логики, такой как скрипт или другие операции, в обработчике позволяет компоненту реагировать на события.
Прием сигналов с помощью обработчиков сигналов
Чтобы получать уведомление, когда для определенного объекта испускается конкретный сигнал, определение объекта должно объявить обработчик сигнала с именем on<Signal>,
где <Signal>
– это имя сигнала с заглавной первой буквой. Обработчик сигнала должен содержать код JavaScript, который будет выполняться при вызове обработчика сигнала.
Например, тип Button
из модуля Qt Quick Controls имеет сигнал clicked
, который выдается при каждом нажатии кнопки. В этом случае обработчик сигнала для получения этого сигнала должен быть с именем onClicked
. В приведенном ниже примере при каждом нажатии кнопки вызывается обработчик onClicked
, применяющий случайный цвет к родительскому прямоугольнику:
import QtQuick
import QtQuick.Controls
Rectangle {
id: rect
width: 250; height: 250
Button {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: "Change color!"
onClicked: {
rect.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
}
}
}
Обработчики сигналов изменений свойств
При изменении значения свойства QML сигнал генерируется автоматически. Этот тип сигнала является сигналом изменения свойства, и обработчики для этих сигналов записываются в виде on<Property>Changed
, где <Property>
– это имя свойства с заглавной первой буквой.
Например, тип MouseArea
имеет свойство pressed
. Чтобы получать уведомление при каждом изменении этого свойства, напишите обработчик сигнала с именем onPressedChanged
:
import QtQuick
Rectangle {
id: rect
width: 100; height: 100
TapHandler {
onPressedChanged: console.log("taphandler pressed?", pressed)
}
}
Несмотря на то, что в документации TapHandler
не описан обработчик сигнала с именем onPressedChanged
, сигнал неявно обеспечивается тем фактом, что существует свойство pressed
.
Параметры сигналов
Сигналы могут иметь параметры. Чтобы получить к ним доступ, вы должны назначить функцию обработчику. Работают как стрелочные функции, так и анонимные функции.
В следующих примерах рассмотрим компонент Status
с сигналом errorOccurred
(дополнительную информацию о добавлении сигналов в компоненты QML смотрите в разделе Добавление сигналов к пользовательским типам QML).
// Status.qml
import QtQuick
Item {
id: myitem
signal errorOccurred(message: string, line: int, column: int)
}
Status {
onErrorOccurred: (mgs, line, col) => console.log(`${line}:${col}: ${msg}`)
}
Примечание. Имена формальных параметров в функции не обязательно должны совпадать с именами в сигнале.
Если вам не нужно обрабатывать все параметры, можно опустить последние из них:
Status {
onErrorOccurred: function (message) { console.log(message) }
}
Идущие в начале параметры пропустить невозможно, однако вы можете использовать какое-либо имя-заполнитель, чтобы указать читателям, что они не важны:
Status {
onErrorOccurred: (_, _, col) => console.log(`Error happened at column ${col}`)
}
Примечание. Вместо использования функции можно, но не рекомендуется использовать простой блок кода. В этом случае все параметры сигнала вводятся в область видимости блока. Однако это может затруднить чтение кода, поскольку неясно, откуда берутся параметры, и приводит к замедлению поиска в механизме QML. Внедрение параметров таким образом устарело и вызовет предупреждения во время выполнения, если параметр действительно используется.
Использование типа соединения
В некоторых случаях может быть желательно получить доступ к сигналу за пределами объекта, который его излучает. Для этих целей в модуле QtQuick предусмотрен тип Connections
для подключения к сигналам произвольных объектов. Объект Connections
может получать любой сигнал от указанной цели (target
).
Например, обработчик onClicked
в предыдущем примере мог быть получен корневым прямоугольником Rectangle
, если бы он был помещен в объект Connections
, целью которого была кнопка:
import QtQuick
import QtQuick.Controls
Rectangle {
id: rect
width: 250; height: 250
Button {
id: button
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: "Change color!"
}
Connections {
target: button
function onClicked() {
rect.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
}
}
}
Прикрепленные обработчики сигналов
Прикрепленный обработчик сигнала получает сигнал от присоединяемого типа, а не от объекта, в котором объявлен обработчик.
Например, Component.onCompleted
– это прикрепленный обработчик сигнала. Он часто используется для выполнения некоторого кода JavaScript, когда процесс создания компонента завершен. Пример:
import QtQuick
Rectangle {
width: 200; height: 200
color: Qt.rgba(Qt.random(), Qt.random(), Qt.random(), 1)
Component.onCompleted: {
console.log("The rectangle's color is", color)
}
}
Обработчик onCompleted
не реагирует на сигнал completed
типа Rectangle
. Вместо этого объект присоединяемого типаComponent
с сигналом completed
был автоматически присоединен движком QML к объекту Rectangle
. Движок выдает этот сигнал при создании объекта Rectangle
, тем самым запуская обработчик сигнала Component.onCompleted
.
Прикрепленные обработчики сигналов позволяют объектам уведомляться о конкретных сигналах, которые важны для каждого отдельного объекта. Если бы не было прикрепленного обработчика сигнала Component.onCompleted
, объект, например, не мог бы получить это уведомление без регистрации на какой-то специальный сигнал от какого-то специального объекта. Механизм прикрепленных обработчиков сигналов позволяет объектам получать определенные сигналы без дополнительного кода.
Дополнительную информацию о прикрепленных обработчиках сигналов смотрите в разделе Прикрепленные свойства и прикрепленные обработчики сигналов.
Добавление сигналов к пользовательским типам QML
Сигналы можно добавлять в пользовательские типы QML с помощью ключевого слова signal
.
Синтаксис для определения нового сигнала:
signal <name>[([<type> <parameter name>[, ...]])]
Сигнал испускается путем вызова сигнала как метода.
Например, приведенный ниже код определен в файле с именем SquareButton.qml. Корневой объект Rectangle
имеет сигнал activated
, который испускается при каждом касании дочернего элемента TapHandler
. В этом конкретном примере сигнал activated
испускается с координатами x
и y
клика мыши:
// SquareButton.qml
import QtQuick
Rectangle {
id: root
signal activated(real xPosition, real yPosition)
property point mouseXY
property int side: 100
width: side; height: side
TapHandler {
id: handler
onTapped: root.activated(root.mouseXY.x, root.mouseXY.y)
onPressedChanged: root.mouseXY = handler.point.position
}
}
Теперь любые объекты SquareButton
могут подключаться к сигналу activated
с помощью обработчика сигнала onActivated
:
// myapplication.qml
SquareButton {
onActivated: (xPosition, yPosition)=> console.log("Activated at " + xPosition + "," + yPosition)
}
Дополнительную информацию о написании сигналов для пользовательских типов QML смотрите в разделе Атрибуты сигналов.
Подключение сигналов к методам и сигналам
Объекты сигналов имеют метод connect()
для подключения сигнала либо к методу, либо к другому сигналу. Когда сигнал подключается к методу, этот метод автоматически вызывается всякий раз, когда генерируется сигнал. Этот механизм позволяет получать сигнал методом, а не обработчиком сигнала.
В примере ниже сигнал messageReceived
подключается к трем методам с помощью метода connect()
:
import QtQuick
Rectangle {
id: relay
signal messageReceived(string person, string notice)
Component.onCompleted: {
relay.messageReceived.connect(sendToPost)
relay.messageReceived.connect(sendToTelegraph)
relay.messageReceived.connect(sendToEmail)
relay.messageReceived("Tom", "Happy Birthday")
}
function sendToPost(person, notice) {
console.log("Sending to post: " + person + ", " + notice)
}
function sendToTelegraph(person, notice) {
console.log("Sending to telegraph: " + person + ", " + notice)
}
function sendToEmail(person, notice) {
console.log("Sending to email: " + person + ", " + notice)
}
}
Во многих случаях достаточно получать сигналы через обработчики сигналов, а не использовать функцию connect()
. Однако использование метода connect
позволяет получать сигнал несколькими методами, как показано выше, что было бы невозможно с обработчиками сигналов, поскольку они должны иметь уникальные имена. Также метод connect
полезен при подключении сигналов к динамически создаваемым объектам.
Также существует соответствующий метод disconnect()
для удаления подключенных сигналов:
Rectangle {
id: relay
//...
function removeTelegraphSignal() {
relay.messageReceived.disconnect(sendToTelegraph)
}
}
Подключение сигнала к сигналу
Соединяя сигналы с другими сигналами, метод connect()
может формировать разные цепочки сигналов.
import QtQuick
Rectangle {
id: forwarder
width: 100; height: 100
signal send()
onSend: console.log("Send clicked")
TapHandler {
id: mousearea
anchors.fill: parent
onTapped: console.log("Mouse clicked")
}
Component.onCompleted: {
mousearea.tapped.connect(send)
}
}
Всякий раз, когда излучается сигнал tapped
элемента TapHandler
, сигнал send
также будет излучаться автоматически.
output:
MouseArea clicked
Send clicked