uiautomation
是一个用于 Windows GUI 自动化的 Python 库,它封装了 Microsoft UI Automation API,使得我们可以通过编程方式查找和操作 Windows 应用程序的控件(如按钮、文本框、菜单等)。
教程大纲:
uiautomation
?uiautomation
searchDepth
控制搜索深度RegexName
ValuePattern
(读写值)InvokePattern
(执行操作,如按钮点击)TogglePattern
(切换状态,如复选框)ExpandCollapsePattern
(展开折叠,如树视图)SelectionItemPattern
和 SelectionPattern
(选择项)ScrollPattern
(滚动)TextPattern
(高级文本操作)uiautomation
库自带的控件检查工具uiautomation
自带方法通常足够)Microsoft UI Automation (UIA) 是 Windows 平台上的一种辅助功能框架,它允许应用程序(包括自动化脚本)以编程方式访问、识别和操作另一个应用程序的用户界面 (UI) 元素。
uiautomation
?uiautomation
库提供了简洁易用的 Python 接口,大大降低了使用 UIA 的门槛。uiautomation
是一个强大的选择,尤其适合传统桌面应用。uiautomation
使用 pip 进行安装:
pip install uiautomation
建议在一个虚拟环境中安装。
uiautomation
中,窗口本身也是一个控件,通常是其他控件的根容器。Windows 应用程序的 UI 元素以层级树状结构组织。顶层通常是桌面 (Desktop),然后是应用程序窗口,窗口内包含面板、工具栏,再往下是具体的按钮、文本框等。uiautomation
通过遍历这个树来查找控件。
uiautomation
主要通过指定控件的属性来查找它们。常用的属性有:
Name
: 控件的文本标签或名称。AutomationId
: 由开发者为控件设置的唯一标识符,最稳定可靠。ClassName
: 控件的窗口类名 (Windows Class Name)。ControlType
: 控件的类型,如 ControlType.ButtonControl
, ControlType.EditControl
。import uiautomation as auto
import time
import subprocess
# 设置全局搜索超时时间 (秒)
auto.uiautomation.SetGlobalSearchTimeout(10) # 默认是10秒
# 打印控件的详细信息,方便调试
def print_control_info(control):
if not control:
print("控件未找到")
return
print(f"控件名称: {control.Name}")
print(f"AutomationId: {control.AutomationId}")
print(f"类名: {control.ClassName}")
print(f"控件类型: {control.ControlTypeName}")
print(f"是否可见: {control.IsVisible()}")
print(f"是否启用: {control.IsEnabled()}")
print("-" * 20)
# --- 启动和附加到应用程序 ---
# 示例1: 启动记事本
subprocess.Popen('notepad.exe')
time.sleep(1) # 等待程序启动
# 附加到已运行的记事本窗口
# searchDepth=1 表示只在桌面的直接子窗口中搜索
# 建议优先使用 Name 和 ClassName 组合,或 AutomationId
notepad_window = auto.WindowControl(searchDepth=1, ClassName="Notepad", Name="无标题 - 记事本")
# 如果记事本已打开文件,Name 会是 "文件名 - 记事本"
# notepad_window = auto.WindowControl(searchDepth=1, ClassName="Notepad", RegexName=".*记事本") # 使用正则匹配标题
if not notepad_window.Exists(maxSearchTime=3):
print("记事本窗口未找到!")
exit()
print("成功附加到记事本窗口:")
print_control_info(notepad_window)
# --- 查找控件 ---
# 记事本的编辑区域通常是 EditControl
edit_area = notepad_window.EditControl() # 查找第一个 EditControl
# 如果有多个同类型控件,需要更精确的定位
# edit_area = notepad_window.EditControl(AutomationId="15") # 假设知道 AutomationId
# edit_area = notepad_window.EditControl(Name="文本编辑器") # 某些版本的记事本可能有Name
if not edit_area.Exists(maxSearchTime=2):
print("编辑区域未找到!")
else:
print("找到编辑区域:")
print_control_info(edit_area)
# --- 控件交互 ---
# 输入文本
edit_area.SendKeys("你好,UIAutomation 世界!{Enter}", interval=0.05) # interval是按键间隔
edit_area.SendKeys("这是第二行。\n", interval=0.05) # \n 也可以换行
# 获取文本 (对于EditControl,更推荐使用ValuePattern)
if edit_area. 패턴_지원_여부(auto.PatternId.ValuePattern): # 检查是否支持ValuePattern
current_text = edit_area.GetValuePattern().Value
print(f"当前文本内容: {current_text}")
else:
print(f"编辑区域的Name属性(可能不全): {edit_area.Name}")
# --- 点击菜单 ---
# 文件菜单
menu_file = notepad_window.MenuItemControl(Name="文件(F)")
if menu_file.Exists(maxSearchTime=2):
print("找到文件菜单")
menu_file.Click() # 点击方式1: 直接调用Click
# menu_file.GetInvokePattern().Invoke() # 点击方式2: 使用InvokePattern
time.sleep(0.5)
# 退出菜单项
menu_exit = notepad_window.MenuItemControl(Name="退出(X)") # 注意:菜单项可能在子菜单中,需要重新从父级开始查找或指定深度
if menu_exit.Exists(maxSearchTime=2):
print("找到退出菜单项")
menu_exit.Click()
else:
print("退出菜单项未找到!")
else:
print("文件菜单未找到!")
# --- 等待机制 ---
# 退出时可能会有 "是否保存" 的对话框
# 等待对话框出现,超时时间5秒
save_dialog = auto.WindowControl(searchDepth=1, ClassName="#32770", Name="记事本") # "#32770" 是标准对话框类名
# 或者 save_dialog = auto.WaitForExist(auto.WindowControl(searchDepth=1, ClassName="#32770", Name="记事本"), timeout=5)
if save_dialog.Exists(maxSearchTime=5): # Exists内部也包含了等待
print("找到保存对话框:")
print_control_info(save_dialog)
# 点击 "不保存(N)" 按钮
# 注意: 按钮的 Name 可能因系统语言而异
# 可以用 Inspect.exe 查看确切的 Name 或 AutomationId
# no_save_button = save_dialog.ButtonControl(Name="不保存(N)")
# 或者,如果知道 AutomationId (更可靠)
# no_save_button = save_dialog.ButtonControl(AutomationId="CommandButton_7") # 这个ID不一定对,需要用Inspect工具查看
# 尝试多种语言或通过索引查找
no_save_button = None
possible_names = ["不保存(N)", "Don't Save", "不保存"]
for name in possible_names:
btn = save_dialog.ButtonControl(Name=name)
if btn.Exists(0.1): # 快速检查
no_save_button = btn
break
if not no_save_button: # 如果按名称找不到,尝试按索引(不推荐,但作为后备)
buttons = save_dialog.GetChildren()
for btn_child in buttons:
if btn_child.ControlTypeName == "ButtonControl":
# 这里可以根据按钮的顺序或特定属性进一步判断
# 例如,"不保存" 通常是对话框中的第2或第3个按钮
# 此处仅为演示,实际中应避免硬编码索引
print(f"发现按钮: {btn_child.Name}")
if "不保存" in btn_child.Name or "Don't Save" in btn_child.Name: # 更灵活的匹配
no_save_button = btn_child
break
if no_save_button and no_save_button.Exists(0.1):
print(f"找到按钮: {no_save_button.Name}")
no_save_button.Click()
else:
print("未找到'不保存'按钮,可能已自动关闭或名称不匹配。")
else:
print("未出现保存对话框,可能记事本内容未更改或已自动关闭。")
print("记事本自动化演示完成。")
# 假设我们有一个更复杂的应用程序窗口
# app_window = auto.WindowControl(Name="我的复杂应用")
# --- 使用 searchDepth 控制搜索深度 ---
# 默认 searchDepth 是无限深。searchDepth=1 只搜索直接子元素。
# control = app_window.Control(searchDepth=2, Name="目标控件") # 搜索到孙子辈
# --- 使用正则表达式 RegexName ---
# control = app_window.ButtonControl(RegexName="提交订单.*") # 匹配以"提交订单"开头的按钮
# --- 组合条件搜索 ---
# control = app_window.EditControl(ClassName="Edit", AutomationId="userTextBox")
# --- 遍历控件树 ---
# parent_control = control.GetParentControl()
# first_child = control.GetFirstChildControl()
# next_sibling = control.GetNextSiblingControl()
# previous_sibling = control.GetPreviousSiblingControl()
# children = control.GetChildren() # 获取所有直接子控件列表
# for child in children:
# print_control_info(child)
# --- 查找所有匹配的控件 ---
# buttons = app_window.FindAllControls(ControlType=auto.ControlType.ButtonControl)
# print(f"共找到 {len(buttons)} 个按钮")
# for btn in buttons:
# print_control_info(btn)
控件模式定义了控件可以执行的特定功能。一个控件可以支持零个或多个模式。
模式是 UIA 的核心概念,它将控件的功能标准化。例如,无论一个按钮长什么样,只要它支持 InvokePattern
,你就可以调用 Invoke()
方法来“点击”它。
ValuePattern
: 用于可以设置和获取值的控件,如文本框、滑块。
control.GetValuePattern().Value
(获取值)control.GetValuePattern().SetValue("新内容")
(设置值,通常比 SendKeys
更可靠)InvokePattern
: 用于可以被调用的控件,如按钮、菜单项。
control.GetInvokePattern().Invoke()
(执行动作)TogglePattern
: 用于有开/关或选中/未选中状态的控件,如复选框、单选按钮。
control.GetTogglePattern().Toggle()
(切换状态)control.GetTogglePattern().ToggleState
(获取当前状态,如 ToggleState.On
, ToggleState.Off
, ToggleState.Indeterminate
)ExpandCollapsePattern
: 用于可以展开和折叠的控件,如树视图节点、组合框。
control.GetExpandCollapsePattern().Expand()
control.GetExpandCollapsePattern().Collapse()
control.GetExpandCollapsePattern().ExpandCollapseState
(获取状态)SelectionItemPattern
和 SelectionPattern
:
SelectionItemPattern
: 用于可选择的单个项(如列表项、树节点)。
item.GetSelectionItemPattern().Select()
item.GetSelectionItemPattern().IsSelected
(布尔值)SelectionPattern
: 用于包含可选子项的容器控件(如列表框)。
listbox.GetSelectionPattern().GetSelection()
(返回选中项的列表)ScrollPattern
: 用于可滚动的控件。
control.GetScrollPattern().Scroll(horizontalPercent, verticalPercent)
control.GetScrollPattern().SetScrollPercent(horizontalPercent, verticalPercent)
TextPattern
: 提供对文本内容的复杂访问,如获取选中文本、按范围操作等(较高级)。# 假设 control 是一个已定位到的控件
if control.IsValuePatternAvailable(): # 检查是否支持 ValuePattern
value_pattern = control.GetValuePattern()
print(f"当前值: {value_pattern.Value}")
value_pattern.SetValue("通过模式设置的值")
else:
print("控件不支持 ValuePattern")
if control.IsInvokePatternAvailable():
invoke_pattern = control.GetInvokePattern()
# invoke_pattern.Invoke() # 执行点击
else:
print("控件不支持 InvokePattern")
# 通用检查方法
if control. 패턴_지원_여부(auto.PatternId.TogglePattern): # PatternId 是一个枚举
toggle_pattern = control.GetTogglePattern()
current_state = toggle_pattern.ToggleState
print(f"Toggle 状态: {current_state}")
if current_state == auto.ToggleState.Off:
toggle_pattern.Toggle() # 切换到 On
RegexName
。GetChildren()[index]
,但UI结构变化时易失效。Exists(timeout)
或 WaitForExist()
,在控件出现前轮询。import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
try:
# ... 你的自动化代码 ...
# button = main_window.ButtonControl(Name="不存在的按钮")
# button.Click() # 这会抛出 LookupError 或 TimeoutError
# 示例:安全地点击
button_to_click = auto.ButtonControl(Name="登录") # 假设这是全局查找
if button_to_click.Exists(3): # 等待3秒
button_to_click.Click()
logging.info("按钮已点击")
else:
logging.warning("按钮未在3秒内找到")
except auto.errors.LookupError as e: # 控件未找到
logging.error(f"控件查找失败: {e}")
except auto.errors.TimeoutError as e: # 操作超时
logging.error(f"操作超时: {e}")
except Exception as e:
logging.error(f"发生未知错误: {e}")
C:\Program Files (x86)\Windows Kits\10\bin\\x64\inspect.exe
(或 x86)使用方法:打开 Inspect.exe,将鼠标悬停在目标应用的控件上,Inspect.exe 会显示该控件的属性树和详细信息。
uiautomation
库自带的控件检查工具uiautomation
库提供了一些方法来帮助你理解控件结构:
control.WalkControl()
: 打印控件及其子控件的树状结构和基本信息。# notepad_window.WalkControl(maxDepth=3) # 打印记事本窗口下3层控件信息
auto.GetRootControl()
: 获取桌面根控件。auto.GetFocusedControl()
: 获取当前拥有焦点的控件。auto.GetControlFromPoint(x, y)
: 获取指定屏幕坐标下的控件。uiautomation
本身主要关注控件层面的交互。如果需要模拟全局键盘按键或鼠标移动点击,可以:
auto.SendKeys()
: 全局发送按键。# auto.SendKeys('{Win}d') # 按 Win + D 显示桌面
auto.PressKey(keyCode, scanCode=0, extended=False)
, auto.ReleaseKey(keyCode, scanCode=0, extended=False)
: 按下/释放特定虚拟键码。auto.MoveTo(x, y)
, auto.Click(x, y, button='left')
, auto.RightClick(x, y)
: 全局鼠标操作。import uiautomation as auto
import subprocess
import time
def run_notepad_automation():
# 1. 打开记事本
subprocess.Popen('notepad.exe')
time.sleep(1) # 等待记事本窗口出现
# 2. 找到记事本窗口
# 使用更通用的方式查找,以防标题变化(例如,如果已打开文件)
notepad_win = None
for i in range(5): # 尝试5秒
# notepad_win = auto.WindowControl(searchDepth=1, ClassName="Notepad", RegexName=".*记事本")
# 对于最新版Win11记事本,ClassName可能是 "RichEditD2DPT" (编辑区) 或者窗口是 "ApplicationFrameWindow"
# 因此,更可靠的是直接通过进程名称获取主窗口
notepad_process_id = None
for proc in auto.ProcessSnapshot().processes:
if proc.name.lower() == "notepad.exe":
notepad_process_id = proc.pid
break
if notepad_process_id:
notepad_win = auto.ControlFromHandle(auto.GetWindowHandleByPid(notepad_process_id)) # 通过PID获取主窗口句柄再转Control
if notepad_win and "记事本" in notepad_win.Name: # 进一步确认
break
# 备用方案:如果通过PID获取的主窗口不理想,尝试传统方法
if not notepad_win or not ("记事本" in notepad_win.Name):
notepad_win = auto.WindowControl(searchDepth=1, ClassName="Notepad", RegexName=".*记事本") # 经典记事本
if notepad_win.Exists(0.2): break
notepad_win = auto.WindowControl(searchDepth=1, NameRegex=".*记事本", ClassName="Window") # Win11 UWP 记事本外框
if notepad_win.Exists(0.2): break
time.sleep(1)
if not notepad_win or not notepad_win.Exists(0.1):
print("错误:未能找到记事本窗口。")
return
print(f"成功找到记事本窗口: {notepad_win.Name}")
notepad_win.SetFocus() # 确保窗口在前台并有焦点
notepad_win.SetActive()
# 3. 定位编辑区
# 经典记事本的编辑区是 EditControl
# Win11 UWP 记事本的编辑区可能是 DocumentControl
edit_area = notepad_win.EditControl()
if not edit_area.Exists(0.5):
edit_area = notepad_win.DocumentControl() # 尝试DocumentControl for Win11 Notepad
if not edit_area.Exists(0.5): # 再尝试通过AutomationId (这个ID可能不通用)
edit_area = notepad_win.Control(AutomationId="RichText Control") # 假设新版记事本有这个ID
if not edit_area.Exists(0.5): # 最后的尝试:通过ControlType和Name模糊查找
edit_area = notepad_win.Control(searchDepth=5, ControlType=auto.ControlType.DocumentControl) # 查找第一个Document
if not edit_area.Exists(0.5):
edit_area = notepad_win.Control(searchDepth=5, ControlType=auto.ControlType.EditControl) # 查找第一个Edit
if not edit_area.Exists(0.1):
print("错误:未能定位到编辑区。")
notepad_win.Close() # 关闭记事本
return
print("成功定位到编辑区。")
# 4. 输入文本 (使用 ValuePattern 更可靠)
if edit_area.IsValuePatternAvailable():
edit_area.GetValuePattern().SetValue("你好,世界!\n这是 `uiautomation` 的高级教程。\n")
edit_area.SendKeys("{Ctrl}{End}{Enter}当前时间: " + time.strftime("%Y-%m-%d %H:%M:%S"), interval=0.01)
else: # 备用方案
edit_area.SendKeys("你好,世界!\n这是 `uiautomation` 的高级教程。\n", interval=0.01)
edit_area.SendKeys("{Ctrl}{End}{Enter}当前时间: " + time.strftime("%Y-%m-%d %H:%M:%S"), interval=0.01)
time.sleep(1)
# 5. 操作菜单 (保存)
# 注意:菜单项的Name可能因系统语言而异
# Win11 UWP 记事本的菜单结构也不同,可能需要用AccessKey或不同的Name
# 以下代码主要针对经典记事本
try:
if "记事本" in notepad_win.Name and notepad_win.ClassName == "Notepad": # 经典记事本
print("尝试经典记事本菜单操作...")
notepad_win.MenuItemControl(Name="文件(F)").Click()
time.sleep(0.5)
# notepad_win.MenuItemControl(Name="另存为(A)...").Click() # 注意有省略号
save_as_item = notepad_win.MenuItemControl(RegexName="另存为.*")
save_as_item.Click()
time.sleep(1)
# "另存为" 对话框
save_dialog = auto.WindowControl(ClassName="#32770", NameRegex="另存为") # 标准对话框
if not save_dialog.Exists(3):
# 尝试通过当前活动窗口获取(如果上一步点击成功,对话框应为活动窗口)
save_dialog = auto.GetFocusedControl().GetTopLevelControl() if auto.GetFocusedControl() else None
if not (save_dialog and "另存为" in save_dialog.Name):
print("错误:未找到另存为对话框。")
return
print("找到另存为对话框。")
# 文件名输入框通常是ComboBox下的EditControl或直接的EditControl
filename_edit = save_dialog.EditControl(AutomationId="1001") # 这个ID比较通用
if not filename_edit.Exists(0.5):
filename_edit = save_dialog.ComboBoxControl(AutomationId="1001").EditControl() # 另一种结构
if not filename_edit.Exists(0.5):
filename_edit = save_dialog.EditControl(Name="文件名:") # 按名称
if filename_edit.Exists(0.1):
filename_edit.GetValuePattern().SetValue("MyTestFile.txt")
else:
print("错误:找不到文件名输入框。")
save_dialog.ButtonControl(Name="取消").Click()
return
save_button = save_dialog.ButtonControl(Name="保存(S)")
save_button.Click()
# 处理可能出现的 "覆盖" 对话框
time.sleep(0.5)
confirm_dialog = auto.WindowControl(ClassName="#32770", NameRegex="确认另存为")
if confirm_dialog.Exists(2):
print("找到文件已存在确认对话框。")
confirm_dialog.ButtonControl(Name="是(Y)").Click()
print("文件已保存。")
else: # UWP 记事本,菜单操作不同,这里仅做演示关闭
print("检测到可能是UWP记事本,菜单操作将跳过,直接关闭。")
except auto.errors.LookupError as e:
print(f"菜单操作失败 (控件未找到): {e}")
except Exception as e:
print(f"菜单操作发生错误: {e}")
finally:
# 6. 关闭记事本 (不保存更改)
time.sleep(1)
# notepad_win.Close() # 这会触发保存对话框(如果内容已更改且未保存)
# 更强制的关闭方式,或者先处理对话框
if notepad_win.Exists(0.1):
if "记事本" in notepad_win.Name and notepad_win.ClassName == "Notepad":
notepad_win.GetWindowPattern().Close() # 尝试正常关闭
time.sleep(0.5)
# 检查是否有保存对话框
save_prompt = auto.WindowControl(ClassName="#32770", Name="记事本")
if save_prompt.Exists(2):
print("关闭时出现保存提示,选择不保存。")
# Name 可能为 "不保存(N)" 或 "Don't Save"
no_save_btn = save_prompt.ButtonControl(RegexName="不保存.*|Don't Save")
if no_save_btn.Exists(0.1):
no_save_btn.Click()
else: # 如果按名称找不到,可能需要用更通用的方式
buttons = save_prompt.GetChildren()
for btn in buttons: # 粗略查找包含“不保存”字样的按钮
if "不保存" in btn.Name:
btn.Click()
break
else: # UWP 或其他版本,尝试用标题栏关闭按钮
close_button = notepad_win.ButtonControl(Name="关闭") # UWP 记事本的关闭按钮Name是"关闭"
if close_button.Exists(0.5):
close_button.Click()
else: # 万能但不优雅的 Alt+F4
notepad_win.SendKeys("{Alt}{F4}")
print("记事本自动化流程结束。")
if __name__ == "__main__":
# 注意:运行此脚本前,请确保没有其他重要内容的记事本窗口打开,以免误操作。
# 最好关闭所有记事本实例。
run_notepad_automation()
新版计算器是 UWP 应用,其控件结构和属性与传统 Win32 应用有很大不同。AutomationId
非常重要。
import uiautomation as auto
import subprocess
import time
def run_calculator_automation():
# 1. 启动计算器
try:
subprocess.Popen('calc.exe')
except FileNotFoundError:
print("错误:无法启动计算器 (calc.exe)。请确保已安装。")
return
time.sleep(2) # 等待计算器启动和加载
# 2. 找到计算器窗口
# 新版计算器窗口的 Name 可能是 "计算器" 或 "Calculator"
# ClassName 通常是 "ApplicationFrameWindow" (外框) 或 "Windows.UI.Core.CoreWindow" (核心内容)
calc_window = None
for _ in range(5): # 尝试5秒
calc_window = auto.WindowControl(searchDepth=1, NameRegex="计算器|Calculator", ClassName="ApplicationFrameWindow")
if calc_window.Exists(0.2): break
# 某些系统下,直接是这个类名
calc_window = auto.WindowControl(searchDepth=1, NameRegex="计算器|Calculator", ClassName="Windows.UI.Core.CoreWindow")
if calc_window.Exists(0.2): break
time.sleep(1)
if not calc_window or not calc_window.Exists(0.1):
print("错误:未能找到计算器窗口。")
# 可以尝试打印所有顶层窗口来调试
# for win in auto.GetRootControl().GetChildren():
# print(f"顶层窗口: Name='{win.Name}', ClassName='{win.ClassName}'")
return
print(f"成功找到计算器窗口: {calc_window.Name}")
calc_window.SetFocus()
# 3. 定位控件 (依赖 AutomationId,这些 ID 是 Windows 计算器常用的)
# 使用 Inspect.exe 确认这些 ID 在你的系统上是否一致
# 数字按钮通常在 PaneControl (有时名为 "数字键盘") 下
# 有时按钮直接在窗口下,需要调整 searchDepth 或父控件
# 为了更稳定,可以先定位到包含数字按钮的面板
# 首先尝试直接在窗口下查找,如果不行,再深入查找
# calc_window.WalkControl(maxDepth=5) # 打印控件树帮助分析
def get_button(name, automation_id):
# 尝试多种方式定位按钮,因为UWP应用结构可能微调
btn = calc_window.ButtonControl(AutomationId=automation_id)
if btn.Exists(0.1): return btn
btn = calc_window.ButtonControl(Name=name) # 某些情况下Name也可用
if btn.Exists(0.1): return btn
# 尝试在常见的容器内查找
# 例如,数字按钮可能在 "NumberPad" 或类似名称的 Pane 里
# Inspect.exe 可以帮助找到这些容器的 AutomationId 或 Name
# number_pad = calc_window.PaneControl(AutomationId="NumberPad") # 示例
# if number_pad.Exists(0.1):
# btn = number_pad.ButtonControl(AutomationId=automation_id)
# if btn.Exists(0.1): return btn
return None
button_1 = get_button("一", "num1Button")
button_7 = get_button("七", "num7Button")
button_plus = get_button("加", "plusButton")
button_equal = get_button("等于", "equalButton")
results_text_box = calc_window.TextControl(AutomationId="CalculatorResults") # 显示结果的控件
if not all([button_1, button_7, button_plus, button_equal, results_text_box]):
print("错误:未能定位到所有必要的计算器按钮或结果显示区域。")
if not button_1: print("未找到按钮 1 (num1Button)")
if not button_7: print("未找到按钮 7 (num7Button)")
if not button_plus: print("未找到加号按钮 (plusButton)")
if not button_equal: print("未找到等于按钮 (equalButton)")
if not results_text_box: print("未找到结果显示区域 (CalculatorResults)")
print("\n请使用 Inspect.exe 检查计算器控件的 AutomationId 和 Name。")
print("计算器控件结构可能因 Windows 版本或计算器模式(标准、科学等)而异。")
print("以下是当前窗口的部分控件树信息:")
calc_window.WalkControl(maxDepth=5) # 打印控件树帮助分析
# 关闭计算器
calc_window.GetWindowPattern().Close()
return
# 4. 执行计算: 1 + 7 =
# UWP应用有时响应较慢,确保点击间隔
button_1.Click(waitTime=0.1)
time.sleep(0.2)
button_plus.Click(waitTime=0.1)
time.sleep(0.2)
button_7.Click(waitTime=0.1)
time.sleep(0.2)
button_equal.Click(waitTime=0.1)
time.sleep(0.5) # 等待计算结果显示
# 5. 获取结果
# CalculatorResults 的 Name 属性通常会包含显示的值,格式如 "显示为 8"
result_name = results_text_box.Name
print(f"结果显示区域的原始Name: '{result_name}'")
# 从 "显示为 8" 中提取 "8"
# 实际提取逻辑可能需要根据具体语言和格式调整
actual_result = result_name
if "Display is " in result_name: # 英文系统
actual_result = result_name.replace("Display is ", "").strip()
elif "显示为 " in result_name: # 中文系统
actual_result = result_name.replace("显示为 ", "").strip()
# 其他语言环境可能需要添加更多判断
print(f"计算结果: 1 + 7 = {actual_result}")
if actual_result == "8":
print("计算正确!")
else:
print(f"计算错误或结果解析失败。期望 '8',得到 '{actual_result}'")
# 6. 清除 (CE按钮)
# clear_entry_button = get_button("清除条目", "clearEntryButton") # CE
clear_button = get_button("清除", "clearButton") # C
if clear_button and clear_button.Exists(0.1):
clear_button.Click(waitTime=0.1)
print("已点击清除按钮。")
else:
print("未找到清除按钮。")
time.sleep(0.5)
# 7. 关闭计算器
# calc_window.Close() # 可能无法关闭UWP应用
if calc_window.IsWindowPatternAvailable():
calc_window.GetWindowPattern().Close()
else: # 尝试使用标题栏的关闭按钮
# UWP应用的关闭按钮通常有特定的AutomationId或Name
# 例如 "Close" 或 "关闭"
close_btn = calc_window.ButtonControl(Name="关闭") # 假设Name是"关闭"
if close_btn.Exists(0.2):
close_btn.Click()
else: # 最后手段
calc_window.SendKeys("{Alt}{F4}")
print("计算器自动化流程结束。")
if __name__ == "__main__":
run_calculator_automation()
关于计算器案例的注意事项:
AutomationId
。AutomationId
的重要性:对于 UWP 应用,AutomationId
是最可靠的定位器。请务必使用 Inspect.exe 来查找正确的 AutomationId
。Name
属性会随系统语言变化。AutomationId
通常是语言无关的。不同 Windows 版本或计算器更新也可能导致 AutomationId
或控件结构变化。PaneControl
(面板) 或其他容器控件内。如果直接在窗口下找不到,可能需要先定位到父容器,再查找子控件。time.sleep()
或使用 control.Exists(timeout)
、control.WaitForExist()
等待控件就绪。Name
, AutomationId
, ClassName
, ControlType
),以及控件支持的模式。control.WalkControl(maxDepth)
:在代码中打印控件树,了解当前控件的子控件结构。# window = auto.WindowControl(Name="MyApp")
# window.WalkControl(maxDepth=5)
# button = window.ButtonControl(Name="OK")
# print(f"Name: {button.Name}, AutoId: {button.AutomationId}, Class: {button.ClassName}")
# print(f"Supported patterns: {button.GetSupportedPatternNames()}")
Exists(timeout)
和 WaitForExist(timeout)
:充分利用等待机制,避免因界面加载延迟导致的查找失败。# control = window.EditControl(Name="username")
# if control.Exists(timeout=5): # 等待5秒看是否存在
# control.SetValue("test")
# else:
# print("Username field not found within 5 seconds.")
# login_button = auto.ButtonControl(Name="Login")
# login_button.WaitForExist(timeout=10, interval=0.5) # 等待10秒,每0.5秒检查一次
# login_button.Click()
AutomationId
:如果开发者为控件设置了 AutomationId
,这是最稳定、最可靠的定位方式,因为它通常是唯一的且语言无关的。AutomationId
不可用时,组合使用 Name
, ClassName
, ControlType
等属性来精确定位。GetChildren()[0]
)会使脚本非常脆弱。def click_button_by_name(parent_control, button_name):
button = parent_control.ButtonControl(Name=button_name)
if button.Exists(3):
button.Click()
return True
print(f"Button '{button_name}' not found.")
return False
try-except
块捕获可能发生的 LookupError
(控件未找到)、TimeoutError
等异常,并给出有意义的错误信息或执行备用逻辑。auto.uiautomation.SetGlobalSearchTimeout(seconds)
可以设置全局搜索超时。也可以在单个控件查找时通过 control.Exists(maxSearchTime=...)
或 control.WaitForExist(timeout=...)
等方法指定局部超时。uiautomation
是一个强大的 Python 库,适用于 Windows 桌面应用的 GUI 自动化。通过理解其核心概念(控件、控件树、定位器、模式),结合 Inspect.exe 等辅助工具,你可以构建出稳定可靠的自动化脚本。
关键点回顾:
pip install uiautomation
Name
, AutomationId
(首选), ClassName
, ControlType
, RegexName
。Click()
, SendKeys()
, GetValuePattern().SetValue()
, InvokePattern().Invoke()
等。官方文档和社区(通常是GitHub)是获取最新信息和解决特定问题的好地方:
uiautomation
GitHub 仓库 (作者 yinkaisheng): https://github.com/yinkaisheng/Python-UIAutomation-for-Windows (包含 README 和一些示例)希望这个完整和高级的教程能帮助你掌握 uiautomation
!