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”的条件:
1
2
3
4
|
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指向的是一个按钮,下面的代码就是模拟点击这个按钮:
1
2
|
var
clickPattern = (InvokePattern)element.GetCurrentPattern(InvokePattern.Pattern);
clickPattern.Invoke();
|
比如下面的代码中element指向的是一个文本框,下面的代码就是使用字符串填充这个输入框:
1
2
|
ValuePattern valuePattern = (ValuePattern)element.GetCurrentPattern(ValuePattern.Pattern);
valuePattern.SetValue(“如鹏网”);
|
下面是我实现的一个模拟点击计算器计算两个数的乘法的数:
1
2
3
4
5
6
7
8
9
10
11
|
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是我封装的一个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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