本页内容

QWidget 应用程序的无障碍支持

简介

我们将重点介绍 Qt 无障碍接口QAccessibleInterface 以及如何使应用程序支持无障碍访问。

基于 QWidget 的应用程序中的无障碍功能

在与辅助技术进行通信时,我们需要以辅助技术能够理解的方式描述 Qt 的用户界面。 Qt 应用程序使用QAccessibleInterface 来公开各个 UI 元素的信息。目前,Qt 支持其控件及控件部件(例如滑块手柄),但如有必要,该接口也可针对任何QObject 进行实现。QAccessible 包含用于描述 UI 的枚举类型。本文将详细探讨这些枚举类型。

UI 的结构以QAccessibleInterface 子类的树形结构表示。这通常与构成应用程序 UI 的 QWidget 层次结构相对应。

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

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

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

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

可访问对象树

如前所述,会根据应用程序的无障碍对象构建一棵树结构。通过遍历该树,客户端可以访问用户界面中的所有元素。对象关系为客户端提供了关于用户界面的信息。例如,滑块控件是其所属滑块的子对象。QAccessible::Relation 描述了客户端可以向对象查询的各种关系。

请注意,Qt 的QObject 树与可访问对象树之间并不存在直接映射关系。例如,滚动条手柄虽然是可访问对象,但在 Qt 中却既不是小部件(widget),也不是对象。

辅助技术客户端可以通过树中的根对象(即QApplication )访问辅助功能对象树。它们可以使用QAccessibleInterface::parent()、QAccessibleInterface::childCount()和QAccessibleInterface::child()函数在树中导航。

Qt Widgets 为其控件以及Qt Quick Controls 提供了可访问性接口。可以通过 QAccessible::queryInterface() 请求任何QObject 子类的接口。如果未定义更专门的接口,则会提供默认实现。 辅助技术客户端无法为没有等效QObject 的可访问对象获取接口,例如滚动条控件;但这些对象会通过父级可访问对象的接口以普通对象的形式出现,例如,您可以使用QAccessibleInterface::relations() 查询它们之间的关系。

为便于说明,我们展示了一张可访问对象树的图示。树形图下方是一张列举对象关系示例的表格。

树形图展示了可访问对象的层次结构,其中 QApplication 为根节点,QSlider 拥有 PageLeft、Position 和 PageRight 这三个子节点

标签自上而下的顺序依次为:QAccessibleInterface 类名、提供接口的控件,以及该对象的Role 属性。Position、PageLeft和PageRight分别对应滑块手柄、滑块槽左侧和滑块槽右侧。这些可访问对象没有对应的QObject 。

源对象目标对象关系
滑块指示器控制器
指示器滑块受控
滑块应用父级
应用程序滑块子控件
按钮指示器同级

QAccessible 的静态函数

辅助功能由QAccessible 的静态函数管理,我们稍后将对此进行探讨。这些函数会生成QAccessible 接口,构建对象树,并启动与MSAA或其他平台特定技术的连接。如果您仅对如何使应用程序支持辅助功能感兴趣,可以跳过本节,直接阅读“实现辅助功能”部分。

当调用setRootObject() 时,客户端与服务器之间的通信即被建立。此操作在QApplication 实例被实例化时自动完成,您无需手动执行。

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

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

为 QObject 生成可访问性接口的工厂是一个QAccessible::InterfaceFactory 类型的函数。系统中可以安装多个工厂。最后安装的工厂将是系统首先请求接口的对象。queryAccessibleInterface() 会利用这些工厂为QObject生成接口。通常情况下,您无需关注工厂,因为您可以实现能够生成接口的插件。 我们稍后将分别给出这两种方法的示例。

实现无障碍功能

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

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

  • Qt 已为其自身的控件实现了无障碍功能。因此,我们建议您尽可能使用 Qt Widgets。
  • 对于每个希望向辅助功能客户端开放的元素,都需要实现一个QAccessibleInterface 。
  • 您需要从所实现的自定义用户界面元素中发送无障碍事件。

一般而言,建议您对 MSAA 有一定了解,因为 Qt 的无障碍支持最初就是为 MSAA 构建的。您还应研究 `QAccessible` 的枚举值,这些枚举值描述了您需要考虑的角色、操作、关系和事件。

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

可以通过继承QAccessibleInterface 并实现其纯虚函数来实现接口。但在实际应用中,通常更推荐继承QAccessibleObject 或QAccessibleWidget ,因为它们已为您实现了部分功能。在下一节中,我们将通过继承QAccessibleWidget 类,展示一个为控件实现辅助功能的示例。

QAccessibleObject 和 QAccessibleWidget 便利类

在为小部件实现辅助功能接口时,通常应继承QAccessibleWidget ,这是一个用于小部件的便利类。另一个可用的便利类是QAccessibleObject (QAccessibleWidget 类便是从其继承而来),它为 QObject 实现了部分接口功能。

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)"));
}

滑块是一个复杂的控件,它为其可访问子控件充当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()。客户端可以使用这些函数向用户展示操作。通常,名称应简洁且仅由一个单词组成,例如“press”。

提供了一份标准操作名称及其本地化版本的列表,当操作符合条件时应使用该列表。这有助于客户端更轻松地理解语义,且 Qt 会尝试在不同平台上正确呈现这些名称。

当然,该操作还需要一种触发方式。doAction() 应根据名称和描述调用该操作。

要查看有关如何实现操作和方法的示例,可以参考 Qt Widgets 标准控件(例如 QAccessiblePushButton)的实现。

实现辅助功能插件

在本节中,我们将说明为您的接口实现无障碍插件的流程。插件是一个存储在共享库中的类,可在运行时加载。将接口作为插件分发非常方便,因为它们只会在需要时被加载。

创建可访问性插件的方法是继承QAccessiblePlugin ,在插件的 JSON 描述中定义支持的类名,并重写QAccessiblePlugin 中的create()方法。必须修改.pro 文件以使用该插件模板,并且包含该插件的库必须放置在 Qt 搜索可访问性插件的路径上。

我们将详细讲解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 ),这是在静态链接的应用程序中提供辅助功能接口的推荐方法。

工厂是一个函数指针,该函数的参数与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);
    ...
}

Accessible

启用 QML 项的无障碍功能

QAccessible

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

QAccessibleActionInterface

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

QAccessibleAnnouncementEvent

用于请求辅助技术朗读给定消息

QAccessibleAttributesInterface

实现对可访问对象报告属性的支持

QAccessibleEditableTextInterface

实现了对包含可编辑文本的对象的支持

QAccessibleEvent

无障碍通知的基类

QAccessibleInterface

定义了一个接口,用于公开可访问对象的相关信息

QAccessibleObject

为 QObject 实现 QAccessibleInterface 接口的部分功能

QAccessiblePlugin

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

QAccessibleSelectionInterface

实现了对选择处理的支持

QAccessibleStateChangeEvent

向辅助功能框架通知对象状态已发生变化

QAccessibleTableCellInterface

实现了对 IAccessibleTable2 Cell 接口的支持

QAccessibleTableInterface

实现了对 IAccessibleTable2 接口的支持

QAccessibleTableModelChangeEvent

表示表格、列表或树中发生变化,即单元格被添加或移除。如果该变化影响了多行,firstColumn 和 lastColumn 将返回 -1。同样,对于列,行相关函数也可能返回 -1

QAccessibleTextCursorEvent

通知光标移动

QAccessibleTextInsertEvent

通知文本被插入

QAccessibleTextInterface

实现了对文本处理的支持

QAccessibleTextRemoveEvent

通知文本被删除

QAccessibleTextSelectionEvent

指示对象的文本选择发生变化

QAccessibleTextUpdateEvent

通知文本变更。此功能适用于支持可编辑文本的可访问控件,例如行编辑框。例如,当通过粘贴新文本替换选定文本的一部分,或在编辑器的覆盖模式下,都会触发此事件

QAccessibleValueChangeEvent

描述可访问对象的值发生变化

QAccessibleValueInterface

实现对可操作值的对象的支持

QAccessibleViewportInterface

实现对视口的支持

QAccessibleWidget

为 QWidgets 实现 QAccessibleInterface

© 2026 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.