Spec-Zone.ru › Qt 5.15

Доступность для приложений QWidget

Введение

Мы сосредоточимся на интерфейсе Qt для доступности QAccessibleInterface и том, как сделать приложения доступными.

Доступность в приложениях на основе QWidget

При взаимодействии с технологиями вспомогательных средств мы должны описывать пользовательский интерфейс Qt таким образом, чтобы они могли его понять. Приложения Qt используют QAccessibleInterface для раскрытия информации об отдельных элементах пользовательского интерфейса. В настоящее время Qt поддерживает свои виджеты и части виджетов, например, ползунки, но интерфейс также может быть реализован для любого QObject, если это необходимо. QAccessible содержит перечисления, описывающие пользовательский интерфейс. Мы рассмотрим эти перечисления в ходе этого документа.

Структура пользовательского интерфейса представлена в виде дерева подклассов QAccessibleInterface. Это часто отражает иерархию QWidget, составляющих пользовательский интерфейс приложения.

Серверы уведомляют клиентов о изменениях в объектах через 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() возвращает интерфейсы доступности для QObject. Все виджеты в Qt предоставляют интерфейсы; если вам нужны интерфейсы для управления поведением других подклассов QObject, вы должны реализовать интерфейсы самостоятельно, хотя класс-утилита QAccessibleObject реализует часть функциональности за вас.

Фабрика, производящая интерфейсы доступности для QObjects, является функцией типа QAccessible::InterfaceFactory. Можно установить несколько фабрик. Последняя установленная фабрика будет первой, к которой обратятся для получения интерфейсов. queryAccessibleInterface() использует фабрики для создания интерфейсов для QObject. Обычно вам не нужно беспокоиться о фабриках, так как вы можете реализовать плагины, которые генерируют интерфейсы. Мы предоставим примеры обоих подходов позже.

Реализация доступности

Для обеспечения поддержки доступности для виджета или другого элемента пользовательского интерфейса необходимо реализовать 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(), соответствующие обычному виджету.
  • Устанавливает состояния states, общие для всех виджетов.

Пример 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 в 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.15/accessible-qwidget.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API