跳到主要内容

NVDA 内幕故事5:控件名称和角色的来源是什么?

· 阅读需 17 分钟
Joseph Lee
The author of NVDA-Internals
Inkydragon
Opensource contribution @ Github

这个内幕故事是一系列帖子的一部分,将介绍 NVDA 对象,这是使屏幕阅读成为可能的 NVDA 的核心部分。 参与 NVDA 用户列表的人们知道,我可以对一个事情继续讲上几个小时,而 NVDA 对象就是其中之一。

然而,由于 NVDA 对象的故事将涉及到计算机编程中的一个关键概念,需要用多个帖子来详细解释(即面向对象编程), 所以我将整个 NVDA 对象的讨论分成多个部分。 此外,我希望在我写的大部分内容中都是实用的,特别是在回应来自这个论坛和其他地方的帖子时。

对于我们的新朋友们: 如果你是一个新的 NVDA 用户,或者对屏幕阅读器也是新手,我即将描述的部分可能会让你感到不知所措,因为它将涉及屏幕阅读器的内部工作原理。 作为一个即将踏上不同人生旅程的开发者,我觉得这是一个机会,我可以传授我对屏幕阅读的了解,以帮助下一批用户和有意成为开发者的人。 我会尽我所能让这些信息易于理解。

系列文章

NVDA对象系列文章包括:

  1. 控件名称和角色是从哪里来的(本帖): 我将讨论NVDA如何准确地了解您正在处理的控件,这源自最近关于应用程序中控件标签的讨论。
  2. “实际”解剖NVDA对象: 这将变得非常极客,因为我将讨论类、获取器和设置器、属性、继承和抽象,以及讨论组成NVDA对象的内容,希望以一种易于理解的方式。
  3. 叠加类(非常接近附加组件开发领域): 许多附加组件的核心,提供对控件和应用程序的改进支持。只有在理解了NVDA对象的组织方式之后,这部分才会有意义。

获取控件名称与角色

作为第一步,让我们找出NVDA如何在不进行屏幕抓取的情况下宣布控件标签(名称)和角色(提示:辅助功能API)

几天前,在这个帖子中有一个关于NVDA在Web浏览器应用程序中遇到齿轮图标时会说什么的问题,答案是“取决于应用程序开发者的说法”。 不久之后,在谈论获取系统规格时,Brian V问我们是否可以通过查看角色来确定子对象的存在,我回答说,“也许将角色视为一种启发式方法”。

但是这些与NVDA对象有什么关系呢?进入辅助功能API和指针(是的,对于来自旧编程语言的人来说,记得指针的概念吗?)。 不,NVDA不会通过扫描屏幕来确定控件的角色,也不会分析图标来找出标签(名称)是什么... 嗯,大部分情况下是这样的(有时NVDA确实依赖屏幕信息,称为“显示模型”来确定控件是什么,但这是在某些特殊情况下)。 所以,为了给你提供关于链接讨论的“真实”答案:NVDA向辅助功能API请求诸如控件名称(标签)和角色以及其他属性的信息。 NVDA是如何做到这一点(内部)是本文其余部分的重点。

大多数情况下,屏幕上的控件具有各种属性:名称、角色、状态、位置等等。 但是屏幕阅读器是如何获取这些信息的呢?这是通过请求辅助功能API来按需获取信息实现的。 将辅助功能API(如Microsoft Active Accessibility和UI Automation) 视为屏幕阅读器和应用程序之间的“中间人”,它们保持着交互的进行。 如果你仔细思考这个陈述,你可能会得出这样的结论: 控件属性最终是由应用程序开发人员设置的,告诉应用程序和辅助功能API以特定的方式公开属性,供屏幕阅读器使用,并最终以特定的方式向用户宣布属性。 但这只是其中的一部分。

你可能会问,既然NVDA似乎知道各种辅助功能API,那么屏幕阅读器如何识别哪个辅助功能API可以用来询问控件的名称和角色呢? 这发生在创建NVDA对象时(一个例子是处理焦点变化事件),以表示你正在处理的屏幕控件。 NVDA执行一系列测试来确定与给定控件交互时要使用哪个辅助功能API(其中之一是通过Windows API公开的窗口类名), 并根据测试结果构建一个适当的NVDA对象,表示一个API类(API类是给定辅助功能API的一组NVDA对象的集合; 我将在以后的Inside Story帖子中回到这个概念)。 但这还不是完整的故事。

因此,NVDA构建了一个表示焦点控件的NVDA对象,那么NVDA如何获取其名称和角色呢? 这需要两个部分:对象属性和辅助功能API代表。 每个NVDA对象都派生自一个适当命名为“NVDAObject”(NVDAObjects.NVDAObject)的抽象类,定义了一种获取名称和角色等属性的基本实现方式。 这些被定义为“_get_property”(一个getter方法),例如_get_name用于获取控件名称,_get_role用于获取角色。 这样做可以使NVDA通过属性访问(例如object.something,例如object.name用于控件标签,object.role用于角色)来查询属性。 但由于基本NVDA对象只是一个蓝图(在技术上称为“抽象基类”),当属性被询问时,它要么提供默认实现,要么什么都不做,因此不能单独用于宣布控件标签和角色。 这就是为什么几乎所有的NVDA对象都从基本NVDA对象中获得力量,并被视为IAccessible/IAccessible2/JAB/UIA对象的原因, 这是另一篇Inside Story文章的主题,它将介绍一些面向对象编程的概念。

那么,是什么使得NVDA能够与不同的API一起使用以获取控件属性? 通过一个称为辅助功能对象/元素的辅助功能API代表,从技术上讲,这些是指针 (指针是指导程序指向特定内存位置/地址的东西;为什么这是如此重要的材料超出了本论坛的范围,因为它涉及到编程和计算机科学的各个方面)。 几乎所有的NVDA对象都必须有一个指向辅助功能API对象或元素的指针作为属性,以便进行“魔术般”的控件属性公告(当然也有例外,包括窗口对象和基本的NVDA对象)。 例如,IAccessible对象包括IAccessibleObject,它是指向表示当前控件的MSAA对象的指针;在UIA世界中,这是UIA元素。 尽管辅助功能API对象的操作方式不同,可能以不同的方式公开相同的属性,但NVDA可以跨API工作,只是因为相同的代码可以跨API工作以获取相同的属性。

示例:获取桌面图标的名称

举个例子,假设你将系统焦点移动到桌面(Windows+D),NVDA会宣布所聚焦的桌面图标的名称。 但是,NVDA是如何在不分析图标的情况下做到这一点的呢?这就是方法:

  1. 系统焦点事件由桌面图标触发。
  2. NVDA识别焦点事件并确定它正在处理的控件类型。
  3. 经过一些测试,NVDA发现它正在与一个MSAA控件一起工作,因此它构建了一个IAccesibleNVDA对象(NVDAObjects.IAccessible.IAccessible)。 对于MSAA对象来说,一个关键属性是IAccessibleObject,因此NVDA也会获取到它的指针。
  4. 即使它是一个MSAA对象,NVDA知道它是一个自定义的MSAA对象,所以它调用 NVDAObjects.IAccessible.IAccessible.findOverlayClasses 方法来确定该做什么, 最终得知它是一个 Dynamic_SysListView32EmittingDuplicateFocusEventsListItemIAccessible 对象(不用担心这个冗长的名称)。
  5. 现在已经创建了一个合适的MSAA NVDA对象,NVDA现在要求IAccessibleObject返回诸如名称和角色之类的属性。 在MSAA中,IAccessibleObject的“accName”属性保存控件名称属性,“accRole”记录对象角色。分别从IAccesible NVDA对象类中定义的_get_name和_get_role getter方法中检索这些属性。
  6. 对于控制角色,会执行额外的步骤让NVDA以友好的方式呈现角色。NVDA的MSAA API处理程序(IAccessibleHandler)具有MSAA角色到NVDA角色的映射,会查询该映射以返回“NVDA角色”。
  7. 获取姓名和角色的步骤用于检索其他属性,例如状态,并将所有这些呈现给用户。

示例:搜索框

作为另一个例子,假设您打开Windows 10/11的开始菜单,并且搜索框获得焦点。 与上面的例子不同的是,NVDA正在处理一个UIA对象。 这需要创建一个UIA NVDA对象,其中包含指向UIA元素(UIAElement)的指针。 UIA元素反过来被调用以获取来自名称和控件类型属性的名称和角色。 就像MSAA控件角色一样,UIA API处理程序(UIAHandler)包括一个将UIA控件类型映射到NVDA角色的映射表。

无论使用哪种辅助功能API,有两个事情始终保持不变: 控件属性(如名称和角色)由应用程序开发人员确定,并且单个源代码可以处理各种辅助功能API。 例如,无论最终调用哪个辅助功能API指针和属性(IAccessibleObject的accName属性/UIAElement的name属性), 从NVDA Python控制台调用focus.name都将返回控件标签。 标签文本是应用程序开发人员所说的控件是什么,开发人员的工作是向辅助功能API透露这些信息,以便屏幕阅读器和用户可以确定控件是什么。 这就是为什么在一些文档中,应用程序被称为“服务器”,而屏幕阅读器被称为“客户端”的原因。 还记得我用来描述辅助功能API的“中间人”类比吗?这就是为什么。

小结

让我以两件事情作为结束:回答关于应用程序可访问性责任的问题,以及跟上不断变化的可访问性解决方案的发展。 有时候,这个论坛会被问到:“我应该向谁寻求使应用程序具备可访问性?”是应用程序开发者、屏幕阅读器供应商还是两者都是? 虽然双方都有责任,但我更倾向于将应用程序可访问性视为应用程序开发者的责任。 从可访问性信息检索的角度来看,屏幕阅读器是消费者。 人们可以争论屏幕阅读器也是生产者,但当我们考虑应用程序及其来源时,应用程序的可访问性和可用性是最初编写应用程序的人的责任。 这就是为什么我一直建议,关于应用程序可访问性的第一个联系对象应该是应用程序供应商,而不是像NV Access这样的屏幕阅读器供应商, 因为应用程序供应商对应用程序可访问性的承诺会使更多的NVDA用户受益。 希望上面关于可访问性控制标签和角色的内幕故事给你提供了我这样说的理由。

最后,辅助功能的解决方案(是的,我说的是解决方案)在1990年代和今天是不同的。 当MSAA处于起步阶段(1990年代末和2000年代初)时,屏幕抓取是一种获取屏幕上控件信息的有效方法。 在2000年代使用JAWS的人可能还记得Vispero(前身为Freedom Scientific)关于最佳屏幕分辨率设置的说明。 如今,使用辅助功能API(如UIA)来检索控件属性已经成为常态,这要归功于残障社区的持续倡导以及 WCAG(Web Content Accessibility Guidelines,网络内容辅助功能指南)和 WAI-ARIA(Web Accessibility Initiative/Accessible Rich Internet Applications, 网络辅助功能倡议/可访问丰富互联网应用)等持续的对话和标准化努力。 这就是为什么屏幕分辨率和屏幕阅读器的最佳设置等说明不再适用,至少对于NVDA用户来说 (在大部分情况下;正如我上面提到的,NVDA在特定情况下仍然可以抓取屏幕以获取信息)。

这个内幕故事的关键要点是:无障碍 API 不能取代应用程序开发者的思维方式。

希望这篇帖子一并回答并澄清了许多事情。

Cheers, Joseph

评论与回复

译注:无评论

译注

译自 Joseph Lee - The Inside Story of NVDA: where do control names and roles come from (2022-10-28)