QWidget 应用程序的辅助功能

简介

我们将重点介绍 Qt 可访问性界面QAccessibleInterface 以及如何使应用程序具有可访问性。

基于 QWidget 应用程序的辅助功能

当我们与辅助技术交流时,需要用他们能理解的方式来描述 Qt 的用户界面。Qt 应用程序使用QAccessibleInterface 来公开各个用户界面元素的信息。目前,Qt 为其 Widget 和 Widget 部件(如滑块手柄)提供支持,但如果有必要,也可以为任何QObject 实现该界面。QAccessible 包含描述用户界面的枚举。我们将在本文档中研究这些枚举。

用户界面的结构以QAccessibleInterface 子类的树形结构表示。这通常是构成应用程序用户界面的 QWidgets 层次结构的镜像。

服务器通过updateAccessibility() 发送事件通知客户端对象的变化,客户端则注册接收事件。可用事件由QAccessible::Event 枚举定义。然后,客户端可以通过QAccessible::queryAccessibleInterface() 查询生成事件的对象。

QAccessible 中的成员和枚举用于描述可访问的对象:

  • Role:描述对象在用户界面中扮演的角色,例如是窗口、文本编辑器还是表格中的单元格。
  • Relation:描述对象层次结构中对象之间的关系。
  • State:对象可以处于多种不同状态。例如,对象是否禁用、是否有焦点或是否提供弹出菜单。

客户端还可以获取对象的内容,例如按钮的文本;对象提供由QAccessible::Text 枚举定义的字符串,这些字符串可提供有关内容的信息。

可访问对象树

如前所述,树形结构由应用程序的可访问对象构建而成。通过树状结构导航,客户端可以访问用户界面中的所有元素。对象关系为客户端提供了用户界面的相关信息。例如,滑块手柄是其所属滑块的子对象。QAccessible::Relation 描述了客户端可以向对象询问的各种关系。

请注意,Qt XMLQObject 树与可访问对象树之间没有直接映射关系。例如,滚动条手柄是可访问对象,但不是 Qt 中的部件或对象。

AT-Clients 可以通过无障碍对象树中的根对象(即QApplication )访问无障碍对象树。他们可以使用QAccessibleInterface::parent(),QAccessibleInterface::childCount() 和QAccessibleInterface::child() 函数浏览无障碍对象树。

Qt 为其 Widget 和Qt Quick Controls 提供了可访问接口。可通过 QAccessible::queryInterface() 请求任何QObject 子类的接口。如果没有定义更专门的接口,则会提供默认实现。AT-Client 无法为没有对应QObject 的可访问对象获取接口,例如滚动条手柄,但它们会通过父可访问对象的接口以普通对象的形式出现,例如,您可以使用QAccessibleInterface::relations() 查询它们之间的关系。

为了说明问题,我们将展示一个可访问对象树的图像。树下有一个表格,其中列出了对象关系的示例。

自上而下的标签依次为:QAccessibleInterface 类名、提供接口的部件以及对象的Role 。Position(位置)、PageLeft(左页)和 PageRight(右页)分别对应滑块手柄、左滑块槽和右滑块槽。这些可访问对象没有对应的QObject

源对象目标对象关系
滑块指示器控制器
指示器滑块受控
滑块应用祖先
应用滑块子应用程序
按钮指示器同胞

静态 QAccessible 函数

可访问性是由QAccessible 的静态函数管理的,我们很快就会研究这些函数。它们生成QAccessible 接口,构建对象树,并启动与 MSAA 或其他特定平台技术的连接。如果您只想了解如何使您的应用程序具有可访问性,您可以安全地跳过这一部分,直接进入 "实现可访问性"。

客户端与服务器之间的通信是在调用setRootObject() 时启动的。这在QApplication 实例实例化时完成,您不必亲自动手。

QObject 调用updateAccessibility() 时,正在监听事件的客户端会收到更改通知。该函数用于向辅助技术发布事件,可访问的eventsupdateAccessibility() 发布。

queryAccessibleInterface() 返回QObjects 的可访问接口。Qt XML 中的所有 Widgets 都提供了接口;如果您需要接口来控制其他QObject 子类的行为,您必须自己实现这些接口,尽管QAccessibleObject 方便类为您实现了部分功能。

为 QObjects 生成可访问性接口的工厂是QAccessible::InterfaceFactory 类型的函数。可以安装多个工厂。最后安装的工厂将首先被要求提供接口。queryAccessibleInterface QObject(通常情况下,您无需关注工厂,因为您可以实现生成接口的插件。稍后我们将举例说明这两种方法。

实现辅助功能

要为部件或其他用户界面元素提供可访问性支持,您需要实现QAccessibleInterface ,并将其发布在QAccessiblePlugin 中。也可以将接口编译到应用程序中,并为其提供QAccessible::InterfaceFactory 。如果是静态链接或不想增加插件的复杂性,可以使用工厂。例如,如果您要提供第三方库,这将是一个优势。

所有部件和其他用户界面元素都应具有接口和插件。如果您希望您的应用程序支持辅助功能,您需要考虑以下几点:

  • Qt 已经为自己的 widget 实现了辅助功能。因此,我们建议您尽可能使用 Qt Widget。
  • 您需要为每个要提供给辅助功能客户端的元素实现QAccessibleInterface
  • 您需要从实现的自定义用户界面元素发送辅助功能事件。

一般来说,建议您对 MSAA 有一定的了解,Qt 的辅助功能支持最初就是为 MSAA 而构建的。您还应学习QAccessible 的枚举值,它描述了您需要考虑的角色、操作、关系和事件。

请注意,您可以研究 Qt 的 Widgets 是如何实现其辅助功能的。MSAA 标准的一个主要问题是接口的实现方式往往不一致。这给客户带来了困难,并经常导致对对象功能的猜测。

可以通过继承QAccessibleInterface 并实现其纯虚拟函数来实现接口。但在实践中,通常更倾向于继承QAccessibleObjectQAccessibleWidget ,它们会为您实现部分功能。在下一节中,我们将举例说明如何通过继承QAccessibleWidget 类来实现 widget 的可访问性。

QAccessibleObject 和 QAccessibleWidget 方便类

在为部件实现可访问性接口时,通常会继承QAccessibleWidget ,这是一个用于部件的方便类。另一个由QAccessibleWidget 继承的方便类是QAccessibleObject ,它实现了 QObjects 的部分接口。

QAccessibleWidget 提供以下功能:

  • 处理树的导航和对象的命中测试。
  • 它可处理所有QWidgets 通用的事件、角色和操作。
  • 处理可在所有部件上执行的操作和方法。
  • 它通过rect() 计算边界矩形。
  • 它给出适合通用 widget 的text() 字符串。
  • 它设置所有 widget 通用的states

QAccessibleWidget 示例

我们将展示如何为 Qt 的一个标准 widget 实现可访问性,而不是创建一个自定义 widget 并为其实现接口:QSlider 。可访问接口 QAccessibleSlider 继承自 QAccessibleAbstractSlider,而 QAccessibleAbstractSlider 又继承自QAccessibleWidget 。阅读本节内容时,您无需查看 QAccessibleAbstractSlider 类。如果你想看一看,Qt 所有可访问接口的代码都可以在 qtbase/src/widgets/accessible 中找到。下面是 QAccessibleSlider 的构造函数:

QAccessibleSlider::QAccessibleSlider(QWidget *w)
: QAccessibleAbstractSlider(w)
{
    Q_ASSERT(slider());
    addControllingSignal(QLatin1String("valueChanged(int)"));
}

滑块是一个复杂控件,其功能是为其可访问子控件提供Controller 。接口必须知道这种关系(对于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);
    }
    ...

我们省略了函数的第一部分,它使用当前的style 计算滑块手柄的边界矩形;该矩形存储在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;
    }
}

所有对象都应重新实现角色函数,并描述自己的角色和未提供自身可访问接口的子对象的角色。

其次,可访问接口需要返回滑块所处的states 。我们将查看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 描述中定义支持的类名,并从QAccessiblePlugin 重新实现create() 。必须修改.pro 文件以使用插件模板,包含插件的库必须放在 Qt XML 搜索可访问插件的路径上。

我们将学习SliderPlugin 的实现,它是一个可访问插件,可生成QAccessibleWidget 示例中的 QAccessibleSlider 接口。我们从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 ),这是在静态链接应用程序中提供无障碍接口的推荐方式。

接口工厂是一个函数指针,它接收与QAccessiblePlugincreate() 相同的参数--一个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);
    ...
}

Accessible

启用 QML 项目的可访问性

QAccessible

与可访问性相关的枚举和静态函数

QAccessibleActionInterface

在接口中实现对可调用操作的支持

QAccessibleAnnouncementEvent

用于请求辅助技术公布给定信息

QAccessibleAttributesInterface

支持报告无障碍对象的属性

QAccessibleEditableTextInterface

为具有可编辑文本的对象提供支持

QAccessibleEvent

无障碍通知基类

QAccessibleInterface

定义了一个可公开无障碍对象信息的接口

QAccessibleObject

为 QObjects 实现 QAccessibleInterface 的部分功能

QAccessiblePlugin

为插件提供用户界面元素无障碍信息的抽象基类

QAccessibleSelectionInterface

实现对选择处理的支持

QAccessibleStateChangeEvent

通知无障碍框架对象的状态已经改变

QAccessibleTableCellInterface

实现对 IAccessibleTable2 Cell 接口的支持

QAccessibleTableInterface

实现对 IAccessibleTable2 接口的支持

QAccessibleTableModelChangeEvent

表示表格、列表或树状结构中单元格的添加或删除发生了变化。如果更改影响了若干行,则 firstColumn 和 lastColumn 将返回-1。同样,对于列,行函数也会返回 -1

QAccessibleTextCursorEvent

通知光标移动

QAccessibleTextInsertEvent

通知插入文本

QAccessibleTextInterface

支持文本处理

QAccessibleTextRemoveEvent

通知删除文本

QAccessibleTextSelectionEvent

通知对象的文本选择发生变化

QAccessibleTextUpdateEvent

通知文本更改。这适用于支持可编辑文本(如行编辑)的访问器。例如,当粘贴新文本或在编辑器的覆盖模式下替换部分选定文本时,就会发生该事件。

QAccessibleValueChangeEvent

描述可访问对象值的变化

QAccessibleValueInterface

为操作值的对象提供支持

QAccessibleWidget

为 QWidgets 实现 QAccessibleInterface

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.