Доступность для приложений 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.
Клиенты AT имеют доступ к дереву доступных объектов через корневой объект в дереве, который является QApplication. Они могут перемещаться по дереву с помощью функций QAccessibleInterface::parent(), QAccessibleInterface::childCount() и QAccessibleInterface::child().
Qt предоставляет интерфейсы доступности для своих виджетов и для Qt Quick Controls. Интерфейсы для любого подкласса QObject могут быть запрошены через QAccessible::queryInterface(). Если не определен более специализированный интерфейс, предоставляется реализация по умолчанию. Клиент AT не может получить интерфейс для доступных объектов, у которых нет эквивалента QObject, например, маркеры полосы прокрутки, но они отображаются как обычные объекты через интерфейсы родительских доступных объектов, например, вы можете запросить их отношения с помощью QAccessibleInterface::relations().
Для иллюстрации мы представляем изображение дерева доступного объекта. Ниже дерева приведена таблица с примерами отношений объектов.
Метки в порядке сверху вниз: имя класса QAccessibleInterface, виджет, для которого предоставлен интерфейс, и роль объекта. Позиция, PageLeft и PageRight соответствуют ползунку, левой и правой части желоба ползунка соответственно. Эти доступные объекты не имеют эквивалентного QObject.
| Исходный объект | Целевой объект | Связь |
|---|---|---|
| Ползунок | Индикатор | Контроллер |
| Индикатор | Ползунок | Управляемый |
| Ползунок | Приложение | Предок |
| Приложение | Ползунок | Потомок |
| Кнопка | Индикатор | Брат |
Статические функции QAccessible
Доступность управляется статическими функциями QAccessible, которые мы рассмотрим вскоре. Они производят интерфейсы QAccessible, строят дерево объектов и инициируют подключение к MSAA или другим платформ-специфичным технологиям. Если вас интересует только то, как сделать приложение доступным, вы можете пропустить этот раздел и перейти к Реализации доступности.
Взаимодействие между клиентами и сервером начинается при вызове setRootObject(). Это делается при создании экземпляра QApplication, и вам не нужно делать это самостоятельно.
Когда QObject вызывает updateAccessibility(), клиенты, которые следят за событиями, уведомляются об изменении. Эта функция используется для отправки событий технологиям вспомогательных средств, и доступные события публикуются функцией updateAccessibility().
queryAccessibleInterface() возвращает интерфейсы доступности для QObject. Все виджеты в 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, которое имеет следующие значения: PageLeft (прямоугольник слева от ползунка), 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. Мы начнём с функции 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's 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.12/accessible-qwidget.html