Введение
Доступность (accessibility, a11y) — обеспечение того, что приложением могут пользоваться люди с ограниченными возможностями: слабовидящие и незрячие пользователи через screen reader, люди с моторными нарушениями через клавиатурную навигацию вместо мыши, пользователи с нарушениями слуха через визуальные альтернативы звуковым сигналам. Qt предоставляет встроенную поддержку доступности через модуль QAccessible, интегрированный с большинством стандартных виджетов «из коробки» — но для кастомных виджетов и сложных сценариев нужна явная настройка. В статье разберём основы того, как Qt экспортирует информацию о доступности операционной системе.
Концепция
Каждая платформа (Windows — UI Automation/MSAA, macOS — NSAccessibility, Linux — AT-SPI) предоставляет собственный API, через который screen reader и другие вспомогательные технологии получают информацию о структуре интерфейса приложения — название элементов, их роль (кнопка, текстовое поле, список), состояние (нажата, отмечена, недоступна), и иерархию вложенности. QAccessible абстрагирует эти платформенные различия: Qt автоматически предоставляет базовую информацию о доступности для стандартных виджетов (текст кнопки, состояние чекбокса) без какого-либо дополнительного кода — но эта информация может быть улучшена через accessibleName/accessibleDescription, а для кастомных виджетов должна быть реализована явно через QAccessibleInterface.
Пример кода: QAccessible
#include <QApplication>
#include <QPushButton>
#include <QLineEdit>
#include <QLabel>
#include <QVBoxLayout>
class SearchWidget : public QWidget
{
Q_OBJECT
public:
explicit SearchWidget(QWidget *parent = nullptr) : QWidget(parent)
{
auto *searchField = new QLineEdit(this);
// accessibleName — то, что прочитает screen reader, может отличаться
// от видимого текста (например, если рядом нет текстовой подписи-label)
searchField->setAccessibleName("Поле поиска по каталогу товаров");
searchField->setPlaceholderText("Введите название товара...");
auto *searchButton = new QPushButton("🔍", this); // только иконка, без текста
// Без accessibleName screen reader прочитал бы только символ эмодзи,
// что бесполезно для пользователя — нужно явное описание действия
searchButton->setAccessibleName("Выполнить поиск");
searchButton->setAccessibleDescription(
"Запускает поиск товаров по тексту, введённому в поле поиска");
auto *resultsLabel = new QLabel(this);
resultsLabel->setAccessibleName("Результаты поиска");
auto *layout = new QVBoxLayout(this);
layout->addWidget(searchField);
layout->addWidget(searchButton);
layout->addWidget(resultsLabel);
connect(searchButton, &QPushButton::clicked, [searchField, resultsLabel]() {
resultsLabel->setText("Найдено товаров: 12 по запросу \"" + searchField->text() + "\"");
// Уведомление screen reader об изменении текста — важно для
// динамически обновляемого контента (см. подробности в статье 205)
QAccessibleEvent event(resultsLabel, QAccessible::NameChanged);
QAccessible::updateAccessibility(&event);
});
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
SearchWidget widget;
widget.show();
return app.exec();
}
#include "main.moc"
Пояснения к коду
setAccessibleName() на searchField задаёт текст, который screen reader произнесёт при попадании фокуса на это поле — он может содержать более подробную информацию, чем видимый placeholder, который сам по себе не всегда корректно озвучивается как полноценная подпись поля во всех вспомогательных технологиях. Для кнопки с одним символом-эмодзи вместо текста (searchButton) явный accessibleName критичен — без него у пользователя screen reader не будет никакой осмысленной информации о назначении этой кнопки, что делает интерфейс полностью непригодным для использования без зрячего контроля.
Вызов QAccessible::updateAccessibility() с событием NameChanged после обновления текста resultsLabel — явное уведомление подсистемы доступности о том, что содержимое элемента изменилось динамически, в ответ на действие пользователя; без этого уведомления screen reader может не заметить изменение и не сообщить пользователю о появлении новых результатов поиска.
Подводные камни
- Расчёт на то, что видимый текст автоматически становится доступным для screen reader без явной настройки. Для большинства стандартных текстовых элементов (
QLabel,QPushButtonс текстом) это действительно так — Qt автоматически использует видимый текст как имя для доступности. Но для виджетов без текста (иконки, кастомная графика) или с неинформативным текстом ("🔍") это предположение неверно, и явныйaccessibleNameобязателен. - Изменение динамического контента без уведомления через
QAccessible::updateAccessibility(). Screen reader, в отличие от зрячего пользователя, не «видит» визуальное изменение текста на экране автоматически — без явного программного уведомления об изменении он продолжит сообщать устаревшую информацию или вовсе не заметит изменения, пока пользователь не переместит фокус заново на этот элемент. - Использование
accessibleDescriptionкак замены, а не дополнения кaccessibleName.accessibleName— короткое, основное обозначение элемента (что это),accessibleDescription— дополнительный, более подробный контекст (как это используется или для чего) — путаница между их назначением приводит к избыточно длинному или, наоборот, недостаточно информативному озвучиванию элемента screen reader’ом. - Тестирование доступности только зрячим разработчиком без реального screen reader. Визуальная проверка интерфейса не выявляет проблем доступности — единственный надёжный способ убедиться, что приложение действительно доступно, это протестировать его с реальным screen reader (NVDA на Windows, VoiceOver на macOS, Orca на Linux), что подробнее разбирается в статье 206 этого цикла.