Доступность для приложений QWidget
Введение
Мы сосредоточимся на интерфейсе Qt для доступности QAccessibleInterface и на том, как сделать приложения доступными.
Доступность в приложениях на основе QWidget
Когда мы взаимодействуем с технологиями вспомогательных средств, нам нужно описать пользовательский интерфейс Qt таким образом, чтобы они могли его понять. Приложения Qt используют QAccessibleInterface для раскрытия информации об отдельных элементах пользовательского интерфейса. В настоящее время Qt предоставляет поддержку для своих виджетов и частей виджетов, например, ползунков, но при необходимости интерфейс можно реализовать и для любого QObject. QAccessible содержит перечисления, описывающие пользовательский интерфейс. В ходе этого документа мы рассмотрим перечисления.
Структура пользовательского интерфейса представлена в виде дерева подклассов QAccessibleInterface. Это часто отражение иерархии QWidgets, составляющих пользовательский интерфейс приложения.
Серверы уведомляют клиентов о изменениях в объектах с помощью updateAccessibility() путём отправки событий, и клиенты регистрируются для получения событий. Доступные события определяются перечислением QAccessible::Event. Клиенты могут затем запросить объект, сгенерировавший событие, с помощью QAccessible::queryAccessibleInterface().
Члены и перечисления в QAccessible используются для описания доступных объектов:
- Role: Описывает роль, которую объект выполняет в пользовательском интерфейсе, например, если это окно, поле ввода текста или ячейка в таблице.
- Relation: Описывает отношения между объектами в иерархии объектов.
- State: Объекты могут находиться в нескольких различных состояниях. Примеры состояний — объект отключён, у него фокус или он предоставляет всплывающее меню.
Клиенты также имеют возможность получить содержимое объектов, например, текст кнопки; объект предоставляет строки, определенные перечислением 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, виджет, для которого предоставляется интерфейс, и Role объекта. Position, 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, которое имеет следующие значения: 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 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-файл, содержащий метаданные плагина. Для получения дополнительной информации о плагинах вы можете обратиться к документу обзора плагинов plugins-howto.
Не имеет значения, нужно ли связывать плагин статически или динамически с приложением.
Реализация фабрик интерфейсов
Если вы не хотите предоставлять плагины для ваших интерфейсов доступности, вы можете использовать фабрику интерфейсов (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-6.2/accessible-qwidget.html