当前位置: 首页 > news >正文

NVDA 2026.1.1 附加组件开发手册(中文版)

 

NVDA 2026.1.1 开发者指南

 

【翻译:
张赐荣,无障碍解决方案研发工程师、NVDA本地化贡献者】




    • NVDA 2026.1.1 开发者指南

        • 简介

            • 附加组件 API 稳定性

                • API
                  中传递导入的稳定性

                • pip
                  包的稳定性

            • 关于 Python
              的说明

            • C++

        • 翻译

            • 字符描述

                • 翻译此文件

            • 符号发音

                • 定义复杂符号

                • 定义符号信息

                • 示例

                • 翻译此文件

            • 手势

                • 示例

                    • 示例
                      1:原始手势使用的字符不是区域键盘布局上的键名

                    • 示例
                      2:原始手势利用了按键的物理位置

                    • 示例
                      3:原始手势被定义为匹配原生快捷键

                • 如何重新映射快捷键

                    • 确定要重新映射的类、脚本和原始手势

                    • 全局命令脚本的情况

                    • 应用专属脚本的情况

                    • 对象专属脚本的情况

                • 翻译此文件

        • 插件

            • 概述

            • 插件类型

            • 应用模块基础

            • 将应用模块与可执行文件关联

            • 示例
              1:在焦点变化事件中发出蜂鸣声的应用模块

            • 托管应用的应用模块

            • 示例 2:为
              wwahost.exe 托管的应用提供的应用模块

            • 示例
              3:为使用 Edge WebView2 (msedgewebview2.exe)
              的应用提供的应用模块

            • 全局插件基础

            • 示例
              3:提供用于朗读 NVDA 版本的脚本的全局插件

            • NVDA 对象

            • 脚本与手势绑定

                • 定义脚本属性

            • 示例
              4:用于查看窗口类和控制 ID 的全局插件

            • 事件

            • 应用模块的 SleepMode 变量

            • 示例
              5:睡眠模式应用模块

            • 提供自定义 NVDA 对象类

            • 示例
              6:使用自定义 NVDA 对象获取编辑框文本长度的命令

            • 在应用模块中对 NVDA
              对象进行小幅修改

            • 示例
              7:使用 eventNVDAObject_init 为记事本编辑框设置标签

        • 在插件中解析额外的命令行参数
        •  

    • 将代码打包为
      NVDA 附加组件

        • Zip 压缩包中的非 ASCII
          文件名

        • 清单文件

            • 可用字段

            • 清单文件示例

        • 插件与驱动程序

        • 可选的安装/卸载代码

            • onInstall 函数

            • onUninstall
              函数

        • 本地化附加组件

            • 特定区域设置的清单文件

            • 特定区域设置的消息

            • 语音符号词典

        • 附加组件文档

        • 盲文翻译表
    •  
    • NVDA Python
      控制台

        • 用法

        • 命名空间

            • 自动导入

            • 快照变量

        • Tab 补全
    •  
    • 远程
      Python 控制台

        • 用法
    •  
    • 扩展点

        • braille

        • appModuleHandler

        • addonHandler

        • brailleViewer

        • config

        • core

        • inputCore

        • logHandler

        • nvwave

        • speech

        • synthDriverHandler

        • tones

        • treeInterceptorHandler

        • utils.security

        • winAPI.messageWindow

        • winAPI.secureDesktop

        • bdDetect

        • vision.visionHandlerExtensionPoints.EventExtensionPoints
    •  
    • 与用户沟通

        • 消息对话框
          API

            • 回退操作

            • 关于线程的说明

            • 按钮

            • 回调

            • 便捷方法
    •  





简介


本指南提供有关 NVDA 开发的信息,包括翻译规范以及 NVDA
组件的开发。


附加组件 API 稳定性


NVDA 附加组件 API 可访问所有 NVDA 内部组件信息,以下情况除外:


    • 带有下划线 () 前缀的符号

    • 传递导入

    • 包含的 pip


NVDA 附加组件 API
会随着时间的推移而发生变化,例如新功能的添加、过时库的移除或替换、未使用或被替换代码及方法的弃用,以及
Python 本身的变更。API 的重要变更会在 NVDA API
邮件列表 上发布。与开发者相关的变更也会通过 NVDA 变更日志
公布。本节概述的 API 策略如有任何变更,也将通过这两个渠道进行传达。


API 破坏性发布(Breaking releases)每年最多发生一次,通常为
.1 版本,例如
2026.1。在两次破坏性发布之间,API 保持向后兼容。API
的破坏性变更在首个 beta 版(例如
2026.1.beta1)中应被视为相对稳定。


API 功能可能会随着时间的推移而被弃用。被弃用的 API
功能可能会有计划移除日期,即在未来的某个破坏性发布(例如
2026.1)中移除。弃用也可能没有计划移除日期,并会一直受支持,直到不再合理为止。请注意,移除路线图是“尽力而为(best
effort)”的,可能会发生变化。如果所述的附加组件 API 变更导致 API
不再满足您开发或维护的附加组件的需求,请在 GitHub 上提交 issue。


API 中传递导入的稳定性


通过检查 NVDA 源代码,确保从原始模块导入您的代码。


例如,如果一个类位于
foo.py,您应该按如下方式导入它:



fromfooimportFoo


如果 bar.py 导入了 Foo,您不能依赖从
bar 导入 Foo。也就是说,您必须直接从
foo 导入它。


API 不支持以下做法,因为 bar
中的导入可能会随时被移除。



frombarimportFoo


pip 包的稳定性


pip 包可能会随时被更新、降级或移除。建议您将任何与 NVDA 共享的 pip
依赖项直接与您的附加组件打包在一起,而不是使用 NVDA 版本的包。


关于 Python 的说明


NVDA 及其组件主要使用 Python 编程语言编写。本指南的目的不是教您学习
Python,但本指南中提供的示例将有助于您熟悉 Python 语法。有关 Python
语言的文档和其他资源可在 www.python.org/ 找到。


C++


NVDA 的部分内容是用 C++ 编写的,例如 nvdaHelper。有关 nvdaHelper
的概述,包括如何配置 Visual Studio
以启用智能感知(Intellisense),请参阅 nvdaHelper
自述文件。


翻译


为了支持多种语言/区域设置,必须对 NVDA
进行翻译,并提供特定于该区域设置的数据。本节仅包含翻译所需的自定义 NVDA
文件格式的信息。还有其他项目需要翻译,例如 NVDA
用户界面和文档,但它们使用标准文件格式。有关翻译 NVDA 的完整文档,请参阅
翻译页面。


字符描述


有时区分一个字符与另一个字符会非常困难,甚至是不可能的。例如,两个字符的发音可能相同,尽管它们实际上是不同的字符。为了在这种情况下帮助用户,可以提供字符描述,以独特的方式描述该字符。


可以为特定区域设置提供字符描述,方法是将其放在该区域设置目录中名为
characterDescriptions.dic 的文件中。这是一个 UTF-8
编码的文本文件。空行和以“#”字符开头的行将被忽略。所有其他行应包含一个字符,后跟一个制表符,然后是一个或多个由制表符分隔的描述。当朗读单个字符时(例如使用
leftArrow
rightArrow),一个字符的多个描述之间会有自然的停顿。当使用拼读命令朗读多个连续字符的字符描述时,每个字符将使用第一个描述,例如通过三击
NVDA+upArrow 拼读当前行。


例如:



# This is a comment. a alpha b bravo beta


在此示例中,“a”将朗读“alpha”作为字符描述,而“b”将朗读为“bravo,
beta”。


在大多数情况下,此文件中的字符应为单个小写字符。假定字符无论大小写都具有相同的描述,因此在查找字符描述之前,大写字母会被转换为小写字母。


翻译此文件


characterDescriptions.dic 的翻译通过 向 NVDA 提交
Pull Request 进行。


有关完整的示例和参考,请查看 英文版
characterDescriptions.dic 文件。


符号发音


在阅读文本时,尤其是逐字符移动时,听到标点符号和其他符号作为单词朗读出来通常很有用。不幸的是,不同语音合成器之间的符号发音不一致,而且许多合成器不朗读很多符号,和/或不允许控制朗读哪些符号。因此,NVDA
允许提供有关符号发音的信息。


这是通过在区域设置目录中提供名为 symbols.dic
的文件来实现的。这是一个 UTF-8
编码的文本文件。空行和以“#”字符开头的行将被忽略。所有区域设置都隐式继承英语的符号信息,尽管可以覆盖其中的任何信息。


该文件包含两个部分:复杂符号 和 符号。


定义复杂符号


第一部分是可选的,为复杂符号定义正则表达式模式。复杂符号不是简单的单个字符或字符序列,而是需要更复杂的匹配。一个例子是英语中表示句子结束的句号(.)。“.”有多种用途,因此需要更复杂的检查来确定它是否指句子的结尾。


复杂符号部分以下面的行开始:



complexSymbols:


后续行包含用于标识符号的文本标识符、一个制表符以及该符号的正则表达式模式。例如:



sentence ending (?<=[^\s.]).(?=[\”‘)\s]|$) dates with . \b(\d\d).(\d\d).(\d{2}|\d{4})\b


同样,英语符号被所有其他区域设置继承,因此您无需包含已经为英语定义的任何复杂符号。


定义符号信息


第二部分提供有关何时以及如何朗读所有符号的信息。它以下面的行开始:



symbols:


后续行应包含几个由制表符分隔的字段。唯一必填的字段是标识符(identifier)和替换(replacement)。省略的字段将使用默认值。字段如下:



    • identifier:符号的标识符。在大多数情况下,这只是符号的一个或多个字符。但是,它也可以是复杂符号的标识符。某些字符无法直接键入文件,因此可以使用以下特殊序列:

        • \0:null

        • \t:tab(制表符)

        • \n:line feed(换行)

        • \r:carriage return(回车)

        • \f:form feed(换页)

        • #:# 字符(需要,因为行首的 # 表示注释)

    • replacement:朗读该符号时应使用的文本。如果符号是复杂符号,可以使用
      \1\2
      等来引用匹配的分组,这些分组将被内联到替换文本中,从而允许使用更简单的规则。这也意味着,要在替换文本中获得
      \ 字符,必须键入 \

    • level:朗读该符号的符号级别。

        • 符号级别由用户配置,并指定应朗读的符号数量。

        • 此字段应包含级别“none”、“some”、“most”、“all”或“char”之一,或使用“-”表示使用默认值。

        • “char”表示仅在逐字符移动时才朗读该符号。

        • 默认值是继承该值,如果没有可继承的值,则为“all”。

    • preserve:是否应保留符号本身,以促进合成器正确发音。例如,引起停顿或语调变化的符号(如英语中的逗号)应被保留。此字段应为以下值之一:

        • never:从不保留符号。

        • always:始终保留符号。

        • norep:仅在符号未被替换时保留它;即用户将符号级别设置为低于此符号的级别。

        • -:使用默认值。默认值是继承该值,如果没有可继承的值,则为“never”。


最后,可以在行尾制表符后的注释中提供符号的显示名称。这将在用户编辑符号信息时显示,对翻译人员为英语复杂符号定义翻译后的名称特别有用。


示例



( left paren most


这意味着仅当符号级别设置为 most 或更高(即 most 或
all)时,才将“(”字符朗读为“left paren”。



, comma all always


这意味着当符号级别设置为 all
时,应将“,”字符朗读为“comma”,并且应始终保留字符本身,以便合成器进行适当的停顿。



. sentence ending point # . fin de phrase


此行出现在法语的 symbols.dic 文件中。这意味着“. sentence
ending”复杂符号应被朗读为“point”。未指定 Level 和
preserve,因此它们将从英语中获取。提供了显示名称,以便法语用户知道该符号代表什么。



dates with . \1 point \2 point \3 all norep # date avec points


此行出现在法语的 symbols.dic
文件中。这意味着匹配的第一、第二和第三组将被包含在内,并由单词“point”分隔。因此,其效果是用单词“point”替换日期中的点。


如果您的语言使用千位分隔符(如句号
(.)),而由于其他规则导致其处理不正确,则需要为其定义一个复杂符号模式。例如,如果您的语言使用逗号
(,) 作为千位分隔符,则应在复杂符号部分包含以下内容:



thousands separator (?<=\d)\,(?=\d)


您还应在主符号部分包含类似以下的内容:



thousands separator comma all norep


翻译此文件


symbols.dic 的翻译通过 向 NVDA 提交
Pull Request 进行。


请参阅文件 locale.dic
以获取所有区域设置继承的英语定义。


手势


NVDA
中最初定义的手势被配置为期望英语软件和键盘布局。在大多数情况下,这些手势也可以在其他键盘布局上毫无问题地执行。然而,有时
NVDA
最初定义的手势并不适合特定的区域设置(键盘布局或软件)。需要修改原始手势可能是由于以下原因:



    • 原始手势使用了一个字符,该字符不是区域键盘布局上的键名。通常,键名是无需借助修饰键(shiftcontrol
      等)即可输入的字符。

    • 原始手势利用了该键在英语键盘布局上的物理位置,但这种优势在区域键盘布局上不存在。

    • 原始手势被定义为匹配 Windows 或应用程序中的原生快捷键,但本地版本的
      Windows 或该应用程序中的快捷键与英语版本不同。


在所有这些情况下,NVDA 允许针对此特定区域设置重新映射此手势。


示例


以下是 gestures.ini
文件的三个详细示例,对应于可能需要重新映射手势的三种列出的情况。


示例
1:原始手势使用的字符不是区域键盘布局上的键名

在原始的英语版本中,鼠标左键和右键单击(笔记本布局)的脚本分别使用
NVDA+[NVDA+] 执行。



    • 在英语键盘布局上,[] 键是
      p 键右侧的两个键。

    • 在意大利语键盘布局上,[] 字符只能借助
      altGr 修饰键输入:分别是 altGr+è
      altGr+plus


因此,意大利语翻译人员决定使用意大利语键盘布局上 p
键右侧的两个键(即 è
+)重新映射这些脚本。为此,他们在 gestures.ini
文件中添加了以下行:



[globalCommands.GlobalCommands] leftMouseClick = kb(laptop):NVDA+è rightMouseClick = kb(laptop):NVDA+plus


示例
2:原始手势利用了按键的物理位置

再次查看鼠标左键和右键单击(笔记本布局)的脚本,我们可以看到它们最初(在英语中)被映射到两个相邻的键。这对应于鼠标的左键和右键。如示例
1
所示,许多翻译人员不得不修改这些键。他们中的大多数(如果不是全部)都选择了两个相邻的键。例如,在法语的
gestures.ini 中,添加了以下行:



[globalCommands.GlobalCommands] None = kb(laptop):nvda+[, kb(laptop):nvda+control+[, kb(laptop):nvda+], kb(laptop):nvda+control+], kb(laptop):nvda+shift+., kb(laptop):nvda+., kb(laptop):nvda+control+. leftMouseClick = kb(laptop):nvda+ù rightMouseClick = kb(laptop):nvda+



法语布局上的 ù 与英语布局上的
[]
不在同一位置,但它们仍然是两个相邻的键。此外,我们可以在这里看到
NVDA+[NVDA+] 等已被映射到
None,以解绑这些手势。对于法语(法国)布局,这不是强制性的,因为没有其他修饰键就无法输入
NVDA+[NVDA+]


示例
3:原始手势被定义为匹配原生快捷键

NVDA 为 Word 文档对象提供了一个名为 toggleBold
的脚本。此脚本映射到的手势与 Word 设置文本加粗的原生快捷键相同,即英语版
Word 中的 control+b。然而,在法语版 Word
中,将文本加粗的快捷键是 control+g。G
代表“gras”,在法语中意为“bold(加粗)”。在法语的
gestures.ini 文件中添加了以下行以重新映射此脚本:



[NVDAObjects.window.winword.WordDocument] None = kb:control+b, kb:control+[, kb:control+], “kb:control+shift+,”, kb:control+shift+., kb:control+l, kb:control+r toggleBold = kb:control+g, kb:control+shift+b


我们可以看到 control+b
已被解绑。这是必要的,因为它是法语版 Word 中另一个命令的快捷键。没有为
toggleItalic 脚本进行重新映射,因为该快捷键在法语版和英语版
Word 中是相同的。


如何重新映射快捷键


确定要重新映射的类、脚本和原始手势

要编辑 gesture.ini
文件,您必须确定要重新映射的类、脚本和原始快捷键。


全局命令脚本的情况

如果要重新映射的手势是全局命令,您可以执行以下步骤来查找命令的类和脚本名称:



    • 激活输入帮助(NVDA+1


    • 按下要重新映射的手势,例如
      NVDA+](笔记本布局)


    • 停用输入帮助(NVDA+1


    • 打开日志(NVDA+F1


    • 找到与您执行手势的时刻相对应的行,例如:



      Input help: gesture kb(laptop):NVDA+], bound to script rightMouseClick on globalCommands.GlobalCommands



您正在寻找的信息就在这行上:



    • 脚本名称:rightMouseClick

    • 类名称:globalCommands.GlobalCommands(请注意,这始终是全局命令的类)

    • 原始手势:kb(laptop):NVDA+]


应用专属脚本的情况

如果您想重新映射应用专属脚本,则必须遵循与全局命令脚本相同的步骤。您只需在继续之前确保您处于目标应用程序中。


对象专属脚本的情况

对于特定对象的脚本(例如与
NVDAObjects.window.winword.WordDocument
关联的脚本),您可以遵循与应用专属脚本相同的步骤,但需注意以下两点:



    • 在继续之前,您需要确保脚本绑定到的对象已获得焦点。

    • 其中一些脚本没有帮助消息,因此在输入帮助模式下执行它们时您可能什么也听不到;但脚本的名称和对象的类仍将出现在日志中。


请注意,日志中显示的对象的类可能是实际绑定原始手势的类的子类。在这种情况下,您将需要探索
NVDA 的源代码以找到此父类。


翻译此文件


gestures.ini 的翻译通过 向 NVDA 提交
Pull Request 进行。



    1. 在此文件中,各个部分对应于脚本所属的类。如果您要找的类不存在,请创建此部分。


    1. 在目标部分下,添加对应于新快捷键的一行。例如:



      toggleBold = kb:control+g, kb:control+shift+b


      如果脚本名称已经存在一行,但您想修改快捷键,请在同一行上添加新快捷键,并用逗号(“,”)分隔每个快捷键。


    1. 如果要取消映射原始快捷键,只需将其映射到
      None,例如:



      None = kb:control+b


      仅当此快捷键与任何其他重新映射的区域快捷键不匹配时,才需要取消映射原始快捷键。



插件


概述


通过插件,您可以自定义 NVDA
的整体行为或其在特定应用程序中的行为。它们能够:



    • 响应特定事件,例如焦点和对象属性的变化(如控件更改其名称时)。

    • 实现绑定到特定按键或其他输入的命令。

    • 自定义特定控件的行为并为其实现附加功能。

    • 自定义或增加对文本内容和复杂文档的新支持。


本节提供插件开发的入门介绍。开发者应查阅代码文档以获取完整的参考信息。


插件类型


插件分为两种类型,分别是:



    • 应用模块(App
      Modules):特定于某个应用程序的代码。应用模块会接收特定应用程序的所有事件,即使该应用程序当前未处于活动状态。当应用程序处于活动状态时,用户可以执行应用模块绑定到按键或其他输入的任何命令。

    • 全局插件(Global Plugins):对 NVDA
      全局生效的代码;即在所有应用程序中均可使用。全局插件接收操作系统中所有控件的所有事件。全局插件绑定的任何命令,无论用户处于操作系统的哪个位置、无论当前是什么应用程序,都可以执行。


如果您希望改善 NVDA
对特定应用程序的访问支持,您很可能需要编写一个应用模块。相反,如果您希望为
NVDA
添加一些全局功能(例如,一个可以在任何应用程序中朗读当前无线网络信号强度的脚本),那么您需要的是全局插件。


应用模块和全局插件具有共同的外观和结构。它们都是 Python
源代码文件(扩展名为
.py),都定义了一个包含所有事件、脚本和绑定的特殊类,并且都可以定义自定义类来访问控件、文本内容和复杂文档。然而,它们在某些方面也存在差异。


自定义的应用模块(appModules)和全局插件(globalPlugins)可以打包为
NVDA
附加组件(add-ons)。这便于分发,并为用户提供了一种安全安装和卸载自定义代码的方式。请参阅本文档后面的
附加组件部分。


为了在开发过程中测试代码,您可以将其放置在 NVDA
用户配置目录中一个特殊的“暂存区(scratchpad)”目录中。您还需要在 NVDA
设置对话框的“高级”类别中启用“从开发者暂存区目录加载自定义代码”选项,以配置
NVDA
允许加载这些代码。如果启用了该选项,“高级”类别中还会提供一个按钮,以便轻松打开开发者暂存区目录。


接下来的几节将分别讨论应用模块和全局插件。在此之后,讨论将再次变得更加通用。


应用模块基础


应用模块文件的扩展名为
.py,在大多数情况下,其名称应与您希望其生效的应用程序的主可执行文件名称相同,或者与宿主可执行文件内的包名称相同。例如,记事本的应用模块应命名为
notepad.py,因为记事本的主可执行文件名为
notepad.exe。要将单个应用模块映射到多个可执行文件,或者处理可执行文件名称违反
Python 导入规则的情况,请参阅 将应用模块与可执行文件关联。对于在宿主可执行文件中托管的应用,请参阅关于托管应用的应用模块的章节。


应用模块文件必须放置在附加组件的 appModules 子目录中,或者 NVDA
用户配置目录的暂存区(scratchpad)目录中。


应用模块必须定义一个名为 AppModule 的类,该类继承自
appModuleHandler.AppModule。然后,该类可以定义事件和脚本方法、手势绑定以及其他代码。这些内容将在后文深入探讨。


一旦 NVDA
检测到应用程序正在运行,就会立即为其加载应用模块。当应用程序关闭或 NVDA
退出时,应用模块会被卸载。


将应用模块与可执行文件关联


如上所述,有时将应用模块与应用程序关联的默认方式不够灵活。例如:



    • 您希望为多个二进制文件使用单个应用模块(也许应用程序的稳定版和预览版都应该具有相同的无障碍增强功能)。

    • 可执行文件的命名方式与 Python
      命名规则冲突。例如,对于一个名为“time”的应用程序,将应用模块命名为“time.py”会与标准库中的内置模块发生冲突。


在这种情况下,您可以随应用模块一起分发一个小型的全局插件,将其映射到该可执行文件。例如,要将名为“timeappmod”的应用模块映射到“time”可执行文件,插件可以编写如下:



importappModuleHandlerimportglobalPluginHandlerclassGlobalPlugin(globalPluginHandler.GlobalPlugin):definit(self,args,*kwargs):super()._init(args,**kwargs)appModuleHandler.registerExecutableWithAppModule(“time”,“time_app_mod”)defterminate(self,args,kwargs):super().terminate(*args,kwargs)appModuleHandler.unregisterExecutable(“time”)


示例 1:在焦点变化事件中发出蜂鸣声的应用模块


以下示例应用模块使 NVDA
在记事本应用程序中每次焦点发生变化时发出蜂鸣声。此示例向您展示了应用模块的基本结构。


将起始和结束标记之间(但不包括标记本身)的代码复制并粘贴到一个名为
notepad.py 的新文本文件中,该文件应保存在 appModules
子目录中。请务必小心,保持所有制表符和空格完整无损。


保存到正确位置后,重新启动 NVDA 或在 NVDA
菜单的“工具”下选择“重新加载插件”。


最后,打开记事本并在应用程序中移动焦点;例如,沿菜单栏移动、打开一些对话框等。每次焦点变化时,您都应该听到蜂鸣声。但请注意,如果您移出记事本(例如,移到
Windows 资源管理器),则不会听到蜂鸣声。



# Notepad App Module for NVDA# Developer guide example 1importappModuleHandlerclassAppModule(appModuleHandler.AppModule):defevent_gainFocus(self,obj,nextHandler):importtonestones.beep(550,50)nextHandler()


此应用模块文件以两行注释开始,描述了该文件的用途。


然后它导入了 appModuleHandler 模块,以便应用模块可以访问基础的
AppModule 类。


接下来,它定义了一个名为 AppModule 的类,该类继承自
appModuleHandler.AppModule


在该类内部,它定义了一个或多个事件、脚本或手势绑定。在此示例中,它为
gainFocus(获得焦点)事件定义了一个事件方法(event_gainFocus),该方法在每次执行时播放一声短促的蜂鸣音。此事件的实现细节对于本示例的目的并不重要。最重要的部分是类本身。事件将在后文更详细地介绍。


与本指南中的其他示例一样,请记得在测试完成后删除创建的应用模块,然后重新启动
NVDA 或重新加载插件,以便恢复原始功能。


托管应用的应用模块


某些可执行文件会在其内部托管各种应用程序,或被应用程序用来显示其界面。这些包括用于运行各种
Java 程序的 javaw.exe、用于某些基于 Web 的应用程序的
wwahost.exe,以及用于在使用 Edge WebView2
运行时的应用程序中显示类似 Web 界面的
msedgewebview2.exe


如果应用程序在宿主可执行文件中运行,或使用不同的应用程序来显示界面,则应用模块的名称必须是宿主或界面可执行文件所定义的名称,这可以通过
AppModule.appName 属性找到。例如,托管在
javaw.exe 中名为“test”的 Java
应用程序的应用模块必须命名为 test.py。对于托管在
wwahost
中的应用,应用模块名称不仅必须是已加载应用的名称,而且应用模块还必须继承自
wwahost 中的应用模块类。默认情况下,使用 Edge WebView2
的应用程序(如现代版 Outlook (olk.exe))会作为网页显示。


示例 2:为 wwahost.exe
托管的应用提供的应用模块


以下示例与上面的记事本应用模块相同,只不过这是针对由
wwahost.exe 托管的应用。



# wwahost/test App Module for NVDA# Developer guide example 2fromnvdaBuiltin.appModules.wwahostimportclassAppModule(AppModule):defevent_gainFocus(self,obj,nextHandler):importtonestones.beep(550,50)nextHandler()



与记事本应用模块最大的区别在于 wwahost
应用模块的来源。作为内置应用模块,wwahost 可以从
nvdaBuiltin.appModules 导入。


另一个区别是应用模块类的定义方式。由于 wwahost
应用模块为内部托管的应用提供了必要的基础设施,您只需继承 wwahost 的
AppModule 类即可。


示例 3:为使用 Edge WebView2
(msedgewebview2.exe) 的应用提供的应用模块


以下示例是一个使用 Edge WebView2
运行时的应用模块,默认禁用浏览模式,以现代版 Outlook (olk.exe)
为例。



# msedgewebview2 example (modern Outlook/olk.py)importappModuleHandlerclassAppModule(appModuleHandler.AppModule):disableBrowseModeByDefault:bool=True


在此示例中禁用浏览模式,是因为使用 WebView2
的应用程序将其界面显示为网页。如果您希望允许用户使用浏览模式命令导航该应用,可以删除“disableBrowseModeByDefault”这一行。


全局插件基础


全局插件文件的扩展名为
.py,并且应有一个简短且唯一的名称来标识其功能。


全局插件文件必须放置在附加组件的 globalPlugins 子目录中,或者 NVDA
用户配置目录的暂存区(scratchpad)目录中。


全局插件必须定义一个名为 GlobalPlugin 的类,该类继承自
globalPluginHandler.GlobalPlugin。然后,该类可以定义事件和脚本方法、手势绑定以及其他代码。这些内容将在后文深入探讨。


NVDA 在启动时会立即加载所有全局插件,并在退出时卸载它们。


示例 3:提供用于朗读 NVDA 版本的脚本的全局插件


以下示例全局插件允许您在操作系统的任何位置按下 NVDA+shift+v 来获知
NVDA 的版本。此示例仅用于向您展示全局插件的基本结构。


将起始和结束标记之间(但不包括标记本身)的代码复制并粘贴到一个名为
example2.py 的新文本文件中,该文件应保存在 globalPlugins
子目录中。请务必小心,保持所有制表符和空格完整无损。


保存到正确位置后,重新启动 NVDA 或在 NVDA
菜单的“工具”下选择“重新加载插件”。


现在,您可以在任何地方按下 NVDA+shift+v,让 NVDA
朗读并显示盲文版本信息。



# Version announcement plugin for NVDA# Developer guide example 3importglobalPluginHandlerfromscriptHandlerimportscriptimportuiimportbuildVersionclassGlobalPlugin(globalPluginHandler.GlobalPlugin):@script(gesture=“kb:NVDA+shift+v”)defscript_announceNVDAVersion(self,gesture):ui.message(buildVersion.version)


此全局插件文件以两行注释开始,描述了该文件的用途。


然后它导入了 globalPluginHandler 模块,以便全局插件可以访问基础的
GlobalPlugin 类。


它还导入了其他几个模块,即 ui、buildVersion 和
scriptHandler,这个特定的插件需要它们来执行朗读版本所需的动作。


接下来,它定义了一个名为 GlobalPlugin 的类,该类继承自
globalPluginHandler.GlobalPlugin


在该类内部,它定义了一个或多个事件、脚本或手势绑定。在此示例中,它定义了一个执行版本朗读的脚本方法。使用
scriptHandler 模块中的 script 装饰器将 NVDA+shift+v
快捷键分配给此脚本。然而,脚本及其绑定的细节对于本示例的目的并不重要。最重要的部分是类本身。有关脚本和
script 装饰器的更多信息,可在本指南的 定义脚本属性
部分找到。


与本指南中的其他示例一样,请记得在测试完成后删除创建的全局插件,然后重新启动
NVDA 或重新加载插件,以便恢复原始功能。


NVDA 对象


NVDA 将控件和其他 GUI 元素表示为 NVDA 对象(NVDA Objects)。这些 NVDA
对象包含标准化的属性,例如名称(name)、角色(role)、值(value)、状态(states)和描述(description),这允许
NVDA
的其他部分以通用的方式查询或呈现有关控件的信息。例如,对话框中的“确定”按钮将被表示为一个名称为“OK”且角色为按钮的
NVDA 对象。同样,标签为“I agree”的复选框将具有名称“I
agree”、角色为复选框,如果当前已选中,则状态为已选中。


由于存在许多不同的 GUI 工具包以及平台和无障碍 API,NVDA
对象将这些差异抽象为 NVDA
可以使用的标准形式,而不管特定控件是使用哪种工具包或 API
构建的。例如,刚才讨论的“确定”按钮可能是 Java
应用程序中的小部件(widget)、MSAA 对象、IAccessible2 对象或 UI
Automation 元素。


NVDA 对象有许多属性。其中一些最有用的包括:



  • name:控件的标签。

    • role:NVDA 的 controlTypes 模块中的 Role.
      常量之一。Button(按钮)、dialog(对话框)、editableText(可编辑文本)、window(窗口)和
      checkbox(复选框)都是角色的示例。

    • states:NVDA 的 controlTypes 模块中的 State.* 常量的 0
      个或多个集合。Focusable(可获焦)、focused(已获焦)、selected(已选中)、selectable(可选)、expanded(已展开)、collapsed(已折叠)和
      checked(已勾选)是一些状态的示例。

    • value:控件的值;例如滚动条的百分比或组合框的当前设置。

    • description:描述控件功能的一两句话(通常与其工具提示相同)。

    • location:对象在屏幕坐标中的左、上、宽和高位置。

    • parent:此对象的父对象。例如,列表项对象的父对象将是包含它的列表。

    • next:在逻辑顺序上同一层级中紧接在此对象之后的对象。例如,菜单项
      NVDA 对象的下一个对象很可能是同一菜单中的另一个菜单项。

    • previous:与 next 类似,但方向相反。

    • firstChild:此对象的第一个直接子对象。例如,列表的第一个子对象将是第一个列表项。

    • lastChild:此对象的最后一个直接子对象。

    • children:此对象的所有直接子对象的列表;例如菜单中的所有菜单项。


还有一些简化的导航属性,如 simpleParent、simpleNext、simpleFirstChild
和 simpleLastChild。它们类似于上述各自的导航属性,但 NVDA
会过滤掉无用的对象。当 NVDA 的简单浏览模式(simple review
mode,默认开启)打开时,会使用这些属性。这些简单属性可能更易于使用,但真实的导航属性更贴近底层的操作系统结构。此外,随着简单浏览模式的改进,这些属性可能会在
NVDA
的未来版本中发生变化,因此在以编程方式定位特定对象时通常应避免使用它们。


在开发插件时,大多数情况下,NVDA 对象底层使用什么工具包或 API
并不重要,因为插件通常只会访问标准属性,如名称、角色和值。然而,随着插件变得更加高级,如果需要,完全可以深入研究
NVDA 对象以获取特定于工具包或 API 的信息。


插件以三种特定的方式使用 NVDA 对象:



    • 大多数插件接收的事件都带有一个参数,即发生该事件的 NVDA
      对象。例如,event_gainFocus 接受代表获得焦点的控件的 NVDA 对象。

    • 脚本、事件或其他代码可能会获取感兴趣的对象,例如具有焦点的 NVDA
      对象、NVDA 当前的导航对象,或者可能是桌面 NVDA
      对象。然后,代码可以从该对象检索信息,或者甚至检索与其相关的另一个对象(例如其父对象、第一个子对象等)。

    • 插件可以定义其自己的自定义 NVDA
      对象类,这些类将用于包装特定控件,以赋予其额外功能、改变其属性等。


与应用模块和全局插件一样,NVDA
对象也可以定义事件、脚本和手势绑定。


脚本与手势绑定


应用模块、全局插件和 NVDA
对象可以定义特殊的方法,这些方法可以绑定到特定的输入(如按键)。NVDA
将这些方法称为脚本(scripts)。

脚本是一个标准的 Python
实例方法,其名称以“script
”开头;例如“scriptsayDateTime”。


脚本方法接受两个参数:



    • self:对调用该脚本的应用模块、全局插件或 NVDA 对象实例的引用。

    • gesture:一个输入手势(Input
      Gesture)对象,代表导致脚本运行的输入。


除了实际的脚本方法外,还必须定义某种形式的手势绑定,以便 NVDA
知道应执行该脚本的输入是什么。


手势标识符字符串是输入的简单字符串表示。它由表示输入源的两个字母的代码、可选的括号内的设备、冒号
(:) 以及一个或多个由加号 (+)
分隔的名称(表示实际的按键或输入值)组成。


一些手势字符串标识符的示例:



    • “kb:NVDA+shift+v”

    • “br(freedomScientific):leftWizWheelUp”

    • “br(alva.BC640):t3”

    • “kb(laptop):NVDA+t”


目前,NVDA 中的输入源包括:



    • kb:系统键盘输入

    • br:盲文显示器控件

    • ts:触摸屏

    • bk:盲文键盘输入


当 NVDA
接收到输入时,它会按照特定的顺序查找匹配的手势绑定。一旦找到手势绑定,就会执行脚本,并且不再使用进一步的绑定,该特定手势也不会自动传递给操作系统。


手势绑定查找的顺序为:



    • 用户特定手势映射

    • 区域特定手势映射

    • 盲文显示器驱动特定手势映射

    • 已加载的全局插件

    • 活动应用程序的应用模块

    • 具有焦点的 NVDA
      对象的树拦截器(如果有);例如虚拟缓冲区(virtualBuffer)

    • 具有焦点的 NVDA 对象

    • 全局命令(内置命令,如退出 NVDA、对象导航命令等)


定义脚本属性


对于 NVDA 2018.3
及更高版本,设置脚本属性的推荐方法是使用所谓的脚本装饰器(script
decorator)。简而言之,装饰器是一个修改特定函数或方法行为的函数。脚本装饰器修改脚本的方式使其能够正确地绑定到所需的手势。此外,它确保脚本以您指定的描述列出,并在输入手势对话框中归类到所需的类别下。


为了使用 script 装饰器,您必须从
scriptHandler 模块中导入它。



fromscriptHandlerimportscript


之后,在脚本定义的上方,添加 script
装饰器,并提供所需的参数。例如:


@script(description=(“Speaks the date and time”),category=inputCore.SCRCATMISC,gestures=[“kb:NVDA+shift+t”,“kb:NVDA+alt+r”])defscript_sayDateTime(self,gesture):



在此示例中,您的脚本将列在输入手势对话框的“杂项(Miscellaneous)”类别下。它的描述将是“朗读日期和时间”,并将绑定到键盘上的“NVDA+shift+t”和“NVDA+alt+r”组合键。


应用脚本装饰器时可以使用以下关键字参数:


  • description:一个简短的、可翻译的字符串,用于向用户描述命令。这在输入帮助模式下会报告给用户,并显示在输入手势对话框中。除非您指定了描述,否则脚本不会出现在输入手势对话框中。

    • category:脚本的类别,以便将其与其他类似的脚本分组。例如,全局插件中添加浏览模式快速导航键的脚本可以归类为“浏览模式(Browse
      mode)”类别。可以为单个脚本设置类别,但您也可以在插件类上设置“scriptCategory”属性,该属性将用于未指定类别的脚本。在
      inputCore 和 globalCommands 模块中有以 SCRCAT

      为前缀的常用类别常量,也可以指定这些常量。脚本将列在输入手势对话框中指定的类别下。如果未指定类别,脚本将归类为“杂项(Miscellaneous)”。

    • gesture:包含与此脚本关联的单个手势的字符串,例如
      “kb:NVDA+shift+r”。

    • gestures:包含与此脚本关联的多个手势的字符串列表,例如
      [“kb:NVDA+shift+r”, “kb:NVDA+alt+t”]。当同时指定 gesture 和 gestures
      时,它们会被合并。gesture 或 gestures
      中的任何项都可以用于触发脚本。

    • canPropagate:一个布尔值,指示当此脚本属于焦点祖先对象时是否也应适用。例如,当您想在特定前景对象或焦点祖先中不是当前焦点对象的另一个对象上指定脚本时,可以使用此选项。此选项默认为
      False。

    • bypassInputHelp:一个布尔值,指示当输入帮助处于活动状态时是否应运行此脚本。此选项默认为
      False。

    • allowInSleepMode:一个布尔值,指示当睡眠模式处于活动状态时是否应运行此脚本。此选项默认为
      False。

    • resumeSayAllMode:在执行此脚本之前如果处于活动状态则应恢复的连续朗读(say
      all)模式。连续朗读模式的常量可以在 speech.sayAll 的 CURSOR
      枚举中找到。如果未指定
      resumeSayAllMode,则在此脚本之后不会恢复连续朗读。

    • speakOnDemand:一个布尔值,指示当语音模式为“按需(on-demand)”时调用此脚本是否应产生语音。此选项默认为
      False。


尽管脚本装饰器使脚本定义过程变得容易得多,但还有更多绑定手势和设置脚本属性的方法。例如,可以在应用模块、全局插件或
NVDA 对象上将一个特殊的“gestures”Python
字典定义为类变量。此字典应包含指向请求脚本名称的手势标识符字符串,且不包含“script”前缀。您还可以在方法的“doc”属性中指定脚本的描述。但是,请注意,如果未设置“doc”属性,请勿在方法开头包含内联文档字符串(docstring),因为这会导致描述无法被翻译。脚本装饰器不受此限制,因此鼓励您在使用它时根据需要内联文档字符串。此外,指定脚本类别的另一种方法是将脚本方法上的“category”属性设置为包含类别名称的字符串。


示例 4:用于查看窗口类和控制 ID 的全局插件


以下全局插件允许您按下 NVDA+leftArrow 朗读当前焦点的窗口类,按下
NVDA+rightArrow 朗读当前焦点的窗口控制
ID。此示例向您展示了如何在类(如应用模块、全局插件或 NVDA
对象)上定义一个或多个脚本和手势绑定。


将起始和结束标记之间(但不包括标记本身)的代码复制并粘贴到一个名为
example3.py 的新文本文件中,该文件应保存在 globalPlugins
子目录中。请务必小心,保持所有制表符和空格完整无损。


保存到正确位置后,重新启动 NVDA 或在 NVDA
菜单的“工具”下选择“重新加载插件”。



#Window utility scripts for NVDA#Developer guide example 4importglobalPluginHandlerfromscriptHandlerimportscriptimportuiimportapiclassGlobalPlugin(globalPluginHandler.GlobalPlugin):@script(description=(“Announces the window class name of the current focus object”),gesture=“kb:NVDA+leftArrow”)defscriptannounceWindowClassName(self,gesture):focusObj=api.getFocusObject()name=focusObj.namewindowClassName=focusObj.windowClassNameui.message(f“class for{name}window:{windowClassName})@script(description=(“Announces the window control ID of the current focus object”),gesture=“kb:NVDA+rightArrow”)defscriptannounceWindowControlID(self,gesture):focusObj=api.getFocusObject()name=focusObj.namewindowControlID=focusObj.windowControlIDui.message(f“Control ID for{name}window:{windowControlID})



事件


当 NVDA 检测到特定的工具包、API
或操作系统事件时,它会将其抽象化并在插件和 NVDA
对象上触发其自己的内部事件。


尽管大多数事件与特定的 NVDA
对象相关(例如名称更改、获得焦点、状态更改等),但这些事件可以在不同层级进行处理。当事件被处理后,它将停止在链中继续向下传递。但是,事件内部的代码可以选择在需要时将其进一步传播。


事件在找到事件方法之前经过的层级顺序为:



    • 已加载的全局插件

    • 与触发事件的 NVDA 对象关联的应用模块

    • 与触发事件的 NVDAObject 关联的树拦截器(如果有)

    • NVDAObject 本身。


事件是 Python 实例方法,名称以“event”开头,后跟事件的实际名称(例如
gainFocus)。


这些事件方法根据它们定义的层级采用略有不同的参数。


如果在 NVDA 对象本身上定义了 NVDA
对象的事件,则该方法仅接受一个强制参数,即“self”参数(即 NVDA
对象实例)。有些事件可能会接受额外的参数,尽管这非常罕见。


如果在全局插件、应用模块或树拦截器上定义了 NVDA
对象的事件,则该事件接受以下参数:



    • self:全局插件、应用模块或树拦截器的实例

    • obj:触发事件的 NVDA 对象

    • nextHandler:一个函数,调用时会将事件进一步传播到链的下方。


一些常见的 NVDA 对象事件包括:



    • foreground:此 NVDA 对象已成为新的前景对象;即活动的顶级对象

    • gainFocus

    • focusEntered:焦点已移入此对象内部;即它是焦点对象的祖先

    • loseFocus

    • nameChange

    • valueChange

    • stateChange

    • caret:当此 NVDA 对象内的光标(插入点)移动时

    • locationChange:物理屏幕位置发生变化


还有许多其他事件,尽管上面列出的通常是最有用的。


有关应用模块处理事件的示例,请参阅 示例 1(记事本中的焦点蜂鸣声)。


应用模块的 SleepMode 变量


应用模块有一个非常有用的属性,称为“sleepMode”,如果将其设置为
true,几乎会完全禁用该应用程序内的
NVDA。睡眠模式对于自带语音播报功能(self
voicing)的应用程序,或者甚至需要完全使用键盘的某些游戏非常有用。


尽管用户可以通过按键命令 NVDA+shift+s
来切换睡眠模式的开启和关闭,但开发者可以选择默认情况下为应用程序启用睡眠模式。这是通过为该应用程序提供一个应用模块来实现的,该模块只需在
AppModule 类中将 sleepMode 设置为 True。


示例 5:睡眠模式应用模块


以下代码可以复制并粘贴到文本文件中,然后保存在
appModules
目录中,文件名为您希望启用睡眠模式的应用程序的名称。与往常一样,文件必须具有
.py 扩展名。



importappModuleHandlerclassAppModule(appModuleHandler.AppModule):sleepMode=True


提供自定义 NVDA 对象类


提供自定义 NVDA 对象类可能是改善 NVDA
插件中应用程序体验的最强大、最有用的方法。此方法允许您将特定控件所需的所有逻辑集中在该控件的一个
NVDA 对象类中,而不是将许多控件的代码分散在插件的各个事件中。


提供自定义 NVDA 对象类有两个步骤:



    • 定义 NVDA 对象类及其事件、脚本、手势绑定和覆盖的属性。

    • 通过在插件的 chooseNVDAObjectOverlayClasses
      方法中处理它,告诉 NVDA 在特定情况下使用此 NVDA 对象类。


在定义自定义 NVDAObject 类时,您有许多 NVDAObject
基类可供选择。这些基类包含对控件底层的特定无障碍或操作系统 API(如
win32、MSAA 或 Java Access
Bridge)的基础支持。通常,您应该从最初选择您的类所需的最高基类继承您的自定义
NVDAObject 类。例如,如果您选择在窗口类名为“Edit”且窗口控制 ID 为 15
时使用您的自定义 NVDAObject 类,您可能应该继承自
NVDAObjects.window.Window,因为您清楚地知道这是一个 Window
对象。同样,如果您匹配 MSAA 的 accRole
属性,您可能需要继承自
NVDAObjects.IAccessible.IAccessible。您还应该考虑要在自定义
NVDA 对象上覆盖哪些属性。例如,如果您要覆盖 IAccessible 特定的属性(如
shouldAllowIAccessibleFocusEvent),那么您需要继承自
NVDAObjects.IAccessible.IAccessible


chooseNVDAObjectOverlayClasses
方法可以在应用模块或全局插件类上实现。它接受 3 个参数:



    1. self:应用模块或全局插件实例。

    1. obj:正在为其选择类的 NVDAObject

    1. clsList:将用于 obj
      NVDAObject 类的 Python 列表。


在此方法内部,您应该通过检查其属性等来决定此 NVDA
对象应使用哪些(如果有)自定义 NVDA
对象类。如果应使用自定义类,则必须将其插入到类列表中,通常是在开头。您也可以从类列表中移除
NVDA 选择的类,尽管这很少需要。


示例 6:使用自定义 NVDA
对象获取编辑框文本长度的命令


这个用于记事本的应用模块提供了一个命令来报告编辑框中的字符数。您可以使用
NVDA+l
激活它。请注意,该命令特定于编辑框;即它仅在您聚焦于编辑框时有效,而不是在应用程序的任何地方都有效。


以下代码可以复制并粘贴到文本文件中,然后以 notepad.py
为名保存在 appModules 目录中。



importappModuleHandlerfromscriptHandlerimportscriptfromNVDAObjects.IAccessibleimportIAccessibleimportcontrolTypesimportuiclassAppModule(appModuleHandler.AppModule):defchooseNVDAObjectOverlayClasses(self,obj,clsList):ifobj.windowClassName==“Edit”andobj.role==controlTypes.Role.EDITABLETEXT:clsList.insert(0,EnhancedEditField)classEnhancedEditField(IAccessible):@script(gesture=“kb:NVDA+l”)defscript_reportLength(self,gesture):ui.message(f{len(self.value)})


在应用模块中对 NVDA
对象进行小幅修改


有时,您可能只想对应用程序中的 NVDA
对象进行微小的更改,例如覆盖其名称或角色。在这些情况下,您不需要自定义
NVDA 对象类的全部功能。要做到这一点,您可以使用仅在应用模块上可用的
NVDAObject_init 事件。


event_NVDAObject_init 方法接受两个参数:



    1. self:AppModule 实例。

    1. obj:正在初始化的 NVDAObject


在此方法内部,您可以检查此对象是否相关,然后相应地覆盖属性。


示例 7:使用 event_NVDAObject_init
为记事本编辑框设置标签


这个用于记事本的应用模块使 NVDA
将记事本的主编辑框报告为名称为“content”。也就是说,当它获得焦点时,NVDA
会说“Content edit(内容 编辑)”。


以下代码可以复制并粘贴到文本文件中,然后以 notepad.py
为名保存在 appModules 目录中。



importappModuleHandlerfromNVDAObjects.windowimportWindowclassAppModule(appModuleHandler.AppModule):defevent_NVDAObject_init(self,obj):ifisinstance(obj,Window)andobj.windowClassName==“Edit”andobj.windowControlID==15:obj.name=“Content”


在插件中解析额外的命令行参数


默认情况下,NVDA
接受有限的命令行参数集,并对未知的参数显示错误。但是,如果您想使用任何额外的参数,可以通过向
扩展点
addonHandler.isCLIParamKnown
添加处理程序来实现。请注意,由于命令行参数在 NVDA
启动后不久就会被处理,您的附加组件需要在全局插件中处理它们,因为在此阶段应用模块或其他驱动程序可能尚未加载。示例处理程序可以编写如下:



defprocessArgs(cliArgument:str)->bool:ifcliArgument==“—enable-addon-feature”:# Code to process your argument…returnTrue# Argument is known to the add-on and should not be flagged by NVDAreturnFalse# unknown argument - NVDA should warn user


然后需要注册该处理程序,最好在全局插件的构造函数中进行:


importaddonHandlerclassGlobalPlugin(globalPluginHandler.GlobalPlugin):definit(self)->None:super().init()addonHandler.isCLIParamKnown.register(processArgs)



将代码打包为 NVDA 附加组件


附加组件使用户能够轻松共享和安装插件、驱动程序、语音符号词典和盲文翻译表。它们可以打包成单个
NVDA 附加组件包,然后用户可以通过 NVDA
菜单“工具”下的“附加组件商店”将其安装到 NVDA
中。附加组件包本质上是一个标准的 zip
压缩包,文件扩展名为“nvda-addon”。它可以包含一个清单文件(manifest
file)、安装/卸载代码,以及包含插件、驱动程序、语音符号词典和盲文翻译表的目录。


Zip 压缩包中的非 ASCII 文件名


如果您的附加组件包含带有非 ASCII(非英语)字符的文件,您应该在创建
zip 压缩包时确保其使用 UTF-8
文件名。这意味着无论系统配置的语言是什么,这些文件都可以在所有系统上正确解压。不幸的是,许多
zip 压缩工具(包括 Windows
资源管理器)都不支持这一点。通常,即使在支持该功能的压缩工具中,也必须显式启用它。7-Zip
支持此功能,但必须通过指定“cu=on”方法参数来启用。


清单文件


每个附加组件包必须包含一个名为 manifest.ini 的清单文件。这必须是一个
UTF-8 编码的文本文件。此清单文件包含键值对(key =
value),用于声明附加组件的名称、版本和描述等信息。


可用字段


虽然强烈建议清单文件包含所有字段,但标记为必填的字段必须包含在内。否则,附加组件将无法安装。


  • name(字符串,必填):附加组件的简短、唯一的名称。

      • 推荐的命名约定是小驼峰命名法(lowerCamelCase)。

      • 用于在内部区分附加组件,同时也用作附加组件在用户配置目录中的目录名称。

      • 应避免使用特殊字符,因为附加组件名称将用作文件夹名称。预期字符为字母、数字、空格、下划线和连字符。

  • summary(字符串,必填):向用户显示的附加组件名称。

  • version(字符串,必填):此附加组件的版本;例如
    2.0.1。上传到附加组件商店时适用某些要求:

      • 使用 <主版本号>.<次版本号>
        <主版本号>.<次版本号>.<修订号>
        格式。

      • 为了让用户能够更新到此附加组件,版本号必须大于上次上传的版本。

      • 附加组件版本对于附加组件名称和通道(channel)应该是唯一的,这意味着同一附加组件的
        beta、stable(稳定版)和
        dev(开发版)不能共享同一个版本号。这是为了确保能够有从最新到最旧的唯一排序。

      • 建议的约定是:开发版增加修订号(patch),beta
        版增加次版本号(minor),稳定版增加主版本号(major)。

    • author(字符串,必填):此附加组件的作者,最好采用
      全名 <电子邮件地址> 的格式;例如
      Michael Curran <[email protected]{.
      cfemail
      cfemail=“75181c161e35100d14180519105b161a18”}>

  • description(字符串):一两句话描述附加组件的功能。

  • changelog(字符串):上一版本与最新附加组件版本之间的更改列表。

      • 用于向用户通报附加组件版本中包含的更改。

      • 更改可以包括新功能、变更、错误修复以及本地化更新(如果有)。

      • 可以使用 Markdown 格式化更改列表,因为它们将被转换为 HTML
        并在浏览模式中显示。

      • 在发布附加组件更新时,应尽可能编辑更改内容。这意味着并非所有附加组件版本都会包含显著的更改。

  • url(字符串):可以找到此附加组件、更多信息和升级的 URL。

      • 提交到附加组件商店时,URL 必须以 https:// 开头。

  • docFileName(字符串):此附加组件的主文档文件的名称;例如
    readme.html。有关更多详细信息,请参阅 附加组件文档 部分。

  • minimumNVDAVersion(字符串,必填):安装或启用此附加组件所需的最低
    NVDA 版本。

      • 例如 “2021.1”

      • 必须是三部分版本号字符串,即
        年份.主版本.次版本,或两部分版本号字符串
        年份.主版本。在后一种情况下,次版本默认为 0。

      • 默认为 “0.0.0”

      • 必须小于或等于 lastTestedNVDAVersion

      • 必须匹配有效的 API 版本才能提交到附加组件商店。有效的 API 版本可在
        GitHub 上
        找到。

  • lastTestedNVDAVersion(字符串,必填):此附加组件经过测试的最新 NVDA
    版本。

      • 例如 “2026.3.3”

      • 必须是三部分版本号字符串,即
        年份.主版本.次版本,或两部分版本号字符串
        年份.主版本。在后一种情况下,次版本默认为 0。

      • 默认为 “0.0.0”

      • 必须大于或等于 minimumNVDAVersion

      • 必须匹配有效的 API 版本才能提交到附加组件商店。有效的 API 版本可在
        GitHub 上
        找到。


所有字符串值必须用引号括起来,如下例所示。


特别是 lastTestedNVDAVersion
字段用于确保用户可以放心地安装附加组件。它允许附加组件作者保证该附加组件不会导致不稳定或破坏用户的系统。如果未提供此字段,或者其值小于当前的
NVDA 版本(忽略小版本更新,例如
2018.3.1),则会警告用户不要安装该附加组件。


清单文件还可以指定有关附加组件提供的任何额外语音符号词典或盲文翻译表的信息。请参阅
语音符号词典
和 盲文翻译表 部分。


清单文件示例



name=“myTestAddon”summary=“Cool Test Add-on”version=“1.0.0”description=“An example add-on showing how to create add-ons!”author=“Michael Curran <[email protected]{.cfemail_
cfemail=“d1bcb8b2ba91b4a9b0bca1bdb4ffb2bebc”}>”url=“https://github.com/nvaccess/nvda/blob/master/projectDocs/dev/addons.md“docFileName=“readme.html”minimumNVDAVersion=“2021.1”lastTestedNVDAVersion=“2025.3.3”


插件与驱动程序


附加组件中可以包含以下插件和驱动程序:



    • 应用模块:将它们放在压缩包中的 appModules 目录下。

    • 盲文显示器驱动程序:将它们放在压缩包中的
      brailleDisplayDrivers 目录下。

    • 全局插件:将它们放在压缩包中的 globalPlugins
      目录下。

    • 语音合成器驱动程序:将它们放在压缩包中的 synthDrivers
      目录下。

    • 语音符号词典:将它们放在一个或多个 区域设置
      的目录中,文件名格式为 symbols-<名称>.dic,例如
      locale\en\symbols-greek.dic

    • 盲文翻译表:将它们放在压缩包中的
      brailleTables 目录下。


可选的安装/卸载代码


如果您需要在从 NVDA
安装或卸载附加组件时执行代码(例如验证许可证信息或将文件复制到自定义位置),您可以在压缩包中提供一个名为
installTasks.py 的 Python 文件,其中包含 NVDA
在安装或卸载您的附加组件时将调用的特殊函数。此文件应避免加载任何非绝对必要的模块,特别是您自己附加组件中的
Python C 扩展或
dll,因为这可能会导致稍后删除附加组件失败。但是,如果确实发生这种情况,附加组件目录将被重命名,然后在
NVDA
下次重新启动后被删除。最后,它不应依赖于其他附加组件的存在或状态,因为它们可能未安装、可能已被删除或可能尚未初始化。


onInstall 函数


NVDA 在将附加组件解压到 NVDA 中完成后,将在
installTasks.py 中查找并执行 onInstall
函数。请注意,尽管此时附加组件已被解压,但在 NVDA
重新启动、目录被重命名并且附加组件真正首次加载之前,其目录将带有
.pendingInstall
后缀。如果此函数引发异常,附加组件的安装将失败,其目录将被清理。


onUninstall 函数


当用户选择移除附加组件后 NVDA 重新启动时,NVDA 将在
installTasks.py 中查找并执行 onUninstall
函数。此函数完成后,附加组件的目录将被自动移除。由于这发生在 NVDA
启动时且在其他组件初始化之前,因此此函数无法向用户请求输入。


本地化附加组件


可以为您的附加组件提供特定于区域设置的信息和消息。区域设置信息可以存储在压缩包中的
locale
目录下。此目录应包含它所支持的每种语言的子目录,使用与 NVDA
其余部分相同的语言代码格式;例如 en 代表英语,fr_CA 代表加拿大法语。


特定区域设置的清单文件


这些语言目录中的每一个都可以包含一个名为 manifest.ini
的特定区域设置的清单文件,该文件可以包含用于翻译的清单字段的一个小子集。这些字段是
summary
description。您还可以覆盖语音符号词典和盲文翻译表的
displayName 字段。所有其他字段将被忽略。


特定区域设置的消息

每个语言目录还可以包含 gettext 信息,这是用于翻译 NVDA
其余用户界面和报告消息的系统。与 NVDA 的其余部分一样,编译后的 gettext
数据库文件 nvda.mo 应放置在此目录内的
LC_MESSAGES 目录中。为了允许附加组件中的插件通过调用
()ngettext()npgettext()
pgettext() 来访问 gettext 消息信息,您必须在每个 Python
模块的顶部通过调用 addonHandler.initTranslation()
来初始化翻译。不能在不属于附加组件的模块(例如在暂存区子目录中)调用此函数。有关
gettext 和 NVDA 翻译的更多信息,请阅读 翻译 NVDA
页面。


语音符号词典


您可以在附加组件中提供自定义语音符号词典以改善符号发音。创建自定义语音符号词典的过程与
现有符号的翻译过程
非常相似。请注意,不支持 复杂符号。


自定义词典必须放置在语言目录中,并且文件名格式为
symbols-<名称>.dic,其中 <名称>
是必须在附加组件清单中提供的名称。所有区域设置都隐式继承英语的符号信息,尽管可以针对特定区域设置覆盖其中的任何信息。


添加未标记为必填的词典时,必须提供一些信息,例如其显示名称,因为它应显示在设置对话框的“语音”类别中。词典也可以标记为必填,在这种情况下,它将随附加组件始终启用。当附加组件附带词典时,此信息包含在其清单的可选
symbolDictionaries 部分中。例如:



[symbolDictionaries][[greek]]displayName=Greekmandatory=false[[hebrew]]displayName=Biblical Hebrewmandatory=true


在上面的示例中,greek 是一个可选词典,将列在 NVDA
设置对话框“语音”类别下的“字符和符号处理的额外词典”设置中。其文件将存储为
locale\en\symbols-greek.dic,而符号的法语翻译则存储在
locale\fr\symbols-greek.dic 中。在使用法语版 NVDA
时,法语词典中未定义的符号将继承英语的符号信息。


同样在示例中,hebrew
词典被标记为必填,因此只要附加组件处于活动状态,它就会始终启用。其文件将存储为
locale\en\symbols-hebrew.dic,而符号的法语翻译则存储在
locale\fr\symbols-hebrew.dic 中。


请注意,为了翻译词典的显示名称,应在 区域设置清单
中添加一个条目。例如,将以下内容添加到
locale\fr\manifest.ini



[symbolDictionaries][[hebrew]]displayName=Hébreu Biblique


附加组件文档


附加组件的文档应放置在压缩包中的 doc 目录下。与
locale
目录类似,此目录应包含提供文档的每种语言的子目录。


用户可以通过打开附加组件商店、选择附加组件并按下“附加组件帮助”按钮来访问特定附加组件的文档。这将打开清单的
docFileName 参数中命名的文件。NVDA
将在相应的语言目录中搜索此文件。例如,如果 docFileName 设置为
readme.html 且用户使用的是英语,NVDA 将打开 doc.html。


盲文翻译表


尽管 NVDA 附带了由 liblouis 项目
提供的一百多个盲文翻译表,旨在满足大多数需求,但它也支持添加自定义表。自定义表必须放置在附加组件的
brailleTables 目录或暂存区目录的子目录中。这些表可以替换
NVDA 附带的标准表,也可以是全新的表。


添加表时,必须提供一些信息,例如其在“首选项”对话框中的显示名称、是否支持输入和/或输出,以及是否用于缩写盲文(contracted
braille)。当附加组件附带表时,此信息包含在其清单的可选
brailleTables 部分中。例如:



[brailleTables] [[fr-bfu-tabmod-comp8.utb]] displayName = French (unified) 8 dot computer braille - Addition contracted = False output = True input = True [[no-no-8dot.utb]] displayName = Norwegian 8 dot computer braille - Replacement contracted = False output = True input = True


在上面的示例中,fr-bfu-tabmod-comp8.utb 是一个新表,而
no-no-8dot.utb 替换了 NVDA
中已包含的一个表。这两个表都需要在附加组件的 brailleTables
目录中附带。也可以在清单中包含一个随 NVDA
附带但在“首选项”对话框中原本无法选择的表。在这种情况下,不需要在附加组件的
brailleTables 目录中附带该表。


提供自定义表,无论其文件名是否与标准表相同,都需要您在附加组件的清单中定义该表。此规则的唯一例外适用于包含在其他表中的表。虽然它们不必包含在附加组件的清单中,但它们只能从属于同一附加组件的其他表中被包含。


请注意,为了翻译表的显示名称,应在 区域设置清单
中添加一个条目。例如,将以下内容添加到
locale\fr\manifest.ini



[brailleTables][[no-no-8dot.utb]]displayName=Norvégien Braille informatique 8 points - Remplacement


自定义表也可以放置在暂存区目录的 brailleTables
子目录中。在这种情况下,表元数据可以放置在暂存区根目录的
manifest.ini
文件中,格式与上述示例完全相同。基本上,这意味着无论使用附加组件还是暂存区,要求和实现步骤都是相同的。请注意,暂存区中的
manifest.ini
文件仅解析盲文表元数据。文件中的其他附加组件元数据将被忽略。


有关盲文翻译表格式的详细信息,请参阅 liblouis 文档。


NVDA Python 控制台


NVDA Python 控制台在 NVDA 内部模拟了交互式 Python
解释器。它是一个开发工具,对于调试、常规检查 NVDA
内部机制或检查应用程序的无障碍层次结构非常有用。


用法


可以通过两种方式激活控制台:



    • 按下 NVDA+control+z。如果以这种方式激活,将会获取按下按键时 NVDA
      当前状态的快照,并将其保存在控制台中可用的特定变量中。有关更多详细信息,请参阅
      快照变量。

    • 从 NVDA 系统托盘菜单中选择“工具 -> Python 控制台”。


控制台类似于标准的交互式 Python
解释器。输入每次接受一行,并在按下回车键时进行处理。可以一次从剪贴板粘贴多行,它们将被逐行处理。您可以使用向上和向下箭头键浏览以前输入行的历史记录。


输出(来自解释器的响应)将在按下回车键时朗读。f6
键在输入和输出控件之间切换。在输出控件上时,alt+up/down
跳转到上一个/下一个结果(添加 shift 进行选择)。按下 control+l
清除输出。


最后执行的命令的结果存储在全局变量“”中。这会遮蔽(shadow)同名的内置
gettext 函数。可以通过执行 del
来取消遮蔽,或者通过执行
= 来完全避免这种情况。


关闭控制台窗口(使用 escape 或
alt+F4)只是将其隐藏。这允许用户返回到关闭时留下的会话,包括历史记录和变量。


命名空间


自动导入


为了方便起见,以下模块和变量会在控制台中自动导入:sys, os, wx, log
(来自 logHandler), api, queueHandler, config, controlTypes, textInfos,
braille, speech, vision, appModules, globalPlugins


请参阅:pythonConsole.PythonConsole.initNamespace


快照变量


每当按下 NVDA+control+z 时,控制台中可用的某些变量将根据 NVDA
的当前状态进行赋值。这些变量是:



    • focus:当前焦点对象

    • focusAnc:当前焦点对象的祖先

    • fdl:焦点差异层级(Focus difference
      level);即当前焦点和上一个焦点的祖先产生差异的层级

    • fg:当前前景对象

    • nav:当前导航对象

    • caretObj:包含光标(焦点或树拦截器,如果有的话)的对象

    • caretPos:光标位置处的文本信息(TextInfo)

    • review:代表用户浏览位置的当前 TextInfo
      实例

    • mouse:当前鼠标对象

    • brlRegions:来自活动盲文缓冲区的盲文区域


Tab 补全


输入控件支持变量和成员属性名称的 Tab 补全。如果只有一个候选项,按一次
Tab 键即可补全当前输入。如果有多个候选项,再按一次 Tab
键将打开一个列出所有匹配可能性的菜单。默认情况下,仅列出“公共(public)”成员属性。也就是说,如果输入是“nav.”,则会提议没有前导下划线的属性名称。如果输入是“nav.”,则会提议具有单个前导下划线的属性名称。同样,如果输入是“nav.__”,则会提议具有两个前导下划线的属性名称。


远程 Python 控制台


在 NVDA 的源码构建版本中提供了一个远程 Python 控制台,适用于需要对
NVDA 进行远程调试的情况。它与上面讨论的 本地 Python 控制台 类似,但通过 TCP
进行访问。


请注意,这存在极大的安全风险。它在 NV Access
分发的二进制构建版本中不可用,并且您只有在连接到受信任的网络时才应启用它。


用法


要启用远程 Python 控制台,请使用本地 Python 控制台导入
remotePythonConsole 并调用
remotePythonConsole.initialize()。然后,您可以通过 TCP 端口 6832
连接到它。


不支持以前输入行的历史记录。


命名空间与 本地 Python
控制台中的命名空间 相同。


有一些特殊函数:



    • snap():获取 NVDA 当前状态的快照,并将其保存在 快照变量
      中。

    • rmSnap():移除所有快照变量。


扩展点


NVDA 的 extensionPoints 模块允许 NVDA
不同部分或附加组件中的代码执行以下任务:



    • 在动作发生或状态更改时收到通知。

    • 作为通知的一部分,接收与动作或已更改状态相关的变量。

    • 基于特定条件,取消或修改 NVDA 原本打算执行的动作。

    • 修改 NVDA
      正在使用的数据(例如在语音序列或盲文被朗读或显示之前对其进行更改)。

    • 在执行干预操作时,延迟 NVDA 正在执行的操作。


扩展点共有五种类型:



































Type Purpose
Action 允许某些代码了解其他代码正在做什么。例如,附加组件可以在配置文件更改之前或之后收到通知。
Filter 编辑数据。在 speech 模块中注册的 Filter
可能允许在语音字符串被朗读之前对其进行更改。
Decider 运行每个已注册的处理程序,直到其中一个返回
False。如果有一个返回了
False,则可用于阻止调用代码的运行。
AccumulatingDecider Decider
类似,但始终运行所有已注册的处理程序,并且仅在最后判断是否有其中一个失败。默认情况下,每个处理程序的预期结果为
True,但也可以预期为 False
Chain 允许注册返回可迭代对象(主要是生成器)的处理程序。对
Chain 调用 iter
会返回一个生成器,该生成器会遍历所有处理程序。

以下各节提供了 NVDA
中当前已定义的扩展点列表及其简要描述。有关进一步说明,请参阅相关文件中的代码文档或代码本身。以下各节的标题表示列出扩展点所在的包或模块。


有关如何定义和使用新扩展点的示例,请参阅 extensionPoints
包的代码文档。


braille















































Type Extension Point Description
Filter filter_displaySize [已弃用] 允许组件或附加组件更改用于盲文输出的显示大小。
Filter filter_displayDimensions 允许组件或附加组件更改用于盲文输出的显示器的行数和列数。
Action displaySizeChanged 在显示大小发生更改时发出通知。
Action pre_writeCells 在单元格即将写入盲文显示器时发出通知。
Action displayChanged 在盲文显示器发生更改时发出通知。
Decider decide_enabled 允许决定是否应强制禁用盲文处理程序。

appModuleHandler

















Type Extension Point Description
Action post_appSwitch 在前台应用程序发生更改时触发。

addonHandler






















Type Extension Point Description
AccumulatingDecider isCLIParamKnown 允许添加适用于插件的 NVDA 命令行参数。有关更多信息,请参阅 开发者指南的这一节。

brailleViewer






















Type Extension Point Description
Action postBrailleViewerToolToggledAction 每次创建/显示或隐藏/销毁盲文查看器时触发。

config










































Type Extension Point Description
Action post_configProfileSwitch 在配置文件切换后发出通知。
Action pre_configSave 在 NVDA 的配置保存到磁盘之前发出通知。
Action post_configSave 在 NVDA 的配置保存到磁盘之后发出通知。
Action pre_configReset 在从磁盘重新加载配置或应用出厂默认设置之前发出通知。
Action post_configReset 在从磁盘重新加载配置或应用出厂默认设置之后发出通知。

core

















Type Extension Point Description
Action postNvdaStartup 在 NVDA 完成启动后发出通知。

inputCore



























Type Extension Point Description
Decider decide_handleRawKey 在收到原始键盘事件时、NVDA
进行任何处理之前发出通知,允许其他代码决定是否应处理该事件。
Decider decide_executeGesture 在手势即将执行时发出通知,允许其他代码决定是否应执行该手势。

logHandler






















Type Extension Point Description
Action _onErrorSoundRequested 每次需要播放错误声音时触发。不应直接使用此扩展点,而应通过调用
getOnErrorSoundRequested() 来获取它。

nvwave






















Type Extension Point Description
Decider decide_playWaveFile 在波形文件即将播放时发出通知,允许其他代码决定是否应播放。

speech















































Type Extension Point Description
Action speechCanceled 在语音被取消时触发。
Action pre_speechCanceled 在语音被取消之前触发。
Action pre_speech 在 NVDA 处理已准备好的语音之前触发。
Action post_speechPaused 在语音被暂停或恢复时触发。
Action pre_speechQueued 在语音被处理和规范化之后、直接入队之前触发。
Filter filter_speechSequence 允许组件或附加组件在语音序列传递给合成器驱动程序之前对其进行过滤。

synthDriverHandler





































Type Extension Point Description
Action synthIndexReached 在合成器在语音过程中到达某个索引时发出通知。
Action synthDoneSpeaking 在合成器完成朗读时发出通知。
Action synthChanged 在合成器发生更改时发出通知。
Action pre_synthSpeak 在当前合成器即将朗读某些内容时发出通知。

tones






















Type Extension Point Description
Decider decide_beep 在蜂鸣声即将生成并播放时发出通知,允许组件决定是否应播放。

treeInterceptorHandler






















Type Extension Point Description
Action post_browseModeStateChange 在浏览模式状态发生更改时发出通知。

utils.security






















Type Extension Point Description
Action post_sessionLockStateChanged 在会话锁定或解锁事件发生时发出通知。

winAPI.messageWindow






















Type Extension Point Description
Action pre_handleWindowMessage 在 NVDA
收到窗口消息时发出通知,允许组件在特定系统事件发生时执行操作。

winAPI.secureDesktop






















Type Extension Point Description
Action winAPI.secureDesktop.post_secureDesktopStateChange 在用户切换到安全桌面或从安全桌面切换回来时发出通知。

bdDetect

















Type Extension Point Description
Chain scanForDevices 可以迭代以扫描盲文设备。

vision.visionHandlerExtensionPoints.EventExtensionPoints


这些扩展点的预期使用和注册方式与其他扩展点不同。有关更多信息和详细描述,请参阅
EventExtensionPoints 类的文档。

























































Type Extension Point Notifies a vision enhancement provider when …
Action post_objectUpdate 对象属性已发生更改时。
Action post_focusChange 具有焦点的 NVDAObject 已发生更改时。
Action post_foregroundChange 前景 NVDAObject 已发生更改时。
Action post_caretMove 物理光标已移动时。
Action post_browseModeMove 虚拟光标已移动时。
Action post_reviewMove 浏览光标的位置已发生更改时。
Action post_mouseMove 鼠标已移动时。
Action post_coreCycle 每个核心周期结束时。

与用户沟通


消息对话框 API


消息对话框 API
提供了一种灵活的方式来向用户展示交互式消息。这些消息高度可定制,可以更改图标和声音、按钮标签、返回值和关闭行为,还可以附加自定义回调。


所有构成消息对话框 API 的类都可以从 gui.message
导入。虽然您不太可能需要全部使用它们,但它们列举如下:



    • ReturnCode:模态 MessageDialog
      的可能返回码。

    • EscapeCodeMessageDialog
      的退出行为。

    • DialogType:对话框类型(设置对话框的声音和图标)。

    • Button:按钮配置数据结构。

    • DefaultButton:预配置按钮的枚举。

    • DefaultButtonSet:常见按钮组合的枚举。

    • MessageDialog:实际的对话框类。


在许多简单情况下,您只需创建一个消息对话框并调用 Show
ShowModal 即可满足需求。例如:


fromgui.messageimportMessageDialogfromguiimportmainFrameMessageDialog(mainFrame,(“Hello world!”),).Show()


这将显示一个非模态(即非阻塞)对话框,其中包含文本”Hello
world!“和一个”确定”按钮。


如果您希望对话框是模态的(即阻止用户在 NVDA
中执行其他操作,直到他们响应对话框为止),可以改为调用
ShowModal


对于模态对话框,响应用户输入最简单的方式是通过返回码。



fromgui.messageimportDefaultButtonSet,ReturnCodesaveDialog=MessageDialog(mainFrame,(“Would you like to save your changes before exiting?”),(“Save changes?”),buttons=DefaultButtonSet.SAVENO_CANCEL)matchsaveDialog.ShowModal():caseReturnCode.SAVE:# 保存更改并关闭caseReturnCode.NO:# 丢弃更改并关闭caseReturnCode.CANCEL:# 不关闭



对于非模态对话框,响应用户按下按钮最简单的方式是通过回调方法。


fromgui.messageimportPayloaddefreadChangelog(payload:Payload):# 执行某些操作defdownloadUpdate(payload:Payload):# 执行某些操作defremindLater(payload:Payload):# 执行某些操作updateDialog=MessageDialog(mainFrame,“An update is available. ““Would you like to download it now?”,“Update”,buttons=None,).addYesButton(callback=downloadUpdate).addNoButton(label=(“&Remind me later”),fallbackAction=True,callback=remindLater).addHelpButton(label=_(“What’s &new”),callback=readChangelog)updateDialog.Show()


您也可以稍后设置 addButton 的许多参数:



    • 可以通过在消息对话框实例上调用 setDefaultFocus
      并传入要设为默认焦点的按钮 ID 来设置默认焦点。

    • 可以通过调用 setFallbackAction
      SetEscapeId 并传入执行回退操作的按钮 ID
      来稍后设置回退操作。

    • 可以通过调用 setButtonLabel 并传入按钮 ID
      和新标签来更改按钮的标签。


回退操作


回退操作是在对话框关闭时执行的操作,此时用户并未按下您添加到对话框的任何按钮。这可能由于多种原因发生:



    • 用户按下 escalt+f4 关闭对话框。

    • 用户使用标题栏关闭按钮或系统菜单关闭项关闭对话框。

    • 用户从任务视图、任务栏或应用切换器关闭对话框。

    • 用户正在退出 NVDA。

    • NVDA 的其他部分或附加组件要求关闭对话框。


默认情况下,回退操作设置为
EscapeCode.CANCEL_OR_AFFIRMATIVE。这意味着如果存在取消按钮,回退操作将是取消按钮;否则是
ID 为 dialog.GetAffirmativeId() 的按钮(默认为
ReturnCode.OK);如果对话框中不存在具有任一 ID 的按钮,则为
None。如果您愿意,可以使用
dialog.SetAffirmativeId(id)
来更改在没有取消按钮时次要使用的按钮 ID。回退操作也可以设置为
EscapeCode.NO_FALLBACK
以完全禁止以这种方式关闭对话框。如果设置为任何其他值,该值必须是用作默认操作的按钮的
ID。


在某些情况下,对话框可能会被强制关闭。如果对话框以模态显示,当回退操作为
EscapeCode.NO_FALLBACK
或未找到时,将使用计算得出的回退操作。当对话框被强制关闭时,计算回退操作的优先级顺序如下:



    1. 开发者设置的回退操作。

    1. 开发者设置的默认焦点。

    1. 添加到对话框的第一个会关闭对话框的按钮。

    1. 添加到对话框的第一个按钮,无论它是否关闭对话框。

    1. 一个只关闭对话框而不执行任何其他操作的虚拟操作。在这种情况下,且仅在这种情况下,模态显示对话框的返回码将是
      EscapeCode.NO_FALLBACK


关于线程的说明


重要: 大多数 MessageDialog
方法不是线程安全的。从非 GUI
线程调用这些方法可能导致崩溃或不可预测的行为。


MessageDialog
或其实例上调用非线程安全方法时,请确保在 GUI 线程上执行。要使用 wxPython
执行此操作,可以使用 wx.CallAfter
wx.CallLater。由于这些操作会安排传入的可调用对象在 GUI
线程上执行,它们会立即返回,并且不会返回传入可调用对象的返回值。如果您想等待可调用对象完成,或关心其返回值,请考虑使用
gui.guiHelper.wxCallOnMain


wxCallOnMain 函数在 GUI
线程上执行您传入的可调用对象及其任何位置参数和关键字参数。它会阻塞调用线程,直到传入的可调用对象返回或引发异常,此时它返回返回值或重新引发该异常。



# 要调用someFunction(arg1,arg2,kw1=value1,kw2=value2)# 在 GUI 线程上:wxCallOnMain(someFunction,arg1,arg2,kw=value1,kw2=value2)


实际上,您不能从 GUI
线程以外的任何线程创建、初始化或显示(模态或非模态)MessageDialog


按钮


您可以通过多种方式添加按钮:



    • 在初始化时,将 ButtonCollection 传递给
      MessageDialogbuttons 仅关键字参数。

    • MessageDialog 实例上调用
      addButton,可以传入 Button 实例或简单参数。

        • 使用 Button 实例调用 addButton
          时,您可以通过提供关键字参数来覆盖除 id
          之外的所有参数。

        • 使用简单参数调用 addButton 时,它接受的参数与
          Button 的参数相同。

        • 在两种情况下,idbutton
          都是第一个参数,且仅限位置参数。

    • 使用 ButtonCollection 调用
      addButtons

    • 调用任何添加按钮辅助方法。


无论您如何添加按钮,都不能向同一个 MessageDialog
添加多个具有相同 ID 的按钮。


Button 是一个不可变数据结构,包含向
MessageDialog 添加按钮所需的所有信息。其字段如下:





























































Field Type Default Explanation
id ReturnCode 无默认值 用于引用按钮的 ID。
label str 无默认值 显示在按钮上的文本标签。使用 & 符号作为前缀来标记加速键。
callback CallableNone None 点击按钮时调用的函数。这对非模态对话框最有用。
defaultFocus bool False 是否显式将按钮设为默认焦点。(1)
fallbackAction bool False 按钮是否应作为回退操作,即当用户按下
esc、使用系统菜单或标题栏关闭按钮,或以编程方式要求关闭对话框时调用的操作。(2)
closesDialog bool True 按下按钮时是否应关闭对话框。(3)
returnCode ReturnCodeNone None 关闭模态对话框时返回的值。如果为 None,则使用按钮的
ID。


    1. 设置 defaultFocus 只会覆盖默认焦点:



        • 如果没有按钮具有此属性,第一个按钮将是默认焦点。

        • 如果多个按钮具有此属性,最后一个将是默认焦点。

    1. fallbackAction 仅设置是否覆盖回退操作:



        • 如果对话框的回退操作设置为
          EscapeCode.CANCEL_OR_AFFIRMATIVE(默认值),且其 ID 为
          ReturnCode.CANCEL(或者如果没有
          id=ReturnCode.CANCEL 的按钮,则为
          GetAffirmativeId() 的值,默认为
          ReturnCode.OK),即使添加时
          fallbackAction=False,此按钮仍将是回退操作。要设置对话框没有回退操作,请使用
          setFallbackAction(EscapeCode.NO_FALLBACK)

        • 如果多个按钮具有此属性,最后一个将是回退操作。

    1. 不支持 fallbackAction=True
      closesDialog=False 的按钮:



        • 添加 fallbackAction=True
          closesDialog=False 的按钮时,closesDialog
          将被设置为 True

        • 如果您尝试使用不关闭对话框的按钮 ID 调用
          setFallbackAction,将引发 ValueError


DefaultButton
枚举中有许多预配置的按钮可供使用,并附带预翻译的标签。这些按钮都不会显式将自己设置为回退操作。您还可以使用添加按钮辅助方法将这些按钮中的任何一个添加到现有的
MessageDialog 实例中,这也允许您覆盖除 id
参数之外的所有参数。以下默认按钮可用:






































































Button Label ID/返回码 关闭对话框 添加按钮辅助方法
APPLY &Apply ReturnCode.APPLY addApplyButton
CANCEL Cancel ReturnCode.CANCEL addCancelButton
CLOSE Close ReturnCode.CLOSE addCloseButton
HELP Help ReturnCode.HELP addHelpButton
NO &No ReturnCode.NO addNoButton
OK OK ReturnCode.OK addOkButton
SAVE &Save ReturnCode.SAVE addSaveButton
YES &Yes ReturnCode.YES addYesButton

由于您通常希望对话框上有多个按钮,因此还有一些预定义的按钮集可用作
DefaultButtonSet 枚举的成员。它们都由
DefaultButton
的成员组成。您还可以使用添加按钮集辅助方法将这些默认按钮集中的任何一个添加到现有的
MessageDialog 中。以下默认按钮集可用:











































Button set 包含 添加按钮集辅助方法 备注
OK_CANCEL DefaultButton.OK
DefaultButton.Cancel
addOkCancelButtons  
YES_NO DefaultButton.YESDefaultButton.NO addYesNoButtons 如果您希望用户能够按 escape
关闭只有这些按钮的对话框,则必须设置回退操作。
YES_NO_CANCEL DefaultButton.YESDefaultButton.NO
DefaultButton.CANCEL
addYesNoCancelButtons  
SAVE_NO_CANCEL DefaultButton.SAVEDefaultButton.NODefaultButton.CANCEL addSaveNoCancelButtons “否”按钮的标签被覆盖为”Do&n’t save”。

如果没有标准 ReturnCode 值适合您的按钮,您还可以使用
ReturnCode.CUSTOM_1
ReturnCode.CUSTOM_5,它们不会与任何内置标识符冲突。


回调


响应按钮按下的便捷方式,特别是对于非模态消息对话框,是将回调附加到按钮。这可以通过向
addButtonaddButtons
或任何添加按钮辅助方法传递 callback 函数来实现。


回调应该是只接受一个位置参数的函数。调用时,将传入一个
Payload
数据结构。此数据结构目前不包含任何信息,但将来可能会扩展以包含有关对话框状态和调用回调的上下文的信息。


便捷方法


MessageDialog
类还提供了许多便捷方法,用于显示常见类型的模态对话框。每个方法都需要一个消息字符串,以及可选的标题字符串和父窗口。它们都支持通过关键字参数覆盖按钮上的标签。它们都是线程安全的。提供了以下便捷类方法(用于覆盖按钮标签的关键字参数在括号中指示):
































方法 按钮 返回值
alert 确定(okLabel None
confirm 确定(okLabel)和取消(cancelLabel ReturnCode.OKReturnCode.CANCEL
ask 是(yesLabel)、否(noLabel)和取消(cancelLabel ReturnCode.YESReturnCode.NO
ReturnCode.CANCEL



参考


原文: a href="https://web.archive.org/web/20260808030521/https://download.nvaccess.org/documentation/developerGuide.html"https://download.nvaccess.org/documentation/developerGuide.html




译者


张赐荣,视障者,资深NVDA软件体验设计师、信息无障碍解决方案研发专家,长期致力于数字化产品的可及性研究与用户体验优化工作,专注于为残障人士消除数字鸿沟。


曾主持过国内外多个互联网大型产品的无障碍改造项目及发布大量原创技术指南,系统性地推动了无障碍技术的标准化进程与落地部署工作,是一位在信息无障碍行业具有显著影响力的领军人物。