Доступность для приложений QWidget
Введение
Мы сосредоточимся на интерфейсе Qt для доступности QAccessibleInterface и на том, как сделать приложения доступными.
Доступность в приложениях на основе QWidget
При взаимодействии с технологиями вспомогательных средств мы должны описать пользовательский интерфейс Qt таким образом, чтобы они могли его понять. Приложения Qt используют QAccessibleInterface для предоставления информации об отдельных элементах пользовательского интерфейса. В настоящее время Qt поддерживает свои виджеты и части виджетов, например, ползунки, но если необходимо, интерфейс может быть реализован для любого QObject. QAccessible содержит перечисления, описывающие пользовательский интерфейс. Мы рассмотрим эти перечисления в ходе данного документа.
Структура пользовательского интерфейса представлена в виде дерева подклассов QAccessibleInterface. Это часто отражает иерархию виджетов QWidgets, которые составляют пользовательский интерфейс приложения.
Серверы уведомляют клиентов об изменениях объектов с помощью updateAccessibility(), отправляя события, а клиенты регистрируются для получения этих событий. Доступные события определяются перечислением QAccessible::Event. Затем клиенты могут запросить объект, который сгенерировал событие, через QAccessible::queryAccessibleInterface().
Члены и перечисления в QAccessible используются для описания доступных объектов:
- Роль: описывает роль объекта в пользовательском интерфейсе, например, является ли он окном, текстовым полем или ячейкой в таблице.
- Связь: описывает отношения между объектами в иерархии объектов.
- Состояние: объекты могут находиться в нескольких различных состояниях. Примеры состояний включают отключение объекта, наличие фокуса или возможность отображения всплывающего меню.
Клиенты также могут получить содержимое объектов, например, текст кнопки; объект предоставляет строки, определенные перечислением QAccessible::Text, которые содержат информацию о содержимом.
Дерево доступных объектов
Как упоминалось ранее, структура дерева строится из доступных объектов приложения. Перемещаясь по дереву, клиенты могут получить доступ ко всем элементам пользовательского интерфейса. Связи между объектами предоставляют клиентам информацию о пользовательском интерфейсе. Например, ползунок является потомком ползунка, к которому он принадлежит. QAccessible::Relation описывает различные отношения, которые клиенты могут запросить у объектов.
Обратите внимание, что между деревом Qt QObject и деревом доступных объектов нет прямого соответствия. Например, маркеры полос прокрутки являются доступными объектами, но не являются виджетами или объектами Qt.
Клиенты вспомогательных технологий имеют доступ к дереву доступных объектов через корневой объект в дереве, которым является QApplication. Они могут перемещаться по дереву с помощью функций QAccessibleInterface::parent(), QAccessibleInterface::childCount() и QAccessibleInterface::child().
Qt предоставляет доступные интерфейсы для своих виджетов и для Qt Quick Controls. Интерфейсы для любого подкласса QObject могут быть запрошены через QAccessible::queryInterface(). Если более специализированный интерфейс не определен, предоставляется реализация по умолчанию. Клиент вспомогательных технологий не может получить интерфейс для доступных объектов, у которых нет эквивалентного QObject, например, маркеров полос прокрутки, но они отображаются как обычные объекты через интерфейсы родительских доступных объектов, например, вы можете запросить их отношения с помощью QAccessibleInterface::relations().
Для иллюстрации мы представляем изображение дерева доступных объектов. Под деревом находится таблица с примерами отношений объектов.
Метки сверху вниз: имя класса QAccessibleInterface, виджет, для которого предоставляется интерфейс, и Роль объекта. Позиция, PageLeft и PageRight соответствуют ползунку, левой и правой части канала ползунка, соответственно. Эти доступные объекты не имеют эквивалентного QObject.
| Источник объекта | Целевой объект | Связь |
|---|---|---|
| Ползунок | Индикатор | Управляющий |
| Индикатор | Ползунок | Управляемый |
| Ползунок | Приложение | Предок |
| Приложение | Ползунок | Потомок |
| Кнопка | Индикатор | Брат |
Статические функции QAccessible
Управление доступностью осуществляется статическими функциями QAccessible, которые мы рассмотрим в ближайшее время. Они создают интерфейсы QAccessible, строят дерево объектов и инициируют подключение с MSAA или другими платформами-специфическими технологиями. Если вас интересует только то, как сделать ваше приложение доступным, вы можете пропустить этот раздел и перейти к разделу Реализация доступности.
Взаимодействие между клиентами и сервером инициируется при вызове setRootObject(). Это происходит при создании экземпляра QApplication, и вам не нужно делать это самостоятельно.
Когда QObject вызывает updateAccessibility(), клиенты, которые прослушивают события, получают уведомление об изменении. Функция используется для отправки событий в технологии вспомогательных средств, и доступные события события публикуются функцией updateAccessibility().
queryAccessibleInterface() возвращает доступные интерфейсы для QObjects. Все виджеты в Qt предоставляют интерфейсы; если вам нужны интерфейсы для управления поведением других подклассов QObject, вы должны реализовать интерфейсы самостоятельно, хотя удобный класс QAccessibleObject реализует часть функциональности за вас.
Фабрика, которая создает доступные интерфейсы для QObjects, является функцией типа QAccessible::InterfaceFactory. Может быть установлено несколько фабрик. Последняя установленная фабрика будет первой, к которой будет обращен запрос на интерфейсы. queryAccessibleInterface() использует фабрики для создания интерфейсов для QObjects. Как правило, вам не нужно беспокоиться о фабриках, потому что вы можете реализовывать плагины, которые создают интерфейсы. Позже мы приведем примеры обоих подходов.
Реализация доступности
Для обеспечения поддержки доступности для виджета или другого элемента пользовательского интерфейса необходимо реализовать QAccessibleInterface и распространить его в QAccessiblePlugin. Также можно скомпилировать интерфейс в приложение и предоставить QAccessible::InterfaceFactory для него. Фабрика может использоваться при статической линковке или если вы не хотите добавлять сложность плагинов. Это может быть преимуществом, например, при разработке сторонней библиотеки.
Все виджеты и другие элементы пользовательского интерфейса должны иметь интерфейсы и плагины. Если вы хотите, чтобы ваше приложение поддерживало доступность, вам необходимо учитывать следующее:
- Qt уже реализовал доступность для собственных виджетов. Поэтому мы рекомендуем использовать виджеты Qt, где это возможно.
- Для каждого элемента, который вы хотите сделать доступным для клиентов с технологиями вспомогательных средств, должен быть реализован QAccessibleInterface.
- Вам нужно отправлять события доступности от пользовательских элементов пользовательского интерфейса, которые вы реализуете.
В целом, рекомендуется, чтобы вы были знакомы с MSAA, для которого изначально была построена поддержка доступности Qt. Вам также следует изучить значения перечислений QAccessible, которые описывают роли, действия, отношения и события, которые вам необходимо учитывать.
Обратите внимание, что вы можете изучить, как виджеты Qt реализуют свою доступность. Одна из основных проблем стандарта MSAA заключается в том, что интерфейсы часто реализуются несогласованным образом. Это затрудняет работу клиентов и часто приводит к догадкам о функциональности объекта.
Возможно реализовать интерфейсы, унаследовав QAccessibleInterface и реализовав его чистые виртуальные функции. Однако на практике обычно предпочтительнее наследовать QAccessibleObject или QAccessibleWidget, которые реализуют часть функциональности за вас. В следующем разделе мы рассмотрим пример реализации доступности для виджета, унаследовав класс QAccessibleWidget.
Удобные классы QAccessibleObject и QAccessibleWidget
При реализации доступного интерфейса для виджетов обычно наследуют QAccessibleWidget, который является удобным классом для виджетов. Другой доступный удобный класс, от которого наследуется QAccessibleWidget, — это QAccessibleObject, который реализует часть интерфейса для QObjects.
Класс QAccessibleWidget предоставляет следующие возможности:
- Он обрабатывает навигацию по дереву и проверку попадания в объекты.
- Он обрабатывает события, роли и действия, которые являются общими для всех QWidget.
- Он обрабатывает действия и методы, которые могут быть выполнены над всеми виджетами.
- Он вычисляет ограничительные прямоугольники с помощью rect().
- Он предоставляет строки text(), которые подходят для общего виджета.
- Он устанавливает состояния, которые являются общими для всех виджетов.
Пример QAccessibleWidget
Вместо создания пользовательского виджета и реализации для него интерфейса, мы покажем, как реализована доступность для одного из стандартных виджетов Qt: QSlider. Доступный интерфейс, QAccessibleSlider, наследуется от QAccessibleAbstractSlider, который, в свою очередь, наследуется от QAccessibleWidget. Вам не нужно изучать класс QAccessibleAbstractSlider для чтения этого раздела. Если вы хотите посмотреть, код всех доступных интерфейсов Qt находится в qtbase/src/widgets/accessible. Вот конструктор QAccessibleSlider:
QAccessibleSlider::QAccessibleSlider(QWidget *w)
: QAccessibleAbstractSlider(w)
{
Q_ASSERT(slider());
addControllingSignal(QLatin1String("valueChanged(int)"));
} Ползунок — это сложное управление, которое функционирует как Контроллер для своих доступных дочерних элементов. Это отношение должно быть известно интерфейсу (для parent(), child() и relations()). Это можно сделать с помощью управляющего сигнала, который является механизмом, предоставляемым QAccessibleWidget. Мы делаем это в конструкторе:
Выбор показанного сигнала не важен; те же принципы применяются ко всем сигналам, которые объявлены таким образом. Обратите внимание, что мы используем QLatin1String, чтобы гарантировать, что имя сигнала правильно указано.
Когда доступный объект изменяется таким образом, что пользователи должны об этом знать, он уведомляет клиентов об изменении, отправляя им событие через доступный интерфейс. Вот как QSlider вызывает updateAccessibility(), чтобы указать, что его значение изменилось:
void QAbstractSlider::setValue(int value)
...
QAccessibleValueChangeEvent event(this, d->value);
QAccessible::updateAccessibility(&event);
...
} Обратите внимание, что вызов производится после изменения значения ползунка, поскольку клиенты могут запросить новое значение сразу после получения события.
Интерфейс должен уметь вычислять ограничительные прямоугольники самого себя и любых дочерних элементов, которые не предоставляют собственный интерфейс. QAccessibleSlider имеет три таких дочерних элемента, идентифицированных закрытым перечислением SliderElements, которое имеет следующие значения: PageRight (прямоугольник справа от ручки ползунка), Position (ручка ползунка). Вот реализация rect():
QRect QAccessibleSlider::rect(int child) const
{
...
switch (child) {
case PageLeft:
if (slider()->orientation() == Qt::Vertical)
rect = QRect(0, 0, slider()->width(), srect.y());
else
rect = QRect(0, 0, srect.x(), slider()->height());
break;
case Position:
rect = srect;
break;
case PageRight:
if (slider()->orientation() == Qt::Vertical)
rect = QRect(0, srect.y() + srect.height(), slider()->width(), slider()->height()- srect.y() - srect.height());
else
rect = QRect(srect.x() + srect.width(), 0, slider()->width() - srect.x() - srect.width(), slider()->height());
break;
default:
return QAccessibleAbstractSlider::rect(child);
}
... Первая часть функции, которую мы опустили, использует текущий стиль, чтобы вычислить ограничительный прямоугольник ручки ползунка; он хранится в srect. Обратите внимание, что дочерний элемент 0, охваченный в операторе по умолчанию в приведенном выше коде, — это сам ползунок, поэтому мы можем просто вернуть ограничительный прямоугольник QSlider, полученный от суперкласса, что по сути является значением, полученным от QAccessibleWidget::rect().
QPoint tp = slider()->mapToGlobal(QPoint(0,0));
return QRect(tp.x() + rect.x(), tp.y() + rect.y(), rect.width(), rect.height());
} Перед возвратом прямоугольника он должен быть преобразован в координаты экрана.
QAccessibleSlider должен повторно реализовать QAccessibleInterface::childCount(), так как он управляет дочерними элементами без интерфейсов.
Функция text() возвращает строки QAccessible::Text для ползунка:
QString QAccessibleSlider::text(Text t, int child) const
{
if (!slider()->isVisible())
return QString();
switch (t) {
case Value:
if (!child || child == 2)
return QString::number(slider()->value());
return QString();
case Name:
switch (child) {
case PageLeft:
return slider()->orientation() == Qt::Horizontal ?
QSlider::tr("Page left") : QSlider::tr("Page up");
case Position:
return QSlider::tr("Position");
case PageRight:
return slider()->orientation() == Qt::Horizontal ?
QSlider::tr("Page right") : QSlider::tr("Page down");
}
break;
default:
break;
}
return QAccessibleAbstractSlider::text(t, child);
} Функция slider() возвращает указатель на QSlider интерфейса. Некоторые значения оставлены для реализации суперкласса. Не все значения подходят для всех доступных объектов, как вы можете видеть для случая QAccessible::Value. Для тех значений, где не может быть предоставлен соответствующий текст, вы должны просто вернуть пустую строку.
Реализация функции role() проста:
QAccessible::Role QAccessibleSlider::role(int child) const
{
switch (child) {
case PageLeft:
case PageRight:
return PushButton;
case Position:
return Indicator;
default:
return Slider;
}
} Функция role должна быть переопределена всеми объектами и описывает роль самих себя и дочерних элементов, которые не предоставляют собственных доступных интерфейсов.
Далее, доступный интерфейс должен вернуть состояния, в которых может находиться ползунок. Мы рассмотрим части реализации state() для демонстрации того, как обрабатывается всего лишь несколько состояний:
QAccessible::State QAccessibleSlider::state(int child) const
{
const State parentState = QAccessibleAbstractSlider::state(0);
...
switch (child) {
case PageLeft:
if (slider->value() <= slider->minimum())
state |= Unavailable;
break;
case PageRight:
if (slider->value() >= slider->maximum())
state |= Unavailable;
break;
case Position:
default:
break;
}
return state;
} Реализация state() в суперклассе использует реализацию QAccessibleInterface::state(). Мы просто должны отключить кнопки, если ползунок находится в минимальном или максимальном значении.
Мы теперь предоставили клиентам информацию о ползунке. Для того, чтобы клиенты могли изменить ползунок — например, изменить его значение — мы должны предоставить информацию о действиях, которые могут быть выполнены, и выполнить их по запросу. Мы обсудим это в следующем разделе.
Обработка запросов на действия от клиентов
Приложения могут раскрывать действия, которые могут быть вызваны клиентом. Для поддержки действий в объекте необходимо унаследовать QAccessibleActionInterface.
Интерактивные элементы должны раскрывать функциональность, вызываемую взаимодействием с мышкой, например. Кнопка, например, должна реализовать действие щелчка.
Установка фокуса — еще одно действие, которое должно быть реализовано для виджетов, которые могут принимать фокус.
Вам нужно повторно реализовать actionNames(), чтобы вернуть список всех действий, которые поддерживает объект. Этот список не должен быть локализован.
Существуют две функции, предоставляющие информацию о действиях, которые должны возвращать локализованные строки: localizedActionName() и localizedActionDescription(). Эти функции могут использоваться клиентом для представления действий пользователю. В общем случае имя должно быть лаконичным и состоять только из одного слова, например, «нажать».
Существует список стандартных имен действий и локализаций, которые следует использовать, когда действие подходит. Это облегчает клиентам понимание семантики, и Qt будет пытаться правильно раскрыть их на разных платформах.
Конечно, действие также должно иметь способ запуска. doAction() должно вызвать действие, как указано по имени и описанию.
Чтобы увидеть примеры реализации действий и методов, можно изучить реализации стандартных виджетов Qt, таких как QAccessiblePushButton.
Реализация доступных плагинов
В этом разделе мы объясним процедуру реализации доступных плагинов для ваших интерфейсов. Плагин — это класс, хранящийся в динамической библиотеке, который может загружаться во время выполнения. Распространять интерфейсы в виде плагинов удобно, так как они будут загружаться только при необходимости.
Создание доступного плагина достигается путем наследования от QAccessiblePlugin, определения поддерживаемых имен классов в описании плагина в формате JSON и переопределения create() из QAccessiblePlugin. Файл .pro должен быть изменен для использования шаблона плагина, а библиотека, содержащая плагин, должна быть размещена в пути, по которому Qt ищет доступные плагины.
Мы рассмотрим реализацию SliderPlugin, который представляет собой доступный плагин, создающий интерфейс QAccessibleSlider из QAccessibleWidget Example. Мы начнем с функции key().
QStringList SliderPlugin::keys() const
{
return QStringList() << QLatin1String("QSlider");
} Нам просто нужно вернуть имя класса единственного интерфейса, для которого наш плагин может создать доступный интерфейс. Плагин может поддерживать любое количество классов; просто добавьте больше имен классов в список строк. Мы переходим к функции create().
QAccessibleInterface *SliderPlugin::create(const QString &classname, QObject *object)
{
QAccessibleInterface *interface = 0;
if (classname == QLatin1String("QSlider") && object && object->isWidgetType())
interface = new QAccessibleSlider(static_cast<QWidget *>(object));
return interface;
} Мы проверяем, является ли запрашиваемый интерфейс для QSlider; если да, мы создаем и возвращаем интерфейс для него. Обратите внимание, что object всегда будет экземпляром classname. Вы должны вернуть 0, если вы не поддерживаете класс. updateAccessibility() проверяет доступные плагины доступности, пока не найдет тот, который не возвращает 0.
Наконец, вам необходимо включить макросы в файл cpp:
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.Accessibility.SliderPlugin" FILE "slider.json") Макрос Q_PLUGIN_METADATA экспортирует плагин в классе SliderPlugin в библиотеку acc_sliderplugin. Первый аргумент — IID плагина, второй — необязательный файл json, содержащий метаданные плагина. Дополнительную информацию о плагинах можно найти в обзоре документов о плагинах.
Неважно, нужно ли вам, чтобы плагин статически или динамически подключался к приложению.
Реализация фабрик интерфейсов
Если вы не хотите предоставлять плагины для своих интерфейсов доступности, вы можете использовать фабрику интерфейсов (QAccessible::InterfaceFactory), что является рекомендуемым способом предоставления доступных интерфейсов в статически подключаемом приложении.
Фабрика — это указатель на функцию, которая принимает те же параметры, что и QAccessiblePlugin `create()` — `QString` и `QObject`. Она также работает так же. Вы устанавливаете фабрику с помощью функции installFactory(). Мы приводим пример создания фабрики для интерфейса QAccessibleSlider:
QAccessibleInterface *sliderFactory(const QString &classname, QObject *object)
{
QAccessibleInterface *interface = 0;
if (classname == QLatin1String("QSlider") && object && object->isWidgetType())
interface = new QAccessibleSlider(static_cast<QWidget *>(object));
return interface;
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QAccessible::installFactory(sliderFactory);
...
}
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.13/accessible-qwidget.html