Введение
Привязки (статья 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, зарезервированному за автоматическими сигналами изменения свойств.