Введение
В Qt Quick доступность реализуется иначе, чем в Qt Widgets — вместо отдельного класса QAccessibleInterface, регистрируемого через фабрику, для QML-элементов используется attached property Accessible, которая декларативно прикрепляется к любому визуальному элементу прямо в QML-коде. В статье разберём, как настроить доступность для типичных QML-компонентов — кастомной кнопки и переключателя, не использующих стандартные Qt Quick Controls.
Концепция
Attached property Accessible доступна для любого Item в QML после установки Accessible.role — само это присвоение активирует экспорт доступности для элемента (по умолчанию произвольный Item не считается значимым для доступности и пропускается подсистемой). Основные свойства: Accessible.role (семантическая роль — Button, CheckBox, StaticText и другие), Accessible.name (озвучиваемое имя), Accessible.description (дополнительный контекст), и для интерактивных элементов — Accessible.onPressAction/другие обработчики, позволяющие screen reader программно активировать элемент, даже если он не предоставляет реальной точки клика для эмулируемого нажатия.
Пример кода: Доступность в QML/Qt Quick
// CustomButton.qml — кастомная кнопка без использования Qt Quick Controls
import QtQuick
Rectangle {
id: root
property string text: ""
signal clicked()
width: 120; height: 40
radius: 6
color: mouseArea.pressed ? "#3a78c2" : "#4a90e2"
Accessible.role: Accessible.Button
Accessible.name: root.text
Accessible.onPressAction: root.clicked() // позволяет screen reader "нажать" кнопку программно
Text {
anchors.centerIn: parent
text: root.text
color: "white"
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: root.clicked()
}
}
// CustomToggle.qml — кастомный переключатель с корректным сообщением состояния
import QtQuick
Rectangle {
id: root
property bool checked: false
signal toggled(bool checked)
width: 50; height: 26
radius: height / 2
color: checked ? "#4caf50" : "#cccccc"
Accessible.role: Accessible.CheckBox
Accessible.name: "Включить уведомления"
// checkable/checked сообщают screen reader текущее состояние переключателя —
// без этого он не сможет озвучить "включено"/"выключено"
Accessible.checkable: true
Accessible.checked: root.checked
Accessible.onToggleAction: {
root.checked = !root.checked;
root.toggled(root.checked);
}
Rectangle {
width: 20; height: 20; radius: 10
color: "white"
x: root.checked ? root.width - width - 3 : 3
anchors.verticalCenter: parent.verticalCenter
Behavior on x { NumberAnimation { duration: 150 } }
}
MouseArea {
anchors.fill: parent
onClicked: {
root.checked = !root.checked;
root.toggled(root.checked);
}
}
}
Пояснения к коду
CustomButton устанавливает Accessible.role: Accessible.Button и Accessible.name, привязанное к видимому тексту кнопки — этого достаточно для базового объявления роли и имени элемента. Критичная деталь — Accessible.onPressAction, привязанный к тому же сигналу clicked(), что и обычный обработчик MouseArea — это даёт screen reader возможность программно «нажать» кнопку (например, через комбинацию клавиш активации текущего элемента в самом screen reader) без необходимости физически кликать мышью, что для незрячего пользователя, ориентирующегося полностью на клавиатуру и озвучивание, абсолютно необходимо.
CustomToggle дополнительно использует Accessible.checkable/Accessible.checked для сообщения булевого состояния переключателя, и Accessible.onToggleAction — специализированный обработчик именно для переключаемых элементов, логически отличный от onPressAction обычной кнопки, поскольку переключатель имеет двойное состояние, которое должно быть программно изменяемым через ту же активацию из screen reader, а не только через визуальный клик мышью.
Подводные камни
- Отсутствие
Accessible.roleделает элемент полностью невидимым для подсистемы доступности. Простое присвоениеAccessible.nameбез указанияroleв некоторых конфигурациях может не активировать экспорт доступности для элемента вовсе — оба свойства обычно нужно устанавливать совместно для надёжной работы, а не полагаться на то, что одно из них автоматически подразумевает другое. - Кастомный визуальный компонент без соответствующего
Accessible.onPressAction/onToggleActionвиден screen reader (если роль и имя установлены), но не интерактивен через программную активацию — пользователь screen reader услышит название элемента, но не сможет с ним взаимодействовать без реального физического клика мышью, что для многих пользователей screen reader полностью недостижимо. - Несоответствие визуального состояния и объявленного
Accessible.checkedпри сложной логике переключения. Если состояниеcheckedэлемента переключается асинхронно (например, после подтверждения с сервера, а не мгновенно при клике), и привязкаAccessible.checked: root.checkedссылается на локальное, оптимистично изменённое значение до получения реального подтверждения, screen reader может на короткое время объявить состояние, не соответствующее тому, что фактически подтверждено на сервере — для критичных переключателей стоит явно синхронизировать визуальное/декларируемое состояние именно с подтверждённым, а не оптимистично предсказанным значением. - Использование стандартных Qt Quick Controls (
Button,CheckBoxизQtQuick.Controls) вместо полностью кастомных компонентов там, где доступность уже встроена «из коробки». Стандартные контролы Qt Quick Controls уже имеют базовую интеграцию с подсистемой доступности без необходимости вручную прописывать каждое свойствоAccessible.*— переход на полностью кастомные визуальные компоненты (как в примере) оправдан только когда есть реальная потребность в нестандартном внешнем виде, и в этом случае вся ответственность за корректную доступность переходит на разработчика компонента, что и продемонстрировано необходимым объёмом кода в примерах этой статьи.