乐趣区

关于microsoft:如何让-WPF-程序更好地适配-UI-自动化

Windows 中很早就内置了 UI 自动化机制(UIAutomation 从 Windows XP SP3 就开始提供了),WPF 第一个版本开始也提供了 UI 自动化的反对。所以按道理说如果你应用了 WPF,那么你的 UI 做筹备好了随时可被自动化的筹备。

微软 MVP 实验室研究员

虽说 WPF 反对不错,但我还是有几点须要阐明一下:

  1. 这里我说的是“UI 自动化”,而不是“UI 自动化测试”;前者比后者范畴更宽泛,因为前者除了能用来做 UI 自动化测试之外,还能同时利用于读屏软件,为残障人士提供方便。
  2. WPF 从机制层面提供了 UI 自动化的反对,但架不住很多不理解相干机制的人意外改坏,所以本文还是很有必要说一说的。
    接下来,我会从上面几个方面来说,只谈及应用层面,不深刻到原理层面。

WPF 自带的 UI 自动化

为了不便演示,我应用 Visual Studio 自带的模板创立一个默认的 WPF 应用程序,我会一直批改这个程序,而后用我本人写的 UI 自动化测试软件来验证它的自动化适配成果。

▍哪些控件自带残缺的 UI 自动化

不过从理论测试状况来看,微软自家都曾经不必这两种非凡控件了,而是应用后面那些罕用控件的组合来代替这两个非凡的控件。

Windows 上

UIAutomation 控件名 对应的 WPF 控件名 翻译
button Button 按钮
calendar Calendar 日历
checkbox CheckBox 查看框
combobox ComboBox 组合框
custom UserControl 自定义控件
datagrid DataGrid 数据表
dataitem DataItem 数据表项
document 文档
edit TextBox 文本框
group 组合
header 题目
headeritem 题目项
hyperlink 超链接
image Image 图像
list ListBox 列表
listitem ListBoxItem 列表项
menu Menu 菜单
menuitem MenuItem 菜单项
menubar 菜单栏
pane 容器
progressbar ProgressBar 进度条
radiobutton RadioButton 单选框
scrollbar ScrollBar 滚动调
separator Separator 分隔符
slider Slider 滑块
spinner 旋转器
splitbutton 拆分按钮
statusbar StatusBar 状态栏
tab TabControl 选项卡
tabitem TabItem 选项卡项
table 表格
text TextBlock 文本
thumb Thumb
titlebar 标题栏
toolbar ToolBar 工具栏
tooltip ToolTip 工具提醒
tree TreeView 树视图
treeitem TreeViewItem 树视图项
window Window 窗口

额定的,在新的 Windows 零碎(或者 UWP/WinUI 程序里)还存在另外两种反对 UI 自动化的全新控件类型:

UIAutomation 控件名 对应的 WPF 控件名 翻译
semanticzoom SemanticZoom
appbar AppBar

不过从理论测试状况来看,微软自家都曾经不必这两种非凡控件了,而是应用后面那些罕用控件的组合来代替这两个非凡的控件。

▍WPF 自带控件的反对状况

为了直观地看到 WPF 每个自带控件对 UI 自动化的反对状况,我给刚刚创立的 WPF 程序增加了各种常见控件,而后用本人写的 UI 自动化测试软件捕捉一下这个窗口。

能够发现,WPF 自带控件给 UI 自动化正确裸露了各种须要的控件。至多,给盲人用的读屏软件能精确读出所有控件的文字描述。

上面是这个直观的捕捉视频

具体来说,WPF 默认状况下有这些特点:

  1. 所有可交互的控件,其整体可被捕捉,而且各个可被交互的局部也能够别离被捕捉(例如日历和外部按钮,树和外部的项,滚动条和外部按钮等)。
  2. 控件中变动的文字局部,也正确裸露给了 UI 自动化(例如按钮内的文本,列表项文本,菜单项等)。
  3. 容器与布局类的控件并没有裸露给 UI 自动化(例如 Grid、StackPanel、Border 等,并没有呈现在自动化测试中)。
  4. 用户控件(UserControl)裸露给了 UI 自动化。

▍默认状况下 WPF 属性与 UI 自动化属性的对应关系

兴许有人晓得,WPF 有自动化相干的一套 API 用来适配 UI 自动化的。是一套附加属性,UIAutomationProperties.Xxx 这样的。比方:

  • AutomationProperties.AutomationId
  • AutomationProperties.Name
  • 还有更多……

但咱们在编写控件的时候,其实并不需要被动、间接地去设置这些属性。尽管没有为这些附加属性设置值,但在裸露相干属性给 UI 自动化时,曾经裸露了其余有用的属性。
比方:

  • 如果你设置了控件的名称 x:Name=”WalterlvDemoButton”,那么 UI 自动化在捕捉到此控件后,其自动化 Id 就是 WalterlvDemoButton 了。
  • 如果你设置了控件的内容(例如按钮 / 复选框 / 单选框 / 列表项的 Content,例如菜单项 / 选项卡的 Header),那么 UI 自动化在捕捉到此控件后,其自动化 Name 就是对应指定的这些属性。
  • 而且即便你没有任何设置,自动化 Class 名称就是控件的类名,IsEnabled 就对应了控件本身的 IsEnabled,IsVisible 也对应了控件本身的 IsVisible。

在有了以上那么多特点作为保底的状况下,好好善用这些自带控件,做控件布局以及调整款式的时候正确依照控件原有的属性含意来做,是不须要专门针对 UI 自动化做任何适配的。然而,理论状况却并不是这样……

哪些状况会毁坏 WPF 的 UI 自动化

很多时候,咱们在写代码时,可能太过于关注最终做成了什么样子,而疏忽了控件本来的层次结构和属性含意,这就可能导致咱们的程序裸露给 UI 自动化测试的控件和层次结构非常诡异,甚至不可读。

上面,我列举几个例子:

  1. 原本给按钮(Button)设置文本属性用的是 Content 属性,但某天想做很特地的款式,独自在模板(Template)外面写死了文本,而没有间接设置按钮的 Content 属性。这样 UI 自动化软件抓取此按钮的时候,就不晓得这个按钮到底是做什么性能的按钮了,会抓到一个没有文本形容的按钮。
  2. 列表或树绑定了一个源(ItemsSource),而这个源汇合中的每一个项都是 ViewModel 中的一项(例如 Walterlv.Demo.DemoItem 类型),这个类型没有重写 ToString 办法,于是列表项裸露给 UI 自动化的名称将是反复的毫无意义的字符串(例如都是 Walterlv.Demo.DemoItem)。
  3. 有些按钮或列表项没有任何文字描述,它们是齐全由图像形成的控件。如果这个按钮还没有指定名称的话,那就跟任何其余同类按钮没有区分度了;而列表类控件在这种状况下根本无奈裸露任何有用的信息。
  4. 有些控件明明是想做成可交互的,却偏偏用 Grid、Border 这种布局或装璜控件来做款式,最初用 MouseDown 这样的通用事件来做交互。这基本上等同于放弃了自带控件的所有 UI 自动化的反对。
  5. 本人做非常复杂的可交互控件(例如本人做一个画布),它继承自十分底层的 FrameworkElement。尽管这个控件指定了控件款式和模板,但它曾经没有对 UI 自动化裸露任何有用的信息了。

前面的 4 和 5 两种,UI 自动化甚至都无奈捕捉到这样的控件。毕竟 WPF 默认也不太好将全副控件裸露给 UI 自动化,否则对 UI 自动化测试软件或读屏软件来说,将面临着如 WPF 可视化树般简单和宏大的 UI 自动化树。

WPF 适配 UI 自动化的最佳实际

在理解到 WPF UI 自动化的已有特点后,咱们将以上的坑点一个个击破,就是咱们举荐的最佳实际。

  1. 尽量保留 WPF 自带的 UI 自动化机制,防止对款式和模板做过于简单的定制,如果要做,则尽可能应用现成罕用的属性,而不是本人定义新属性(例如用好 Content 而不是定义一个新的 TitleText 之类的)。
  2. 如果某个 ViewModel 汇合会被绑定到 UI 列表或树中,这个 ViewModel 应该重写 ToString() 办法,返回对用户可读的有用的信息(不要像控制台输入一样一股脑把所有属性打印进去)。
  3. 如果某个按钮或图像没有任何文本形容,请为其设置 x:Name 属性以减少一个惟一的 Id;更好地,能够设置 AutomationProperties.Name 附加属性指定一个敌对的名称供视觉阻碍人士浏览。
  4. 如果没有文字描述的按钮或图像在列表中,请为其设置 AutomationProperties.Id 属性绑定一个能辨别彼此的信息作为惟一 Id,而后设置 AutomationProperties.Name 附加属性指定一个敌对的名称供视觉阻碍人士浏览。
  5. 尽量应用通用控件来做控件对应的交互(例如像一个按钮那就用按钮,像一个组合框那就用组合框),而不是应用 Grid、Border 等用来布局或装璜的控件来随便解决。
  6. 如果肯定要做特地的控件交互(没有任何现有控件能够代表这个交互方式),那么充分利用用户控件(UserControl)会主动裸露给 UI 自动化的特点,做一个用户控件。相同地,如果你用用户控件仅仅只是为了拆分代码,就应该为此控件重写 OnCreateAutomationPeer 办法,返回 null 防止这个控件呈现在 UI 自动化层级当中。
  7. 如果还心愿特地交互的控件被复用(不适宜用 UserControl),那么你须要为这个控件重写 OnCreateAutomationPeer 办法,返回一个适合的 AutomationPeer 的实例。
// 对于上述第 6 点,应该为用户控件重写此办法。protected override AutomationPeer? OnCreateAutomationPeer()
{return null;}
public class WalterlvDemoControl : FrameworkElement
{    
// 对于上述第 7 点,应该为用户控件重写此办法。protected override AutomationPeer? OnCreateAutomationPeer()    
{return new WalterlvDemoAutomationPeer(this);    
}
}
// 自定义的 AutomationPeer。只须要继承自 FrameworkElementAutomationPeer 就可主动领有大量现成自动化属性的反对。public class WalterlvDemoAutomationPeer : FrameworkElementAutomationPeer
{public WalterlvDemoAutomationPeer(WalterlvDemoControl demo) : base(demo)    
{ }    
// 在 AutomationControlType 里找一个最能反馈你所写控件交互类型的类型,// 精确返回类型能够让 UI 自动化软件针对性地做一些自动化操作(例如按钮的点击),// 如果找不到相似的就阐明是全新品种的控件,应返回 Custom。protected override AutomationControlType GetAutomationControlTypeCore()    
{return AutomationControlType.Custom;}    
// 针对下面返回的类型,这里给一个本地化的控件类型名。protected override string GetLocalizedControlTypeCore()    
{return "吕毅的示例控件";}    
// 这里的文字就相似于按钮的 Content 属性一样,是给用户“看”的,可被读屏软件读出。// 你能够思考返回你某个自定义属性的值或某些自定义属性组合的值,而这个值最能向用户反映此控件以后的状态。protected override string GetNameCore()    
{return "吕毅在 https://blog.walterlv.com 中展现的博客文本。";}
}

给一个简直都是图像组成的 ListBox 的 UI 自动化适配例子。在上面动图中,如果齐全没有适配,那么捕捉的时候只会失去齐全没有区分度的 ViewModel 的名称,也是就 ToString 默认生成的类名 Walterlv.Demo.ThemeItem。

参考资料

UI Automation – Win32 apps – Microsoft Docs:https://docs.microsoft.com/zh…
UI Automation Overview – .NET Framework – Microsoft Docs:https://docs.microsoft.com/zh…

微软最有价值专家(MVP)

微软最有价值专家是微软公司授予第三方技术专业人士的一个寰球奖项。29 年来,世界各地的技术社区领导者,因其在线上和线下的技术社区中分享专业知识和教训而取得此奖项。
MVP 是通过严格筛选的专家团队,他们代表着技术最精湛且最具智慧的人,是对社区投入极大的激情并乐于助人的专家。MVP 致力于通过演讲、论坛问答、创立网站、撰写博客、分享视频、开源我的项目、组织会议等形式来帮忙别人,并最大水平地帮忙微软技术社区用户应用 Microsoft 技术。
更多详情请登录官方网站:
https://mvp.microsoft.com/zh-cn

长按辨认二维码关注微软中国 MSDN                         

点击理解 UI Automation~

退出移动版