QML

Сигналы и обработчики в QML: onXxx, Connections

Введение

Привязки (статья 481) обрабатывают декларативную синхронизацию значений свойств — для реакции на дискретные события (нажатие кнопки, завершение анимации, изменение конкретного значения в конкретный момент) QML использует механизм сигналов и обработчиков, концептуально похожий на сигналы/слоты Qt Widgets (основной цикл статей), но с QML-специфичным синтаксисом.

Концепция

Каждое свойство QML-объекта автоматически имеет соответствующий сигнал изменения (onXxxChanged, где Xxx — имя свойства с заглавной первой буквы), вызываемый каждый раз, когда значение свойства меняется — это даёт возможность выполнить произвольный JavaScript-код именно в момент изменения, в отличие от привязок, которые лишь синхронизируют значения, не выполняя произвольных побочных действий. Встроенные QML-типы также объявляют собственные, специфичные для их назначения сигналы (onClicked для MouseArea/Button, onTriggered для Timer) — обработчик этих сигналов пишется через тот же синтаксис onSignalName: { /* код */ }. Connections — отдельный QML-тип, позволяющий подключить обработчик к сигналам объекта, на который ссылаются динамически (через свойство target), что особенно полезно, когда целевой объект может меняться во время выполнения, в отличие от статичного, встроенного в объект onXxx-обработчика.

Пример кода: Сигналы и обработчики в QML

import QtQuick
import QtQuick.Controls

Column {
    property int counter: 0
    onCounterChanged: console.log("Счётчик изменился, новое значение:", counter)
    // ^ автоматический сигнал изменения СВОЙСТВА — срабатывает на КАЖДОЕ изменение counter,
    // независимо от того, через привязку или явное присваивание оно произошло

    Button {
        text: "Увеличить"
        onClicked: counter++ // встроенный сигнал MouseArea/Button-подобных элементов
    }

    Timer {
        interval: 1000; running: true; repeat: true
        onTriggered: console.log("Прошла секунда") // встроенный сигнал Timer
    }
}
// Connections — подключение к сигналам объекта, который может меняться динамически
import QtQuick

Item {
    property var currentTarget: null

    Connections {
        target: currentTarget // привязка target, а не статичное, "жёстко прибитое" подключение
        function onSomeSignal() {
            console.log("Сигнал получен от текущей цели")
        }
    }

    // При изменении currentTarget на другой объект Connections автоматически
    // переключает подписку на сигналы НОВОГО объекта, отключаясь от старого
}
// Передача параметров сигнала в обработчик
Rectangle {
    width: 200; height: 200
    color: "lightblue"

    MouseArea {
        anchors.fill: parent
        onClicked: function(mouse) {
            console.log("Клик в координатах:", mouse.x, mouse.y, "кнопка:", mouse.button)
        }
    }
}

Пояснения к коду

onCounterChanged показывает автоматически генерируемый обработчик изменения пользовательского свойства counter — этот сигнал срабатывает независимо от способа изменения значения (явное присваивание counter++ внутри onClicked, или гипотетически — изменение через привязку), что отличает его от привязок, реагирующих только на участие свойства в выражении привязки другого свойства. Connections с target: currentTarget демонстрирует динамическое подключение — в отличие от обычного onClicked внутри самого объекта Button (статичная, неизменная связь с конкретным, заранее известным объектом), Connections позволяет переключать объект-источник сигналов во время выполнения, что невозможно выразить через обычный, встроенный в объект синтаксис onXxx. Параметр mouse в onClicked: function(mouse) {...} показывает доступ к дополнительной информации о событии, передаваемой вместе с сигналом — аналогично параметрам сигналов Qt Widgets, многие встроенные сигналы QML передают объект с дополнительными деталями о произошедшем событии.

Подводные камни

  • Использование Connections без необходимости, когда обычный, встроенный onXxx-обработчик внутри самого объекта решил бы задачу проще — Connections нужен именно для случая динамического, изменяющегося целевого объекта (как показано в примере), и для статичного, заранее известного объекта прямой onClicked внутри него самого — более простой, идиоматичный и понятный способ подписки на тот же сигнал, не требующий дополнительной структуры Connections.
  • Использование старого синтаксиса Connections { onSomeSignal: ... } (как свойства) вместо нового, рекомендуемого синтаксиса с функцией function onSomeSignal() {...} в современных версиях Qt — оба синтаксиса исторически существовали, но синтаксис с явным объявлением функции более явно поддерживает несколько обработчиков одного сигнала и считается актуальным, рекомендуемым подходом в современных версиях Qt Quick.
  • Создание избыточно сложной, многошаговой логики непосредственно внутри обработчика сигнала прямо в QML-файле, вместо выделения этой логики в отдельную, именованную JavaScript-функцию (статья 483) или, для действительно существенной логики, в C++ (статья 490) — короткие, простые обработчики (counter++) уместны непосредственно в месте объявления сигнала, но разрастающаяся, сложная логика внутри обработчика снижает читаемость и затрудняет последующее тестирование (статья 496) этой логики в изоляции от остального QML-документа.
  • Путаница между сигналом изменения свойства (onXxxChanged) и одноимённым, но иным по смыслу пользовательским сигналом, если разработчик случайно объявляет собственный сигнал с именем, совпадающим по паттерну с автоматически генерируемым именем обработчика изменения свойства — это может привести к неочевидным конфликтам имён или путанице при чтении кода, и стоит явно избегать именования пользовательских сигналов по шаблону xxxChanged, зарезервированному за автоматическими сигналами изменения свойств.