「中途入职、有视觉障碍的同事,无法用屏幕阅读器操作核心接单应用。浏览器与邮件没问题,只有我们业务应用的朗读不对劲。有办法吗?」── 来自客户 IT 部门的这类咨询正在增加。
背景之一是法律框架。2021 年残障者歧视消除法修正于 2024 年 4 月 1 日施行,对残障者「提供合理便利」也成为事业的义务。1 再者,开头那种员工与公司的关系(雇用领域)属于残障者就业促进法,该法自 2016 年 4 月起就课予雇主合理便利义务。2「无障碍是网站的事,跟公司内部 Windows 应用无关」这种想法,在法律与实务上都不成立了。
另一方面,从开发现场诚实地说,「不知道该做什么」是常见位置。Windows 桌面应用的无障碍信息比 Web 少,也没有事后的魔法解。也不必悲观。只要理解屏幕阅读器读取应用的机制(UI Automation),并掌握名称、键盘、颜色的基础,业务应用的可用性就会大幅改善。 而且其中许多改善,无论有没有障碍,都会提高每位用户的生产力。
本文以日本业务应用的开发者与 IT 人员为对象,从法律框架与标准的最低整理,一路连到 UI Automation 的机制、WinForms/WPF 实作、键盘操作、颜色与对比、验证工具,以及现实的优先顺序。
flowchart TB
accTitle: 本文的流程
accDescr: 本文结构依序从整理法律框架与标准,连到 UI Automation 的机制、WinForms 与 WPF 实作、键盘操作、颜色与对比、验证工具,以及如何排优先顺序
law["整理法律框架与标准"] --> uia["UI Automation 的机制"]
uia --> impl["WinForms/WPF 实作"]
impl --> kb["键盘操作"]
kb --> color["颜色与对比"]
color --> verify["验证工具"]
verify --> prio["如何排优先顺序"]
图 1: 本文把法律框架到机制、实作、验证、优先顺序连成一条流程。
1. 先说结论
- 提供合理便利自 2024 年 4 月 1 日起也是事业的义务。残障者表明要除去障碍的意图时,须在不过度负担的范围内对应。雇用领域在残障者就业促进法,自 2016 年 4 月起就是雇主义务。12
- 合理便利是「透过建设性对话回应个别要求」的过程;事先让应用更好用是「环境改善」(努力义务)。完美的事先对应不是义务;重要的是不要单方面拒绝对话。1
- 无障碍的技术准则集中在 WCAG(JIS X 8341-3:2016)。JIS X 8341-3:2016 是与 WCAG 2.0 内容相同的对应标准,W3C 的 WCAG2ICT 说明如何套用到非 Web 软件。桌面应用可用同一套思路检查。34
- 屏幕阅读器透过 UI Automation(UIA)读取应用。UIA 树上各元素持有的属性──Name、ControlType 等──以及 Invoke、Value、SelectionItem 等控制模式,是朗读与操作的材料。5
- Name 空白的按钮只会被念成「按钮」。最高优先的修正是命名。WinForms 用 AccessibleName 与把 Label 关联到 Tab 顺序;WPF 用 AutomationProperties.Name/LabeledBy。67
- 单靠键盘就能到达每个功能,是 WCAG 成功准则(2.1.1),同时也是熟练操作者的输入速度本身。把 Tab 顺序、快速键、焦点指示做好,直接连到每位用户的效率。8
- 文字对比以 4.5:1 以上为指引,不要只靠颜色传达信息。在对比主题(高对比)下,尊重系统颜色而不是写死的颜色。89
- 验证把 Accessibility Insights for Windows 的 FastPass 与屏幕阅读器实地检查组合起来。因为坐在同一套 UIA 基础上,这份工作也与 FlaUI 这类 UI 自动测试资产互相划算。10
- 不必一次修所有画面。现实顺序是(1)从那位用户在用的画面、(2)新开发符合标准、(3)靠修共用控制项横向展开。
一句话:无障碍对应是「在 UIA 树上公开正确的名称与操作,并守住键盘与颜色的基础」。
2. 整理法律框架与标准 ── 「变成义务」改变了什么
2.1. 残障者歧视消除法 ── 从 2024 年 4 月起,事业也有提供合理便利的义务
残障者歧视消除法禁止行政机关与事业对残障者做「不正当差别待遇」,并要求「提供合理便利」。在 2021 年(令和 3 年)修正中,事业提供合理便利从努力义务变成义务,修正法于 2024 年 4 月 1 日(令和 6 年)施行。1
依内阁府传单,提供合理便利是残障者表明需要某种对应以除去社会中的障碍时,在不过度负担的范围内回应。而且内容因障碍特性、场面、状况而异,因此强调残障者与事业把对话叠起来、一起思考对应的「建设性对话」。单方面拒绝建设性对话,被写成可能构成违反提供合理便利的义务。1
这里有两个实务区分很重要。
- 「事先把一切准备好」并不是变成义务的那件事。针对不特定多数残障者的事先改善──手册与训练等软件侧、设施无障碍等硬件侧──称为「环境改善」,这是努力义务。1 事先让业务应用能用屏幕阅读器使用,可想成环境改善的努力。环境改善走得愈远,个别合理便利的负担愈轻。
- 雇用领域不在残障者歧视消除法,而在残障者就业促进法。同一份传单也写雇用与工作依残障者就业促进法的规定。1 而该法依 2016 年 4 月(平成 28 年)施行的修正,禁止雇用上的身心障碍歧视,以及在不过度负担范围内提供合理便利,已课予雇主。2 开头「员工用不了业务应用」的咨询,事实上早在 2024 年之前就已是义务领域。
flowchart TB
accTitle: 合理便利与环境改善的定位
accDescr: 一般事业与残障者的关系在消除歧视法,透过建设性对话回应个别要求的合理便利自 2024 年 4 月起是义务;雇用领域自 2016 年 4 月起依就业促进法是雇主义务;事先让应用更好用是环境改善、努力义务
scene{"哪个场面?"}
scene -->|"事业 + 障碍"| kaisho["消除歧视法"]
scene -->|"雇用/工作"| koyou["就业促进法"]
kaisho --> moushide["经对话提出要求"]
moushide --> hairyo["提供调整"]
hairyo -.-> hairyoN["2024 年起义务"]
koyou --> koyougimu["提供调整"]
koyougimu -.-> koyouN["2016 年起义务"]
kaisho -.-> kankyo["事先让应用更好用"]
kankyo -.-> kankyoN["环境改善"]
kankyoN -.-> kankyoN2["努力义务"]
kankyo -.-> moushide
图 2: 适用法律依场面拆开;合理便利是义务,事先修正是环境改善、努力义务。
个案在法律上怎么处理依情况而定。本文不踏进法律解释;从工程师被要求对应时能做什么的视角前进。一手数据请参内阁府与厚生劳动省数据。12
2.2. JIS X 8341-3 与 WCAG ── 「Web 准则」也延伸到软件
技术准则侧集中在 JIS X 8341-3:2016。此标准是 ISO/IEC 40500:2012 的对应标准,标准本文与 W3C 的 WCAG 2.0 内容相同。3 若想具体知道「无障碍对应」由什么组成,读 WCAG 成功准则(现已在 WCAG 2.1/2.2 扩充)是最短路径,WAIC 也公开日文翻译。8
「WCAG 是 Web 内容的准则吗?」这个问题合理,但 W3C 在称为 WCAG2ICT(Guidance on Applying WCAG 2 to Non-Web Information and Communications Technologies)的 Group Note 里,整理了如何把 WCAG 2.0/2.1/2.2 成功准则套用到非 Web 文件与软件。4 也就是说,「文字替代」「对比」「键盘操作」「颜色不是唯一手段」这类思路,可用与 Web 同一框架套到 Windows 桌面应用。本文第 3 章起把那套思路落到具体的 WinForms/WPF 实作。
flowchart TB
accTitle: JIS X 8341-3 与 WCAG 的关系
accDescr: JIS X 8341-3:2016 是与 WCAG 2.0 内容相同的对应标准,WCAG2ICT 说明如何把 WCAG 成功准则套用到非 Web 软件,因此 Windows 桌面应用可用同一框架检查
wcag["WCAG 2.0(W3C)"] ---|内容相同的对应标准| jis["JIS X 8341-3:2016"]
wcag --> ict["WCAG2ICT"]
ict --> soft["套用到非 Web 软件"]
soft --> app["Windows 桌面应用"]
图 3: JIS X 8341-3:2016 是 WCAG 2.0 的对应标准,WCAG2ICT 把同一准则延伸到桌面应用。
3. 辅助科技怎么读应用 ── UI Automation 三件套
3.1. UIA 树、属性、控制模式
Windows 内建称为 UI Automation(UIA) 的无障碍基础。UIA 让屏幕阅读器这类辅助科技取得 UI 信息、用标准输入以外的手段操作 UI,并在应用侧(提供者)与辅助科技侧(用户端)之间仲介。5
UIA 的世界可理解成下面三件套。5
| 要素 | 角色 | 代表性例子 |
|---|---|---|
| UIA 树 | 以桌面为根、窗口 → 控制项延续的树。辅助科技走这棵树掌握 UI | 窗口、窗格、按钮、编辑框 |
| 属性 | 表示各元素性质的值 | Name(用途)、ControlType(种类)、AutomationId(识别码)、IsEnabled、IsKeyboardFocusable |
| 控制模式 | 依种类的「能做的操作」词汇 | Invoke(按下)、Value(读写值)、SelectionItem(选取)、Toggle(开/关)、ExpandCollapse(展开/收合) |
屏幕阅读器聚焦按钮时,「确认订单按钮」的朗读,大致是 Name + 控制类型 的组合。用户做「执行」操作时,辅助科技透过 Invoke 模式按下该按钮。也就是说,Name 与模式正确公开就能读、能操作;没有公开,即使画面上看得见也等于不存在。
flowchart TB
accTitle: UI Automation 三件套
accDescr: 应用作为提供者,在 UIA 树上公开各元素的属性与控制模式;屏幕阅读器作为用户端,朗读 Name 与 ControlType,并透过 Invoke 等模式操作
app["应用(提供者)"] --> tree["UIA 树"]
tree --> prop["属性(Name、ControlType 等)"]
tree --> pat["模式(Invoke、Value 等)"]
sr["屏幕阅读器(用户端)"] -->|朗读| prop
sr -->|操作| pat
图 4: 屏幕阅读器使用应用在 UIA 树上公开的属性与模式来朗读与操作。
3.2. 屏幕阅读器是 UIA 用户端
Windows 上主要的屏幕阅读器包括 Windows 内建的讲述人、免费开源的 NVDA、11 以及在日本广泛使用的商业产品 PC-Talker。朗读风格各异,但读桌面应用 UI 的主路径每种都是 UIA。因此应用侧的对应不是「支援特定屏幕阅读器」,而是集中在向 UIA 公开正确信息。
flowchart TB
accTitle: 主要屏幕阅读器的共同路径
accDescr: 若应用向 UIA 公开正确信息,讲述人、NVDA、PC-Talker 都能用同一路径读 UI,因此应用侧对应不针对特定屏幕阅读器,而集中在向 UIA 公开
app["应用"] -->|公开信息| uia["UI Automation(UIA)"]
uia --> nar["讲述人"]
uia --> nvda["NVDA"]
uia --> pct["PC-Talker"]
app -.-> goal["对应集中在向 UIA 公开"]
图 5: 主要屏幕阅读器都以 UIA 为路径,因此应用的对应集中在向 UIA 公开。
3.3. 「Name 空白的按钮」会被念成什么?
一个具体例子。假设工具列有一个只显示软碟图示的保存按钮。对视力正常的用户,图示传达意义;若 Name 留空,屏幕阅读器会把这个按钮只念成「按钮」。若旁边的「打开」与「列印」也一样,用户只听到「按钮、按钮、按钮」,无从知道哪个是哪个。Microsoft 的无障碍修正指南也把没有 Name 的按钮、只被念成「Image」的影像,列为让用户工作停住的代表性问题。7
幸好 WinForms 与 WPF 标准控制项一开始就有 UIA 支援,许多情况下 Name 会从文字或标签自动决定。坏掉的通常是(1)只有图示、没有名称材料、(2)没有与标签关联、或(3)自订绘制没有把信息放到 UIA 树。 接下来两章依架构看怎么修。
flowchart TB
accTitle: 朗读坏掉的三种典型方式
accDescr: 朗读会在只有图示没有名称材料、没有与标签关联、或自订绘制没有把信息放到 UIA 树时坏掉,最后只被念成按钮
c1["只有图示、没有材料"] --> broken["Name 变成空白"]
c2["没有与标签关联"] --> broken
c3["自订绘制不输出信息"] --> broken
broken --> result["只被念成按钮"]
图 6: 朗读坏掉通常可归到三种模式:名称材料不足、关联不足、或自订绘制。
4. WinForms 实作 ── AccessibleName 与 Tab 顺序
4.1. Text 会自动变成 Name 的控制项,以及不会的控制项
在 WinForms,Button 或 CheckBox 这类显示文字的控制项,把 Text 属性的值当 UIA Name。另一方面,ComboBox、ListBox、ListView、PictureBox、ProgressBar、TabControl、TextBox、TreeView 等不会把 Text 当 Name。这些需要用别的手段给名称。6
最好维护的做法是在目标控制项的直前 Tab 顺序放一个说明用 Label。若把目标控制项的 TabIndex 设成紧接在 Label 的 TabIndex 之后,该 Label 的文字会自动当 UIA Name。画面上看得见的标签与朗读一致,不必管理两次文案。612
若不能放 Label,就明确设置 AccessibleName。需要补充说明可设 AccessibleDescription,角色与外观不同可设 AccessibleRole。13
flowchart TB
accTitle: WinForms 控制项的名称怎么决定
accDescr: Button 等的 Text 原样成为 UIA Name;TextBox 等不重用 Text 的控制项,使用直前 Tab 顺序的 Label 文字;不能放 Label 时明确设置 AccessibleName
ctrl["控制项"] --> qtext{"Text 会变成 Name 的种类?"}
qtext -->|是| usetext["Text 原样成为 Name"]
qtext -->|否| qlabel{"直前 Tab 顺序有 Label?"}
qlabel -->|是| uselabel["使用 Label 的文字当 Name"]
qlabel -->|否| explicit["明确设置 AccessibleName"]
图 7: WinForms 的 Name,依 Text、直前 Tab 顺序的 Label、AccessibleName 的顺序决定。
// An icon-only toolbar button: state the name for announcement explicitly
saveToolStripButton.AccessibleName = "Save";
// An image-only button: name + a supplementary explanation
btnSearchCustomer.AccessibleName = "Search customer";
btnSearchCustomer.AccessibleDescription = "Search the customer master by customer code or name";
// Set it directly on an input field where you cannot place a Label in the immediately preceding tab order
txtOrderNo.AccessibleName = "Order number";
// A PictureBox reused as a chart display: match the role to the reality as well
pictureBoxChart.AccessibleRole = AccessibleRole.Chart;
pictureBoxChart.AccessibleName = "Monthly order-count chart";
要注意的是,若在 Visual Studio 的属性窗格设过一次 AccessibleName 再清掉,设计工具档可能留下空字串设置,干扰默认名称解析。从设计工具档删掉对应列。6
flowchart TB
accTitle: 空字串 AccessibleName 留下的问题
accDescr: 若在属性窗格设过一次 AccessibleName 再清掉,设计工具档会留下空字串设置并干扰默认名称解析,因此从设计工具档删掉对应列来修
set["设置 AccessibleName"] --> erase["在属性窗格清掉"]
erase --> remain["空字串设置留下"]
remain --> block["干扰默认名称解析"]
block -.-> fix["从设计工具档删掉对应列"]
图 8: 在属性窗格清掉仍会留下空字串,因此从设计工具档删掉对应列来修。
4.2. 接单画面上常见的改善
业务应用里实际常修的地方,整理成检查清单。
| 常见状态 | 问题 | 怎么修 |
|---|---|---|
| 只有图示的 ToolStripButton | 只被念成「按钮」 | 设置 AccessibleName |
| TextBox 附近有 Label 但 Tab 顺序散乱 | 输入栏名称空白,或变成无关名称 | 把输入栏放在 Label 的 TabIndex 正后方 |
| 用 Click 把 PictureBox 当按钮 | 角色没被传达成按钮,也不能从键盘按 | 换成 Button,或设 AccessibleRole/AccessibleName 加上键盘支援 |
| DataGridView 栏标题空白或只有符号 | 念保存格时栏的意义不清楚 | 在 HeaderText 设有意义的栏名 |
| 只用 Panel 分组内容,标题是影像 | 分不出是哪个输入群组 | 用 GroupBox,或把标题做成 Label |
每一项都是几行的修正,但对屏幕阅读器用户,这是「用不了的画面」与「用得了的画面」的分叉。
5. WPF 实作 ── AutomationProperties 与 AutomationPeer
5.1. AutomationProperties.Name / LabeledBy / HelpText
在 WPF,Content 是字串的控制项(如 Button)把该内容当 UIA Name。只有图示的按钮(Image 或 Path)没有 Name 材料,因此用 AutomationProperties.Name 陈述,或附近有显示文字时用 AutomationProperties.LabeledBy 关联。7
TextBox 有重要注意。TextBlock 的 Text 会被重用当 Name,但 TextBox 的 Text 公开在 UIA Value 属性侧,不会变成 Name。对输入栏,用 LabeledBy 关联显示标签 TextBlock 是第一候选。朗读与画面显示一致,也避免管理两次文案。14
<!-- An input field: associate the display label with LabeledBy -->
<TextBlock x:Name="OrderNoLabel" Text="Order number" />
<TextBox
AutomationProperties.LabeledBy="{Binding ElementName=OrderNoLabel}"
AutomationProperties.AutomationId="OrderNoTextBox" />
<!-- An icon-only button: state the name, and a supplement if needed -->
<Button
AutomationProperties.Name="Confirm order"
AutomationProperties.HelpText="Confirm the order being entered and allocate inventory">
<Path Data="{StaticResource CheckIconGeometry}" Width="16" Height="16" />
</Button>
flowchart TB
accTitle: WPF 控制项的名称怎么决定
accDescr: Content 是字串的控制项把该内容当 Name;否则用 LabeledBy 关联附近显示标签是第一候选;若也没有,明确设置 AutomationProperties.Name;TextBox 的 Text 公开在 Value 侧,不是 Name
ctrl["控制项"] --> qc{"Content 是字串?"}
qc -->|是| auto["内容成为 Name"]
qc -->|否| ql{"附近有显示标签?"}
ql -->|是| lb["用 LabeledBy 关联"]
ql -->|否| nm["明确设置 Name"]
tbx["TextBox 的 Text"] -.-> val["公开为 Value,不是 Name"]
图 9: WPF 的 Name 依 Content 字串、LabeledBy、明确设置的顺序决定;TextBox 的 Text 不会变成 Name。
放不进 Name 的补充信息可用 AutomationProperties.HelpText 公开。7 另外,AutomationId 是 UI 自动测试用来识别元素的识别码,因此在画面设计时决定命名惯例,之后会划算(深度写在〈Windows 桌面应用的 UI 自动测试〉)。
5.2. 自订控制项需要 AutomationPeer
自己画的自订控制项,原样无法在 UIA 树上公开有意义的信息。在 WPF,于 UIElement 衍生类覆写 OnCreateAutomationPeer,回传 AutomationPeer 衍生类,以公开名称、种类、模式。若继承既有控制项,继承对应的 Peer(ButtonBase 用 ButtonBaseAutomationPeer)就能接手已实作的行为。15
flowchart TB
accTitle: 经 AutomationPeer 公开信息的方式
accDescr: 自订控制项借由覆写 OnCreateAutomationPeer 并回传 AutomationPeer 衍生类来公开名称、种类、模式;若继承既有控制项,继承对应 Peer 并接手已实作的行为
custom["自订控制项"] --> ov["OnCreateAutomationPeer"]
ov --> peer["回传 Peer 衍生类"]
peer --> pub["公开名称、种类、模式"]
inherit["继承既有控制项"] -.-> basepeer["继承对应 Peer"]
basepeer -.-> reuse["接手已实作的行为"]
图 10: 自订控制项从 OnCreateAutomationPeer 回传 Peer,向 UIA 公开信息。
// An example of a control that custom-draws line status as a coloured lamp
public class StatusLamp : Control
{
public static readonly DependencyProperty IsOnlineProperty =
DependencyProperty.Register(nameof(IsOnline), typeof(bool), typeof(StatusLamp),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender, OnIsOnlineChanged));
public bool IsOnline
{
get => (bool)GetValue(IsOnlineProperty);
set => SetValue(IsOnlineProperty, value);
}
internal static string NameFor(bool isOnline)
=> isOnline ? "Line status: online" : "Line status: offline";
private static void OnIsOnlineChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Raise a UIA property-changed event the moment the value changes. Without this,
// a screen reader keeps the old name and cannot notice the change of state
if (UIElementAutomationPeer.FromElement((UIElement)d) is AutomationPeer peer)
{
peer.RaisePropertyChangedEvent(
AutomationElementIdentifiers.NameProperty,
NameFor((bool)e.OldValue), NameFor((bool)e.NewValue));
}
}
protected override AutomationPeer OnCreateAutomationPeer()
=> new StatusLampAutomationPeer(this);
}
public class StatusLampAutomationPeer : FrameworkElementAutomationPeer
{
public StatusLampAutomationPeer(StatusLamp owner) : base(owner) { }
protected override AutomationControlType GetAutomationControlTypeCore()
=> AutomationControlType.Text; // Text-equivalent if it is a status display with no operation
protected override string GetNameCore()
=> StatusLamp.NameFor(((StatusLamp)Owner).IsOnline);
}
回传名称还不够;值一变就用事件告诉它 也是 Peer 的工作。辅助科技没有自己重新抓值的时机,因此不引发变更事件的实作处于「再问一次才正确」的状态,屏幕阅读器用户不会被告知状态变化。
sequenceDiagram
accTitle: 把状态变化告诉屏幕阅读器的流程
accDescr: 控制项的值一变,AutomationPeer 就引发 Name 属性变更事件;辅助科技不会自己重新抓,因此没有事件就停在旧名称、无法注意到变化
participant c as 控制项
participant p as AutomationPeer
participant s as 屏幕阅读器
c->>p: IsOnline 值改变
p->>s: 引发 Name 属性变更事件
s->>s: 朗读新状态
Note over s: 没有事件就停在旧名称
图 11: 值的变化只有在 AutomationPeer 用变更事件告诉它时,才会到达屏幕阅读器。
若自订控制项有操作(能按、能改值、能选),覆写 GetPattern 并提供 IInvokeProvider 或 IRangeValueProvider 这类模式界面。15 重点是若在共用控制项函数库侧也把 Peer 建好,每个使用它的画面就自动被支援。那是第 9 章「横向展开」的基础。
6. 单靠键盘就能到达每个功能吗?
WCAG 成功准则 2.1.1(Keyboard)要求内容的所有功能都能透过键盘界面操作。8 屏幕阅读器用户原则上不用鼠标,因此键盘到不了的功能等于不存在的功能。检查视角如下。
| 视角 | 要确认什么 | WinForms/WPF 的主要手段 |
|---|---|---|
| Tab 顺序 | Tab 键移动顺序是否对齐视觉顺序(左上 → 右下)? | 整理 TabIndex、设置 TabStop |
| 快速键 | 能否用 Alt+字母直接移到主要项目? | WinForms 在 Text 用 &;WPF 在标头用 _ |
| 捷径 | 频繁操作(保存、搜寻、确认)有没有独立按键? | 指定 Ctrl+S 等,并在功能表显示 |
| 焦点指示 | 能否用眼睛跟上现在焦点在哪? | 不要拿掉焦点矩形;自订绘制时自己画 |
| 只有鼠标的功能 | 有没有只能靠双击、右键、拖曳、悬停使用的功能? | 也从功能表或按键提供同一功能 |
| 对话框 | Enter=默认按钮、Esc=取消是否有效? | AcceptButton/CancelButton、IsDefault/IsCancel |
WinForms 无障碍逐步解说也把在输入栏直前 Tab 顺序放标签、以及在用户想移到的控制项与功能表放快速键,列为基础。12
想强调的是,这不是「为障碍支援多付的成本」。在接单这类例行工作里,手能不能离开原位就完成输入,原样决定操作者的吞吐量。散乱的 Tab 顺序或必须用鼠标的操作,是每天从每位用户生产力削一点的缺陷。无障碍对应与键盘效率,只是同一份工作的两个名字(依使用环境的优先顺序,也见〈Windows 应用中 UX 设计的思考方式〉)。
flowchart TB
accTitle: 把键盘整理好的双重效果
accDescr: 把 Tab 顺序、快速键、焦点指示做好,一次产生两种效果──辅助科技用户能到达功能,以及每位操作者的输入速度──只有鼠标能用的功能等于不存在
seibi["把键盘整理好"] --> a11y["辅助科技用户"]
seibi --> speed["每位操作者的速度"]
mouse["只有鼠标的功能"] -.-> none["等于不存在"]
图 12: 把键盘操作整理好,同时实现辅助科技支援与每位用户的效率;只有鼠标的功能等于不存在。
7. 颜色与对比 ── 4.5:1 与「颜色不是唯一手段」
7.1. 对比的指引是 4.5:1
WCAG 成功准则 1.4.3(Contrast (Minimum))要求文字与文字影像的对比至少 4.5:1,大文字至少 3:1。8 在白底上放浅灰文字的现代设计,不够这个准则并不罕见。业务应用的用户包括视力与色觉随年龄改变的人,以及在工厂这类光线差的环境使用的人。在设计审查养成用对比检查器量的习惯。
7.2. 不要只靠颜色传达信息
成功准则 1.4.1(Use of Color)是颜色不得作为传达信息的唯一视觉手段。8 业务应用的典型例子如下。
- 错误列只显示红字 → 也提供错误图示与消息栏
- 必填栏只靠标签颜色 → 加上「*」或「必填」文案
- 状态只靠灯的颜色 → 做成颜色 + 形状,或文案(「运转中」「已停止」)
考量色觉多样性,这也不是「特殊对应」,而是显示设计的基础。
flowchart TB
accTitle: 替换只靠颜色传达的信息
accDescr: 错误只显示红字改成也提供错误图示与消息栏;必填只靠标签颜色改成加上必填文案;状态只靠灯的颜色改成搭配形状或文案
err["错误只显示红字"] --> erra["也提供图示与文案"]
req["必填只靠标签颜色"] --> reqa["加上必填文案"]
lamp["状态只靠灯的颜色"] --> lampa["颜色搭配形状或文案"]
图 13: 只靠颜色传达的典型例子,改成搭配图示、文案、以及形状或文字。
7.3. 跟随对比主题(高对比)
Windows 有对比主题(旧称高对比),切到前景与背景强烈分离的配色;用户可选并编辑内建主题,对比大致 7:1 以上。9 应用侧的原则很单纯:不要把颜色写死;尊重系统颜色。
- WinForms:若把 ForeColor/BackColor 留在默认,会使用用户的颜色设置。自己套过颜色的地方,用 SystemInformation.HighContrast 判断,切到以 SystemColors 为基础的方案,并用 UserPreferenceChanged 事件跟随设置变更。12
- WPF/WinUI:若参照 SystemColors 类资源,会跟随主题切换。自己用笔刷填满的地方会成为坏掉的原因。9
flowchart TB
accTitle: 跟随对比主题
accDescr: 颜色写死的地方在切到对比主题时会坏,因此切到以 SystemColors 为基础的方案并用设置变更事件跟随;若参照系统颜色就能自动跟随用户的颜色
theme["切到对比主题"] --> qh{"颜色怎么指定?"}
qh -->|写死| broken["配色坏掉"]
qh -->|系统颜色参照| ok["自动跟随用户的颜色"]
broken -.-> fix["切到 SystemColors"]
fix -.-> ev["用设置变更事件跟随"]
图 14: 只有颜色写死的地方会在对比主题下坏掉;系统颜色参照会自动跟随。
另外,低视能用户常用高 OS 放大(DPI 缩放),因此高 DPI 对应也是无障碍对应的一部分。在 125%–200% 版面就坏的应用,到那一点就用不了。细节见〈WinForms 的高DPI支援〉与〈WPF 高 DPI 对应〉。
8. 验证实务 ── Accessibility Insights 与屏幕阅读器实地检查
8.1. Accessibility Insights for Windows
Microsoft 提供 Accessibility Insights for Windows 作为 Windows 应用的无障碍验证工具,主要有三种用途。10
- Live Inspect:只要把鼠标停在元素上或用键盘聚焦,就能确认其 UIA 属性(Name、ControlType、模式等)。看「这个按钮的 Name 是什么」的最短手段。
- FastPass:五分钟内检测高影响无障碍问题的轻量检查。缺 Name 这类能机械判断的问题,可依新画面盘点。
- Troubleshooting:协助诊断与修正特定问题。从检测到的问题可直接走到本文也引用的依架构修正指南。
Windows SDK 内含的 Inspect.exe 与 AccEvent 也能确认 UIA 树与属性,但定位为旧工具,现在建议移到 Accessibility Insights。10
flowchart TB
accTitle: Accessibility Insights 的三种用途
accDescr: Accessibility Insights for Windows 提供用 Live Inspect 确认 UIA 属性、用 FastPass 轻量检查高影响问题、用 Troubleshooting 协助诊断与修正;建议从 Inspect.exe 等旧工具移过来
ai["Accessibility Insights"] --> live["Live Inspect"]
ai --> fast["FastPass"]
ai --> ts["Troubleshooting"]
live --> livef["确认 UIA 属性"]
fast --> fastf["检测高影响问题"]
ts --> tsf["协助诊断与修正"]
legacy["Inspect.exe 等"] -.->|建议移过来| ai
图 15: Accessibility Insights 有确认、检测、诊断三种用途,是从旧工具迁移的目的地。
8.2. 用屏幕阅读器做实地检查
工具自动检查能检测的,只有能机械判断的问题。最后一定要用屏幕阅读器走一遍真实业务操作。 Windows 内建讲述人可用 Ctrl+Windows 键+Enter 立刻启动,NVDA 可免费导入。11 检查的诀窍是不看画面(或关掉显示器),只靠朗读,试能不能完成「输入一笔订单并确认」这类真实工作。名称在但朗读顺序不通、焦点逃出强制回应这类问题,只有实地才找得到。
flowchart TB
accTitle: 组合工具验证与实地检查
accDescr: FastPass 等自动检查能检测的只有能机械判断的问题;其余靠用屏幕阅读器走真实业务操作、找出朗读顺序与焦点问题来实地发现
tool["工具的自动检查"] --> kikai["能机械判断的问题"]
tool -.-> nokori["检测不到的问题留下"]
nokori --> sr["用屏幕阅读器实地检查"]
sr --> task["走一遍业务操作"]
task --> mieru["朗读顺序与焦点问题"]
图 16: 用自动检查盘点机械问题,其余用屏幕阅读器实地检查找出。
8.3. 建进开发流程,以及与 UI 自动测试的互相划算
为避免验证变成看人,建议把下列检查清单建进新画面的审查项目。
| # | 检查项目 | 手段 |
|---|---|---|
| 1 | FastPass 零错误 | Accessibility Insights |
| 2 | 每个输入栏与按钮都有 Name | Live Inspect |
| 3 | 单靠 Tab 键就能到达每个功能 | 手动 |
| 4 | Enter/Esc 与主要捷径有效 | 手动 |
| 5 | 文字对比 4.5:1 以上 | 对比检查器 |
| 6 | 对比主题下不坏 | 切主题目视检查 |
| 7 | 200% 缩放不坏 | 改显示设置目视检查 |
| 8 | 能用屏幕阅读器完成代表性工作 | 讲述人/NVDA |
还有一件。FlaUI 等 UI 自动测试建在屏幕阅读器使用的同一套 UIA 上。 为无障碍放进去的 Name 与模式成为测试代码的零件,为测试设计的 AutomationId 也让 Live Inspect 调试更容易。反过来说,没有出现在 UIA 树的 UI,对测试与辅助科技都看不见。无障碍与可测试性是同一笔投资的两面(〈Windows 桌面应用的 UI 自动测试〉)。
flowchart TB
accTitle: 无障碍与 UI 自动测试的互相划算
accDescr: 屏幕阅读器与 FlaUI 等 UI 自动测试建在同一套 UIA 上,因此放进去的 Name 与模式两边都能用;没有出现在 UIA 树的 UI 从哪边都看不见
uia["把 UIA 树整理好"] --> sr["屏幕阅读器能读"]
uia --> test["能用在 UI 自动测试"]
sr -.-> both["同一笔投资的两面"]
test -.-> both
hidden["没有出现在 UIA 的 UI"] -.-> invisible["从哪边都看不见"]
图 17: 因为坐在同一套 UIA 基础上,把 UIA 树整理好对辅助科技与 UI 自动测试都划算。
9. 如何排优先顺序 ── 不要一次修所有画面
一次修核心系统的数百个画面,成本与品质都不现实。我们建议的做法是下面三层。
- 从那位用户在工作中使用的画面开始修。合理便利是对当事人要求个别回应的过程。1 先让当事人用屏幕阅读器操作实际工作,一起找出卡在哪。许多情况下日常使用的画面会窄到几个到十几个,其中致命问题(没有名称的按钮、键盘按不到的确认按钮)可用数天的修正解决。
- 让新开发符合标准。把第 8 章的检查清单加进 Definition of Done,新画面从一开始就支援。不像事后修正,设计时建进去的成本增加很小。
- 靠修共用控制项横向展开。若在搜寻对话框、格线、日期输入这类公司内部共用零件上实作默认 AccessibleName 或 AutomationPeer,会对每个使用它们的画面一次生效。远比逐个画面碰更划算。
flowchart TB
accTitle: 修正优先顺序的三层
accDescr: 从用户在工作中使用的画面开始修,用检查清单让新开发符合标准,并靠修共用控制项展开到每个画面
s1["1. 从用户在用的画面修"] --> s2["2. 新工作符合标准"] --> s3["3. 用共用控制项横向展开"]
s3 -.-> all["对每个使用它们的画面一次生效"]
图 18: 不要一次修所有画面,而是依使用中画面、新工作、共用零件三层前进。
而且对话纪录与技术对应一样重要。合理便利是「个别对话与调整」的过程,不是全面满足每个要求。与当事人一起考虑替代手段(在另一个画面做那份工作、准备 CSV 导出、用作业覆盖),并对负担过重的修正达成协议,也是建设性对话的正当结果。1 记录要求了什么、对应了什么、什么做成替代手段,成为组织善意的证明。
flowchart TB
accTitle: 建设性对话与纪录的流程
accDescr: 透过建设性对话回应残障者的要求;能对应的修正就做;负担过重的修正与当事人一起考虑替代手段并达成协议;记录要求了什么、对应了什么、什么做成替代手段
req["要求"] --> talk["建设性对话"]
talk --> q{"负担过重?"}
q -->|否| kaishu["用修正回应"]
q -->|是| alt["考虑替代手段并达成协议"]
kaishu --> rec["记录经过"]
alt --> rec
图 19: 在建设性对话中与当事人就修正或替代手段达成协议,并把那段经过留在纪录里。
10. 总结
- 随着 2024 年 4 月施行的残障者歧视消除法修正,提供合理便利也成为事业的义务。雇用领域自 2016 年起依残障者就业促进法是雇主义务。事先让应用更好用是「环境改善」(努力义务),走得愈远,个别对应愈轻。
- 技术准则集中在 WCAG(JIS X 8341-3:2016),同一思路可经 WCAG2ICT 套到桌面应用。
- 屏幕阅读器透过 UI Automation 读取应用。UIA 树、属性(Name/ControlType/AutomationId)、控制模式三件套是基础。
- 最高优先是 Name。WinForms 用 AccessibleName 与把 Label 关联到 Tab 顺序;WPF 用 AutomationProperties.Name/LabeledBy;自订控制项用 AutomationPeer。
- 单靠键盘就能到达每个功能是 WCAG 成功准则,同时也是每位操作者的生产力。把 Tab 顺序、快速键、焦点指示整理好。
- 颜色的三个基础是对比 4.5:1、颜色不是唯一手段、在对比主题尊重系统颜色。
- 验证把 Accessibility Insights 的 FastPass+Live Inspect 与讲述人/NVDA 实地检查组合起来,并以新画面检查清单建进开发流程。
- 不要一次修所有画面;依用户在用的画面 → 新工作的标准支援 → 共用控制项横向展开的顺序前进。合理便利是对话的过程,经过纪录保护组织。
第一步我们建议挑一个主要画面,跑 Accessibility Insights for Windows 的 FastPass,然后单靠 Tab 键走一遍工作。三十分钟内,自己应用的现况会具体得令人意外。
相关文章
- Windows 桌面应用的 UI 自动化测试 ── UI Automation 原理与用 FlaUI 打造不易损坏的测试
- Windows 应用 UX 设计 - 按使用场景划分的优先级
- WinForms 的高DPI支持 - 4K显示器下模糊、错位的原因与实用对策
- WPF 高 DPI 支持──「不是应该对 DPI 很强吗」却仍模糊、渗色的原因与对策
- 小村软件为什么用日本数字厅设计系统做网站 ── 便宜与品质可以兼顾
- 日文字体与字符的陷阱 ── 业务应用中的 JIS2004、IVS、外字
相关咨询领域
小村软件有限公司承接 WinForms/WPF 业务应用的无障碍修正(屏幕阅读器支援、整理键盘操作、对比主题对应)、在共用控制项实作 AutomationPeer,以及用 Accessibility Insights 做现况诊断与排优先顺序的咨询。从「想确认员工能不能用屏幕阅读器使用我们的应用」的阶段开始即可。
参考链接
-
Cabinet Office, Leaflet “From 1 April 2024, the provision of reasonable accommodation became an obligation”. 关于令和 3 年残障者歧视消除法修正于令和 6 年 4 月 1 日施行、事业提供合理便利成为义务;关于提供合理便利是对残障者表明意图、在不过度负担范围内的对应;关于建设性对话的重要性以及单方面拒绝可能构成违反义务;关于「环境改善」(针对不特定多数残障者的事先改善措施)是努力义务;以及雇用与工作依残障者就业促进法的规定。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
Ministry of Health, Labour and Welfare, The prohibition of discrimination against persons with disabilities in employment and the obligation to provide reasonable accommodation. 关于平成 28 年 4 月施行的残障者就业促进法修正课予雇主禁止雇用上的身心障碍歧视、以及在不过度负担范围内提供合理便利;以及合理便利指针等相关数据。 ↩ ↩2 ↩3 ↩4
-
Web Accessibility Infrastructure Committee (WAIC), Understanding JIS X 8341-3:2016. 关于 JIS X 8341-3:2016 是 ISO/IEC 40500:2012 的对应标准,标准本文与 WCAG 2.0 内容相同;以及标准假设的网页内容范围。 ↩ ↩2
-
W3C, Guidance on Applying WCAG 2 to Non-Web Information and Communications Technologies (WCAG2ICT). 关于说明如何把 WCAG 2.0/2.1/2.2 的原则、指南、成功准则套用到非 Web 文件与软件的 W3C Group Note。 ↩ ↩2
-
Microsoft Learn, UI Automation Specification. 关于 UI Automation 向屏幕阅读器等辅助科技提供 UI 信息,并让标准输入以外的手段能操作;以及 UIA 元素、树、属性、控制模式、控制类型、事件的组成。 ↩ ↩2 ↩3
-
Microsoft Learn, WinForms: Setting the accessible name on a control. 关于部分控制项把 Text 重用为 UIA Name,而 ComboBox、ListBox、ListView、PictureBox、ProgressBar、TabControl、TextBox、TreeView 等不重用;关于把目标控制项放在 Label 的 TabIndex 正后方,让 Label 文字当 Name;以及明确设置 AccessibleName,与设计工具档留下空字串的问题。 ↩ ↩2 ↩3 ↩4
-
W3C / Web Accessibility Infrastructure Committee (WAIC) translation, Web Content Accessibility Guidelines (WCAG) 2.1 Japanese translation. 关于成功准则 1.4.3(Contrast (Minimum))文字 4.5:1、大文字 3:1;关于成功准则 1.4.1(Use of Color)不让颜色成为唯一视觉手段;以及成功准则 2.1.1(Keyboard)所有功能的键盘可操作性。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Contrast themes. 关于对比主题使用对比大致 7:1 以上的受限调色盘;关于选内建主题并编辑颜色;以及 SystemColor 类资源定义成前景/背景配对、自动跟随主题切换。 ↩ ↩2 ↩3
-
Microsoft Learn, Accessibility testing. 关于 Accessibility Insights for Windows 的三种情境──Live Inspect(用悬停/聚焦确认 UIA 属性)、FastPass(五分钟内检测高影响问题)、Troubleshooting──以及建议从 Inspect、AccEvent 等旧工具移过来。 ↩ ↩2 ↩3
-
NVDA Japanese Team, NVDA Japanese edition. 关于免费开源的 Windows 屏幕阅读器 NVDA 及其日文版的提供。 ↩ ↩2
-
Microsoft Learn, Walkthrough: Creating an Accessible Windows-based Application. 关于在输入栏直前 Tab 顺序放说明用 Label;关于 Text 里用 & 的快速键;关于用 SystemInformation.HighContrast 判断高对比并使用 SystemColors;关于跟随 UserPreferenceChanged 事件;以及把视觉提示与用颜色传达的信息组合起来。 ↩ ↩2 ↩3
-
Microsoft Learn, Providing Accessibility Information for Controls. 关于 WinForms 控制项的 AccessibleName、AccessibleDescription、AccessibleRole、AccessibleDefaultActionDescription 属性以及如何设置。 ↩
-
Microsoft Learn, WPF: Setting the accessible name on an edit field. 关于 TextBlock 的 Text 被重用为 UIA Name,而 TextBox 的 Text 公开为 UIA Value;以及用 AutomationProperties.LabeledBy 把标签 TextBlock 关联到 TextBox,或设置 AutomationProperties.Name。 ↩
-
Microsoft Learn, UI Automation of a WPF Custom Control. 关于自订控制项覆写 OnCreateAutomationPeer 并回传 AutomationPeer 衍生类;关于继承对应基底控制项的 Peer 类;关于经 GetPattern 提供模式提供者;以及从 XAML 侧用 AutomationProperties 属性覆写。 ↩ ↩2
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Windows 桌面应用的 UI 自动化测试 ── UI Automation 原理与用 FlaUI 打造不易损坏的测试
本文从 Windows UI Automation 的原理(树结构、AutomationId、控件模式)出发,梳理 WinForms/WPF 应用的 UI 自动化测试。内容涵盖用 FlaUI 实现的最小示例、WinAppDriver 的现状、通过 AutomationId ...
委托开发 Windows 应用程序前该梳理的事项
在委托外包开发 Windows 应用程序之前,梳理现有软件改造、设备联动、COM/ActiveX、发布与更新、维护等需要注意的要点。
「无响应」的真正含义 ── Windows 如何判定应用已挂起,以及如何设计不挂起的应用
Windows 的「无响应」是操作系统判定窗口已 5 秒未取出消息并换成幽灵窗口的机制。本文说明该判定的内部、挂起的经典原因、把重活移出 UI 线程的设计,以及调查挂起的步骤。
剪贴板与拖放如何工作 ── 在业务应用中正确处理 OLE 数据传输
粘贴 Excel 表格时格式散架;关闭源应用后就再也贴不上——两者都来自剪贴板把同一内容同时放成多种格式。本文说明标准格式、延迟渲染、OLE 拖放,以及管辖剪贴板历史和云同步的策略。
在 WinForms/WPF 应用中集成 Entra ID 认证 —— MSAL.NET 与 WAM Broker 的实务架构
本文以实务视角整理在 WinForms/WPF 桌面应用中集成 Entra ID(原 Azure AD)认证的步骤:公共客户端的思路、ROPC 被弃用的现状、应用注册、MSAL.NET 的 AcquireTokenSilent 模式、WAM Broker、令牌缓存的持久化,...
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
UI 线程 & 计时器
整理 WPF / WinForms UI 线程、异步流程、Dispatcher 使用、计时器判断的主题页面。
常见问题
汇总了咨询这一主题时常见的问题。
- 业务应用的无障碍对应是法律义务吗?
- 2021 年残障者歧视消除法修正于 2024 年 4 月 1 日施行,对残障者提供合理便利也成为事业的义务。合理便利是残障者提出要求时,在不过度负担的范围内除去个别障碍的对应;事先让应用更好用,定位为称为「环境改善」的努力义务。雇用(员工与公司的关系)不在该法而在残障者就业促进法,该法自 2016 年 4 月施行的修正起就课予雇主合理便利义务。也就是说「员工用不了业务应用」早就是义务领域。个案要做到哪里依个别情况而定,因此要确认内阁府与厚生劳动省的一手数据,并与当事人对话后决定。
- 屏幕阅读器怎么读 Windows 桌面应用?
- 讲述人与 NVDA 这类屏幕阅读器,透过称为 UI Automation(UIA)的无障碍基础读取应用的 UI。应用侧把画面上的元素以 UIA 树这种结构公开;每个元素有 Name(用途)、ControlType(种类)等属性,以及 Invoke(按下)、Value(值)等控制模式。屏幕阅读器把这些信息念成「确认订单按钮」,并透过模式操作。标准 WinForms 与 WPF 控制项一开始就有这套机制,因此开发者的主要工作是不要让 Name 空白、让 UI 能用键盘操作,以及为自订控制项实作信息。
- 既有 WinForms 应用该从哪里开始?
- 最短路径是对目标画面跑 Accessibility Insights for Windows 的 FastPass,盘点 Name 空白的控制项与 Tab 顺序问题。修正从为只有图示的按钮设置 AccessibleName、把 Label 关联到输入栏的直前 Tab 顺序、整理 TabIndex 让它对齐视觉顺序开始。然后启动讲述人或 NVDA,不看画面走一遍真实业务操作,确认卡在哪。不必一次修所有画面;从有人实际在用的画面开始,新画面用检查清单做到符合标准,才是现实做法。
- 高对比(对比主题)该怎么对应?
- 基准是不要把颜色写死,并尊重系统颜色。WinForms 把 ForeColor/BackColor 留在默认或用 SystemColors,用 SystemInformation.HighContrast 判断状态,并用 UserPreferenceChanged 事件跟随切换。WPF 与 WinUI 也一样,若参照 SystemColors 类资源,会自动跟随主题切换。同时停止「只靠颜色」传达信息──错误只显示红色──并搭配图示或文字。即使在一般主题,把 WCAG 的文字对比 4.5:1 以上当指引,对光线差的现场与年长用户也更好读。
- 无障碍对应也有助于 UI 自动测试吗?
- 有。FlaUI 这类 UI 自动测试工具建在屏幕阅读器使用的同一套 UI Automation 上。为无障碍放进去的 Name、ControlType、控制模式,测试代码可以原样使用;为测试设计的 AutomationId 也让元素识别更稳。反过来说,没有出现在 UIA 树的自订绘制 UI,对屏幕阅读器与测试都看不见。无障碍与自动测试是同一套基础的投资,因此做其中一边也会降低另一边的成本。