Система сигналов и обработчиков событий в QML

Добавлено 10 мая 2022 в 17:26

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

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

Теги

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

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

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