> 技术文档 > UIAutomation

UIAutomation


前言

UIAutomation(UI Automation) 是微软提供的一套 自动化测试框架,用于以编程方式访问和操作用户界面(UI)元素(如按钮、文本框、菜单等)。它主要服务于辅助功能(如屏幕阅读器)和自动化测试(如UI测试工具),支持 Win32、WPF、WinForms、UWP 等多种技术构建的应用程序。

核心功能

跨技术支持:统一操作 Win32、WPF、UWP、甚至浏览器(如 Chrome/Firefox 通过插件)。

树形结构访问:通过逻辑树(ControlView)和原始树(RawView)遍历UI元素。

模式(Patterns):提供标准化的交互方式(如点击、输入、滚动),例如:

- InvokePattern(按钮点击)

- ValuePattern(文本框输入)

- TogglePattern(复选框切换)。

属性访问:获取UI元素的名称、类型、状态等(如 Name、ClassName、IsEnabled)。

事件监听:监听UI变化(如窗口弹出、元素属性改变)。

关键信息

assemblies中勾選UIAutomationClientUIAutomationTypes

勾选后,需要引入命名空间:using System.Windows.Automation;

核心类:

AutomationElement:代表UI元素(如窗口、按钮等)。

AutomationElementCollection:

TreeScope:

Condition:用于查找元素(如按名称、控件类型)。

Patterns:定义交互行为(如 InvokePattern 触发按钮点击)。

AutomationElement类

作用:

查找元素:通过 FindFirst() / FindAll() 按条件(如 Name、ClassName)定位控件。

读取属性:获取控件的 Name、AutomationId、BoundingRectangle 等属性。

操作控件:使用 InvokePattern(点击)、ValuePattern(输入文本)自动化交互。

等待元素:配合 AutomationWatcher 监控 UI 变化(如动态加载的元素)。

常用属性

Current.Name:控件的显示名称(如按钮文本)。

Current.AutomationId:唯一标识符(类似 x:Name 或 win32控件的 ID)。

Current.ClassName:控件类名(如 \"Button\"、\"Edit\")。

Current.BoundingRectangle:控件的屏幕坐标(Rect 结构)。

Current.ControlType        :控件类型(如 ControlType.Button)。

常用方法

FindFirst():查找匹配条件的第一个元素。

public AutomationElement FindFirst(

    TreeScope scope,             // 搜索范围(子级/后代/元素自身等)

    Condition condition          // 查找条件(如按名称、控件类型等)

);

例:查找名为 \"OK\" 的按钮

Condition condition = new PropertyCondition(    AutomationElement.NameProperty,    \"OK\");AutomationElement okButton = parent.FindFirst(    TreeScope.Descendants,  // 递归搜索所有后代    condition);

FindAll():查找匹配条件的所有元素。

public AutomationElementCollection FindAll(

    TreeScope scope,             // 搜索范围

    Condition condition          // 查找条件

);

例:查找所有文本框

Condition condition = new PropertyCondition(    AutomationElement.ControlTypeProperty,    ControlType.Edit);AutomationElementCollection textBoxes = parent.FindAll(    TreeScope.Descendants,    condition);

GetCurrentPattern():获取操作控件的模式.。

public object GetCurrentPattern(

    AutomationPattern pattern    // 模式标识符(如 InvokePattern.Pattern

);

例:获取按钮的 InvokePattern 并点击

AutomationElement button = ...;InvokePattern invokePattern =    button.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;invokePattern.Invoke();

注意:若控件不支持该模式,会抛出 InvalidOperationException

TryGetCurrentPattern():安全获取模式(避免未支持该模式的控件抛出异常)。

public bool TryGetCurrentPattern(

    AutomationPattern pattern,   // 模式标识符

    out object patternObject     // 输出模式对象

);

例:安全获取 ValuePattern(适用于输入框)

AutomationElement textBox = ...;if (textBox.TryGetCurrentPattern(    ValuePattern.Pattern,    out object patternObj)){    ValuePattern valuePattern = patternObj as ValuePattern;    valuePattern.SetValue(\"Text\");}

例:查找窗口

// 查找记事本窗口(按窗口标题)Condition condition = new PropertyCondition(    AutomationElement.NameProperty,    \"无标题 - 记事本\"  // 窗口标题);AutomationElement notepadWindow = AutomationElement.RootElement.FindFirst(    TreeScope.Children,    condition);

例:查找子控件(如按钮)

// 在记事本窗口中查找\"文件\"菜单Condition menuCondition = new PropertyCondition(    AutomationElement.NameProperty,    \"文件\");AutomationElement fileMenu = notepadWindow.FindFirst(    TreeScope.Descendants,    menuCondition);

Condition类

Condition 类是一个基类,用于定义搜索 UI 元素的条件。它与 FindFirst() 和 FindAll() 一起使用,用于筛选符合特定条件的 AutomationElement 对象。

子类:

PropertyCondition:按 UI 元素的某个属性匹配。

按控件类型(ControlTypeProperty)

例:查找所有 Button 控件

Condition condition = new PropertyCondition(    AutomationElement.ControlTypeProperty,    ControlType.Button);AutomationElementCollection buttons = rootElement.FindAll(    TreeScope.Children,    condition);

按名称(NameProperty)

例:查找名称为Submit的按钮

Condition condition = new PropertyCondition(    AutomationElement.NameProperty,    \"Submit\");AutomationElement submitButton = rootElement.FindFirst(    TreeScope.Descendants,    condition);

按自动化 ID(AutomationIdProperty)

例:查找 AutomationId 为 \"txtUsername\" 的文本框(适用于唯一标识元素)

Condition condition = new PropertyCondition(    AutomationElement.AutomationIdProperty,    \"txtUsername\");AutomationElement usernameBox = rootElement.FindFirst(    TreeScope.Descendants,    condition);

按控件是否可交互(AutomationElement.IsEnabledProperty)

控件是否在屏幕上可见(AutomationElement.IsOffscreenProperty)

AndCondition:逻辑 AND,同时满足多个条件。

例:查找 所有 CheckBox 且 Name 包含 \"Agree\" 的元素

Condition controlTypeCondition = new PropertyCondition(    AutomationElement.ControlTypeProperty,    ControlType.CheckBox);Condition nameCondition = new PropertyCondition(    AutomationElement.NameProperty,    \"Agree\");AndCondition combinedCondition = new AndCondition(controlTypeCondition, nameCondition);AutomationElementCollection checkBoxes = rootElement.FindAll(    TreeScope.Descendants,    combinedCondition);

OrCondition:逻辑 OR,满足任意一个条件。

NotCondition:逻辑 NOT,排除满足条件的元素。

TrueCondition:匹配任何 UI 元素(相当于 * 通配符)。

例:查找当前窗口下的所有 UI 元素

AutomationElement rootElement = AutomationElement.RootElement;// 使用 TrueCondition(匹配所有元素)Condition condition = Condition.TrueCondition; // 或 new TrueCondition()// 查找所有子元素(包括嵌套元素)AutomationElementCollection allElements = rootElement.FindAll(    TreeScope.Descendants,    condition);// 遍历结果foreach (AutomationElement element in allElements){    Console.WriteLine($\"Element: {element.Current.Name} | ControlType: {element.Current.ControlType}\");}

FalseCondition:不匹配任何 UI 元素。

TreeScope

TreeScope 是一个枚举类型,用于指定在 UI 元素树(UI Tree)中的搜索范围,即在调用 FindFirst 或 FindAll 方法时,要查找哪些层级的元素。

搜索模式

Element:仅搜索当前元素本身(不包含子元素)。        

Children:仅搜索当前元素的直接子元素(不递归)。        

Descendants:递归搜索当前元素的所有后代元素(包括子元素的子元素)。        

Subtree:当前元素 + 所有后代(相当于 `Element        + Descendants`)。

Patterns

Patterns(模式) 代表 UI 元素的交互方式。不同的控件(如按钮、复选框、滑块、下拉菜单)支持不同的交互模式,例如 Invoke(执行操作)、Selection(选择项目)、或 Value(修改值)。通过 Patterns,自动化测试可以 模拟用户操作,如点击按钮、输入文本、选中选项等。

常见的 UIA Patterns

PATTERN 名称

功能描述

典型控件

Invoke

执行默认操作(如点击按钮)。

Button、Hyperlink

Selection

管理选中项(单/多选)。

ListBox、RadioButton

ExpandCollapse

展开/折叠(如树形菜单)。

TreeView、ComboBox

Value

设置/获取值(如文本框)。

TextBox、SpinBox

Toggle

开关状态(如复选框)。

CheckBox、Switch

Scroll

滚动容器(如滚动条)。

ScrollViewer、ListBox

Window

管理窗口(最小化/最大化)。

Window、Dialog

Text

获取或操作文本内容。

RichTextBox、Label

RangeValue

调节数值范围(如滑块)。

Slider、ProgressBar

例:点击按钮(InvokePattern)

// 查找按钮(示例:计算器中的“5”按钮)AutomationElement button = AutomationElement.RootElement    .FindFirst(TreeScope.Descendants, new PropertyCondition(        AutomationElement.NameProperty, \"5\"  // 按钮名称    ));// 获取 InvokePattern(如按钮可点击)if (button.TryGetCurrentPattern(InvokePattern.Pattern, out object patternObj)){    InvokePattern invokePattern = (InvokePattern)patternObj;    invokePattern.Invoke();  // 模拟点击    Console.WriteLine(\"Button clicked!\");}

例:操控复选框(TogglePattern)

// 查找复选框AutomationElement checkbox = AutomationElement.RootElement    .FindFirst(TreeScope.Descendants, new PropertyCondition(        AutomationElement.ControlTypeProperty, ControlType.CheckBox    ));// 获取 TogglePatternif (checkbox.TryGetCurrentPattern(TogglePattern.Pattern, out object toggleObj)){    TogglePattern togglePattern = (TogglePattern)toggleObj;    Console.WriteLine($\"Current state: {togglePattern.Current.ToggleState}\");       togglePattern.Toggle();  // 切换状态(On ↔ Off)    Console.WriteLine($\"New state: {togglePattern.Current.ToggleState}\");}

例:修改文本框内容(ValuePattern)

// 查找文本框AutomationElement textBox = AutomationElement.RootElement    .FindFirst(TreeScope.Descendants, new PropertyCondition(        AutomationElement.ControlTypeProperty, ControlType.Edit    ));// 获取 ValuePatternif (textBox.TryGetCurrentPattern(ValuePattern.Pattern, out object valueObj)){    ValuePattern valuePattern = (ValuePattern)valueObj;    valuePattern.SetValue(\"Hello, UIA!\");  // 设置文本    Console.WriteLine($\"Text set to: {valuePattern.Current.Value}\");}

接口:

static AutomationElement GetElementByName(AutomationElement parent,string strName, int iAddTry = 0, bool bMsg = true)        {            AutomationElement child = null;            child = parent.FindFirst(                    TreeScope.Children,                    new PropertyCondition(AutomationElement.NameProperty, strName));            int iRetry = 1;            int iRetryEnd = RETRY + iAddTry;            while (iRetry++ < iRetryEnd && child == null)            {                WriteLog(logFilePath, string.Format(\"retry found Name: \'{0}\' ,number: {1}.\", strName, iRetry));                child = parent.FindFirst(                    TreeScope.Children,                    new PropertyCondition(AutomationElement.ClassNameProperty, strName));                if (child != null)                {                    break;                }                Thread.Sleep(SLEEPTIME * iRetry);                //The last time                if (iRetry == iRetryEnd && bMsg)                {                    var customMsgBox = new SiviewMessageBox(\"获取控件超时\", \"Warning\");                    DialogResult result = customMsgBox.ShowDialog();                    if (result == DialogResult.OK)                    {                        iRetry = 1;                    }                }            }            return child;        }
static AutomationElement GetElementByClassName(AutomationElement parent, string strClassName, int iAddTry = 0, bool bMsg = true)        {            AutomationElement child = null;            child = parent.FindFirst(                    TreeScope.Children,                    new PropertyCondition(AutomationElement.ClassNameProperty, strClassName));            int iRetry = 1;            int iRetryEnd = RETRY + iAddTry;            while (iRetry++ < iRetryEnd && child == null)            {                WriteLog(logFilePath, string.Format(\"retry found ClassName: \'{0}\' ,number: {1}.\", strClassName, iRetry));                child = parent.FindFirst(                    TreeScope.Children,                    new PropertyCondition(AutomationElement.ClassNameProperty, strClassName));                if (child != null)                {                    break;                }                Thread.Sleep(SLEEPTIME * iRetry);                //The last time                if (iRetry == iRetryEnd && bMsg)                {                    var customMsgBox = new SiviewMessageBox(\"获取控件超时\", \"Warning\");                    DialogResult result = customMsgBox.ShowDialog();                    if (result == DialogResult.OK)                    {                        iRetry = 1;                    }                }            }            return child;        }
static async Task InvokeButtonAsync(AutomationElement btn)        {            await Task.Run(() =>            {                InvokePattern invoke = btn.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;                if (invoke != null)                {                    invoke.Invoke();                }            });        }
static bool BtnAction(AutomationElement btn)        {            if (btn == null)            {                return false;            }            ControlType conType = btn.Current.ControlType;            if (conType == ControlType.RadioButton || conType == ControlType.DataItem || conType==ControlType.TabItem)            {                SelectionItemPattern sele = btn.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;                if (sele != null)                {                    sele.Select();                    return true;                }                return false;            }            else if (conType == ControlType.Button || conType == ControlType.MenuItem)            {                InvokePattern invoke = btn.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;                if (invoke != null)                {                     invoke.Invoke();               /*有些控件不能使用invoke.Invoke()即使他是InvokePattern                      Task.Run(() => InvokeButtonAsync(btn));                      Thread.Sleep(iMS);*/                    return true;                }                return false;            }            return false;        }

 

static bool LineAction(AutomationElement line, string text)        {            if (line == null)            {                return false;            }            ControlType conType = line.Current.ControlType;            if (conType == ControlType.Edit || conType == ControlType.ComboBox)            {                ValuePattern value = line.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;                if (value != null)                {                    value.SetValue(text);                    return true;                }                return false;            }            else if (conType == ControlType.Document)            {                line.SetFocus();                if (text == \"\")                    return true;                //SendKeys.SendWait(text); //会自动切换成中文输入法,导致输入不全                Clipboard.SetText(text);                SendKeys.SendWait(\"^v\");                return true;            }            else if (conType == ControlType.Text)            {                if (line.Current.Name == \"Equipment ID\")                {                    if (text == \"\")                        return true;                    //line.SetFocus(); //Call failure, 可能是父窗口下只有一个Line(可以放置焦点的元素),默认打开父窗口,焦点就会在这个元素上,此时调用SetFocus就会error                    //SendKeys.SendWait(text); //会自动切换成中文输入法,导致输入不全                    Clipboard.SetText(text);                       SendKeys.SendWait(\"^v\");                 }                else                {                    line.SetFocus();                    if (text == \"\")                        return true;                    //SendKeys.SendWait(text); //会自动切换成中文输入法,导致输入不全                    Clipboard.SetText(text);                    SendKeys.SendWait(\"^v\");                }                return true;            }            return false;        }