UIAutomation是.Net 3.5之后提供的“界面自动化测试”技术,本来是给测试人员用的,不过UIAutomation由于也是界面自动操作的技术,比直接使用keybd_event、GetWindowText等Win32的API进行界面模拟操作简单很多,因此也可以用UIAutomation做软件的“外挂”。
我手头正好有这样一个需求,如鹏网有一个内部使用的一个工具(购买的第三方软件),用于根据学生的机器码计算“播放密码”,这个工具只提供了图形化的界面:
输入机器码之后点击【创建播放密码】按钮就能生成播放密码。
如鹏网第二期学习辅助系统的开发中需要开发“自动生成播放密码”的功能,也就是学生在浏览器中输入他的机器码,网站自动计算他的播放密码。
由于这个工具只提供了图形化的界面,没有提供API,所以我就想到使用模拟点击的方法来进行“自动化”,直接使用Win32太麻烦,AutoIt使用还要注册组件,因此就想到了UIAutomation。
完成的效果如下:
下面分享一下主要技术。
学习UIAutomation之前一定要知道,Windows中的程序界面元素都是由“窗口组成的”(DirectUI等除外),按钮、文本框等都是窗口,窗口之间也有父子关系。Windows桌面是所有窗口的根窗口。
UIAutomation支持普通Win32程序(不是VC++、.Net开发的也支持,因为本质上都是Win32程序)和WPF程序,但是不支持普通的DirectUI窗口(比如QQ、浏览器)。
使用UIAutomation之前先要添加对UIAutomationClient、 UIAutomationProvider、 UIAutomationTypes三个程序集的引用。所有的界面元素都是由AutomationElement组成,每个窗口就是一个AutomationElement,因此AutomationElement之前也有父子结构。
可以使用AutomationElement.RootElement获得桌面的根元素;使用AutomationElement.FromHandle(IntPtr hwnd)从Win32窗口句柄拿到AutomationElement对象。
拿到一个AutomationElement通常要遍历他的子元素。遍历子元素之前需要先了解“遍历条件”的概念,遍历条件就是按照什么样的条件去搜索子元素。所有的条件都继承自Condition类,Condition类的主要子类有PropertyCondition、AndCondition 、NotCondition 、OrCondition,这些之类之间可以进行复合的组合,形成各种复杂的遍历条件。
PropertyCondition是根据属性的名字和值进行过滤的。它构造函数的第一个参数为属性的名字,所有支持的属性都在AutomationElement的***Property这些静态成员中;构造函数的第二个参数为被比较的值。又可以使用AndCondition、NotCondition、OrCondition把各个条件进行复杂的逻辑组合。比如下面的conditionBtn9就是“类名为Button并且名字为9”的条件:
Condition conditionBtn9 = new AndCondition( new PropertyCondition(AutomationElement.ClassNameProperty, "Button"), new PropertyCondition(AutomationElement.NameProperty, "9") );
Condition类有两个固定的值,Condition. TrueCondition代表永远为True的条件,Condition. FalseCondition代表永远为False的条件(应该很少用)
我们可以使用AutomationElement的FindAll或者FindFirst方法进行元素的遍历。FindAll是获取所有符合遍历条件的AutomationElement,因此是返回AutomationElementCollection集合,而FindFirst是返回第一个符合遍历条件的AutomationElement,因此是返回AutomationElement。
FindFirst、FindAll的第一个参数代表搜索的范围,最常用的就是TreeScope.Children和TreeScope.Descendants,TreeScope.Children代表在直接子节点中搜索,而TreeScope.Descendants代表递归的在所有子孙节点中搜索。FindFirst、FindAll的第二个参数代表搜索条件。
定位到要操作的AutomationElement之后,可以进行模拟点击(比如按钮)或者读写值(比如输入框)。比如下面的代码中element指向的是一个按钮,下面的代码就是模拟点击这个按钮:
var clickPattern = (InvokePattern)element.GetCurrentPattern(InvokePattern.Pattern); clickPattern.Invoke();
比如下面的代码中element指向的是一个文本框,下面的代码就是使用字符串填充这个输入框:
ValuePattern valuePattern = (ValuePattern)element.GetCurrentPattern(ValuePattern.Pattern); valuePattern.SetValue(“如鹏网”);
下面是我实现的一个模拟点击计算器计算两个数的乘法的数:
AutomationElement desktop = AutomationElement.RootElement; var calcFrame1 = desktop.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, "CalcFrame")); ClickCalcButton(calcFrame1, "3"); ClickCalcButton(calcFrame1, "6"); ClickCalcButton(calcFrame1, "5"); ClickCalcButton(calcFrame1, "*"); ClickCalcButton(calcFrame1, "1"); ClickCalcButton(calcFrame1, "2"); ClickCalcButton(calcFrame1, "=");
其中ClickCalcButton是我封装的一个方法:
private static void InvokeButton(AutomationElement e) { InvokePattern invoke = (InvokePattern)e.GetCurrentPattern(InvokePattern.Pattern); invoke.Invoke(); } private static void ClickCalcButton(AutomationElement calcFrame1, string name) { Condition conditionBtnPlus = new AndCondition( new PropertyCondition(AutomationElement.ClassNameProperty, "Button"), new PropertyCondition(AutomationElement.NameProperty, name) ); var btn = calcFrame1.FindFirst(TreeScope.Descendants, conditionBtnPlus); if (btn == null) { throw new Exception("找不到名字为"+name+"的计算器按钮"); } InvokeButton(btn); }
文章篇幅有限,特别是对于一些没有Win32基础的朋友,光看上面的文字会不太容易懂,因此我录制了一套大约90分的视频教程,从入门到实用的讲解了UIAutomation的使用,感兴趣的朋友可以看。
视频教程地址如下 http://www.rupeng.com/Courses/Chapter/298