pywinauto 同时支持空间操作和图像操作,支持win32 API和MS UI Automation API

定位工具

UISpy

环境安装

python环境安装(3.5 以上)

pass

pywinauto库安装

1
$ pip install pywinauto

pywinauto 基本特性

  • 跨平台
  • 强大GUI自动化功能
  • 简单易用
  • 支持多种应用程序的类型
  • 丰富的文档和社区支持
  • 图像识别和模糊匹配

基本使用

主要用户模块

pywinauto.application

Application.start() 启动应用程序或使用 Application.connect()连接到已启动的应用程序。

打开应用程序

  • Win32 API (backend="win32") - 现在的默认backend

    应用程序是传统的 Win32 应用程序(例如用 C++ 编写的桌面应用),通常可以使用 win32 后端。

  • MS UI Automation (backend="uia")

    • WinForms, WPF, Store apps, Qt5, 浏览器

    注意: Chrome在启动之前需要--force-renderer-accessibility cmd标志。 由于comtypes Python库限制,不支持自定义属性和控件。

  • 如何识别 win32 还是 uia ,可以使用 Inspect 工具

    将Inspect.exe切换到UIA模式(使用MS UI Automation)。如果它可以显示比Spy ++更多的控件及其属性,可能"uia" 后端是你的选择。

启动应用
1
2
from pywinauto.application import Application
app = Application(backend='uia').start('notepad.exe')
连接已经打开了窗口的应用程序
1
2
3
4
5
6
7
8
9
from pywinauto.appliction import Application
# 通过进程id连接
app = Application(backend='uia').connect(process=4444)
# 通过窗口句柄连接 - 可以通过辅助工具获取窗口句柄
app = Application(backend='uia').connect(handle=1904040)
# 通过窗口标题
app = Application(backend='uia').connect('notepad')
# 通过应用程序exe路径连接
app = Application(backend='uia').connect(r'C:\Program Files (x86)\iOA\iOA.exe')

创建一个桌面应用程序对象

当创建应用程序对象,但并不需要打开某个应用程序时。而是通过后续选择桌面任何窗口

1
2
from pywinauto from Desktop
app = Desktop()

获取当前后台所有窗体或控件信息

find_elements

find_elements 是一个用于查找窗体或控件的函数,返回一个找到的元素列表。

参数

  • control_type: 控件的类型,例如 Button, Edit, ComboBox 等。可以通过控件的类型来过滤结果。
  • title: 窗口的标题(或部分标题)。可以精确匹配或使用正则表达式。
  • classname: 控件类名。如 Notepad
  • process_id: 指定查找控件的进程 ID。
  • handle: 窗口句柄(HWND)以指定范围。
  • 其他: 还支持其他查找条件,例如 control_id, backend, 等。
1
2
3
4
5
from pywinauto.findwindows import find_elements
# 查找所有的 'Edit' 控件
edit_controls = find_elements(control_type='Edit')
for edit in edit_controls:
print(edit)
find_element

find_element 用于查找单个元素,返回找到的单个控件对象。返回唯一的结果。如果没有找到符合条件的控件,会抛出 ElementNotFoundError

1
2
3
from pywinauto.findwindows import find_elements
# 查找所有的 'Edit' 控件
dlg = find_element()
  1. dlg.handle:获取第一个窗口的句柄。
  2. dlg.class_name:获取第一个窗口的类名。
  3. dlg[0].window_text():正确的方法来获取窗口的标题。

查找当前后台所有窗口信息

find_windows
  • 查找所有符合条件的窗口,返回一个窗口句柄列表。
  • 使用方法类似 find_window,但返回多个匹配项。
find_window
  • 用于查找单个窗口,并返回该窗口的句柄。
  • 参数可以包含 titleclass_namecontrol_idprocess_id 等等。

获取当前打开的所有窗口的列表,包括句柄、类名和标题

定位程序的某个窗口

使用类名选择窗口 - 推荐
1
2
3
4
5
from pywinauto.application import Application
# 打开应用
app = Application(backend='uia').start('notepad.exe')
# 类名选择定位窗口
dlg = app['ClassName']
使用app.窗口类名
1
2
3
4
5
6
from pywinauto.application import Application

# 打开应用
app = Application(backend='uia').start('notepad.exe')
# 使用app.类名 定位窗口
dlg = app.ClassName
使用window()方法
1
2
3
4
5
6
from pywinauto.application import Application

# 打开应用
app = Application(backend='uia').start('notepad.exe')
# 使用app.类名 定位窗口
dlg = app.window()

app.window() 方法是用于获取与特定应用程序的窗口对象的主要手段

主要参数

  • title: 窗口的标题文本
  • title_re: 用于匹配窗口标题。
  • control_type: 窗口控件的类型,例如 ‘Button’, ‘Edit’, ‘ComboBox’ 等
  • control_type_re: 用于匹配窗口控件类型。
  • class_name: 窗口的类名,通常在窗口属性中可以找到,例如 ‘Notepad’
  • class_name_re: 用于匹配窗口类名。
  • handle: 窗口的句柄(HWND)。如果你已经知道窗口的句柄,可以直接使用它
  • found_index: 当存在多个窗口符合查询条件时,用于指定要返回的窗口的索引。

特性

  • 方法链

    可以通过方法链来一层一层地获取控件并执行操作

    1
    app.window(title='无标题 - 记事本')['编辑'].type_keys('Hello, World!')
打印窗口所有的控件信息
1
2
# 打印窗口所有的控件信息
app.print_control_identifiers()

窗口控制

窗口最大化
1
dlg.maximize()
窗口最小化
1
dlg.minimize()
还原窗口正常大小
1
dlg.restore()
显示窗口状态
1
2
# 获取窗口显示状态 1:最大化 0:正常
dlg.get_show_state()
关闭窗口
1
dlg.close()
获取窗口显示坐标
1
rect = dlg.rectangle()

窗口控件操作

使用类名选择窗口
1
2
3
4
5
6
7
from pywinauto.application import Application
# 打开应用
app = Application(backend='uia').start('notepad.exe')
# 类名选择定位窗口
dlg = app['ClassName']
# 类名定位窗口控件
dlg = app['Window control']
使用app.窗口类名
1
2
3
4
5
6
7
8
from pywinauto.application import Application

# 打开应用
app = Application(backend='uia').start('notepad.exe')
# 使用app.类名 定位窗口
dlg = app.ClassName
# 使用dlg.类名 定位窗口控件
dlg = app.WindowControl
使用子节点属性 - 可直接定位深层属性
1
2
3
4
5
6
7
from pywinauto.application import Application

# 打开应用
app = Application(backend='uia').start('notepad.exe')
# 使用app.类名 定位窗口
dlg = app.child_window(title="文本编辑器", auto_id="15", control_type="Edit")

获取窗口控件属性方法

获取控件类型
  • wrapper_object()
1
2
3
# 获取编辑控件
Edit = dlg.child_window(title="文本编辑器", auto_id="15", control_type="Edit")
print(Edit.wrapper_object()) # uia_controls.EditWrapper - '文本编辑器', Edit
获取该控件支持的方法
  • print(dir(a.wrapper_object()))
获取控件的子元素
  • children()
1
2
3
4
# 获取编辑控件
Edit = dlg.child_window(title="文本编辑器", auto_id="15", control_type="Edit")
# 只会显示子元素,孙子元素不会显示
print(Edit.print_control_identifiers()) # [<uiawrapper.UIAWrapper - '垂直滚动条', ScrollBar, 343298644>]
获取控件类名
  • class_name()
1
2
Edit = dlg.child_window(title="文本编辑器", auto_id="15", control_type="Edit")
print(Edit.class_name()) # Edit
以字典形式返回控件属性
  • get_properties
1
2
3
# 获取编辑控件
Edit = dlg.child_window(title="文本编辑器", auto_id="15", control_type="Edit")
print(Edit.get_properties) # <bound method BaseWrapper.get_properties of <uia_controls.EditWrapper - '文本编辑器', Edit, 372726574>>
控件文本内容获取
  • dlg.texts()
1
2
3
4
5
6
# 获取菜单栏
menu = dlg['menu']
# 选择文件菜单项
file = menu.child_window(title="文件")
# 获取file菜单项文本内容
print(file.texts())

窗口& 控件内容截图

  • capture_as_image

该方法需要安装 pillow 图像处理的第三方库,不然在save方法时会事变

1
2
3
4
# 将指定窗口或空间调用capture_as_image()方法,进行截图操作
pic = app[xxx].capture_as_image()
# 将pic截图数据进行保存
pic.save("xxx.png")

pywinauto窗口控件分类

  • 状态栏: StatusBar
  • 按钮: Button
  • 单选框:RadioButton
  • 组合框:ComboBox
  • 编辑栏:Edit
  • 列表框:ListBox
  • 弹出菜单:PopupMenu
  • 工具栏:Toolbar
  • 树状视图:TreeView
  • 菜单项:MenuItem
  • 静态内容:Static
  • 复选框:CheckBox
  • 组框:GroupBox
  • 对话框(窗口):Dialog
  • 头部内容:Header
  • 列表显示控件:ListView
  • 选项卡控件:TabControl
  • 工具提示:ToolTips
  • 菜单:Menu
  • 窗格:Pane

控件操作

菜单类型控件操作

获取所有的子菜单
  • items
1
2
3
4
5
Menu = dlg['Menu2']
print(Menu.items())
'''
[<uia_controls.MenuItemWrapper - '文件(F)', MenuItem, 285048582>, <uia_controls.MenuItemWrapper - '编辑(E)', MenuItem, 1026408548>, <uia_controls.MenuItemWrapper - '格式(O)', MenuItem, -605001711>, <uia_controls.MenuItemWrapper - '查看(V)', MenuItem, 136358255>, <uia_controls.MenuItemWrapper - '帮助(H)', MenuItem, -1495052004>]
'''
根据索引选定指定菜单项
  • item_by_index
1
2
3
4
5
6
Menu = dlg['Menu2']
# 通过下标获取
print(Menu.item_by_index(1))
'''
uia_controls.MenuItemWrapper - '编辑(E)', MenuItem
'''
根据路径指定菜单项
  • item_by_path
1
2
3
4
5
6
7
8
Menu = dlg['Menu2']
# 只找下一层的属性
print(Menu.item_by_path("编辑"))
'''
uia_controls.MenuItemWrapper - '编辑(E)', MenuItem
'''
# 跨层级选择 (这里指找编辑里面的编辑文件,-> 代表下一层级)
print(Menu.item_by_path("编辑->编辑文件"))
点击菜单项
  • click_input()
1
2
3
Menu = dlg['Menu2']
# 通过下标获取菜单项后进行点击操作
Menu.item_by_index(1).click_input()

编辑类型控件操作

输入框,编辑框内容输入
  • type_keys()
1
2
3
Edit = dlg['Edit']
# 对定位到的编辑控件进行内容输入
Edit.type_keys('123123')

键盘操作 - send_keys()

使用键盘操作需要导入模块 pywinauto.keyboard 的 send_key方法
输入的内容位置,都是光标所在位置

键盘点击/输入
  • 使用方法:send_keys(按键)

  • 例如:

    • 按F5: send_keys({VK_F5})
    • 按F5: send_keys({VK_F12})
    • 按回车键: send_keys({VK_RETURN})
    • 按普通字母键: send_keys(‘A’)
    • 按win键: send_keys({VK_LWIN})
  • 可以将多个按键卸载一起,例如

    • 按win键,输入cmd,按回车

      send_keys(‘{VK_LWIN}cmd{VK_RETURN}’)

键盘修饰符

各种电脑键盘功能键的简便写法

  • 修饰符
    • “+”: 指Shift键
    • “^”: 指Ctil键
    • “%”: 指Alt键
  • 组合键使用
    • send_keys(“^s”): 按Ctil+s ,执行保存操作的快捷键
    • send_keys(“^c”): 按Ctil+c ,执行复制操作的快捷键
    • send_keys(“^v”): 按Ctil+v ,执行粘贴操作的快捷键
    • send_keys(“^a”): 按Ctil+a ,执行全选操作的快捷键

模拟鼠标操作

使用鼠标模拟操作需导入 pywinauto.mouse 模块 的相关方法

单击 - click()
双击 - doubl_click()
鼠标右击 - right_click()
鼠标中间点击 - wheel_click()
按下鼠标 - press()
鼠标释放 - repleace()
鼠标移动 - move()
鼠标滚动 - scroll()
移动鼠标 - move()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pywinauto import mouse
# 点击(默认左键) x:100,y:100 坐标的元素
mouse.click(coords=(100,100))
# 右键单击 x:100,y:100 坐标的元素
mouse.right_click(coords=(100,100))
# 双击 x:100,y:100 坐标的元素,button为左右键
mouse.doubl_click(button="left", coords=(100,100))

# 框选
# 按下鼠标
mouse.press(coords=(100, 100))
# 释放鼠标
mouse.release(coords(200, 200))

# 鼠标滚轮滚动
# 在坐标200,200的地方,滚动滚轮2次
mouse.scroll(coords=(200, 200), wheel_dist=2)

# 移动鼠标
mouse.move(coords=(200,200))

访问系统通知(任务栏区域)区域

使用explorer程序访问任务栏区域
1
2
3
4
from pywinauto from Application
app = Application('uia').connect('explorer')
# 获取底部状态栏
bottom_icons = app['任务栏']['用户通知区域'].click()
隐藏的任务栏区域
1
2
3
4
from pywinauto from Application
app = Application('uia').connect('explorer')
# 获取底部状态栏
bottom_icons = app['任务栏']['用户通知区域'].click()

timings 模块等待机制

等待某窗口处于某个状态 - wait()

  • 参数

    • wait_for - 等待状态(有以下几种状态)

      • exists - 表示该窗口是有效句柄
      • visible - 表示该窗口未隐藏
      • enabled - 表示为禁用窗口
      • ready - 表示窗口可见并启动
      • active - 表示该窗口处于活动状态
    • timeout - 超时时间

    • retry_interval - 重试时间间隔

1
2
3
4
5
6
from pywinauto.timings import wait 
# ... 前面实现了点击某个元素效果,展示了新页面
new_dlg = app["新页面"] # 定位到新页面
# 等待窗口处于可见状态(超时时间为10s,每2s检查一次)
new_dlg.wait(wait_for="ready", timeout=10, retry_interval=2)
# 上面等待结果为真,则继续执行,结果为否,则报错

等地啊某个窗口不处于某个特定状态 - wait_not()

  • 参数

    • wait_for_not - 等待状态(有以下几种状态)

      • exists - 表示该窗口是有效句柄
      • visible - 表示该窗口未隐藏
      • enabled - 表示为禁用窗口
      • ready - 表示窗口可见并启动
      • active - 表示该窗口处于活动状态
    • timeout - 超时时间

    • retry_interval - 重试时间间隔

1
2
3
4
5
6
from pywinauto.timings import wait_not
# ... 前面实现了点击某个元素效果,展示了新页面
new_dlg = app["新页面"] # 定位到新页面
# 等待窗口处于可见状态(超时时间为10s,每2s检查一次)
new_dlg.wait_not(wait_for_not="ready", timeout=10, retry_interval=2)
# 上面等待结果为真,则继续执行,结果为否,则报错

等待直到某个条件为真后再继续执行后续操作 - wait_until

1
2
3
from pywinauto.timings import wait_until  

wait_until(condition, timeout=None, retry_interval=None, count=None)

参数说明:

  • condition: 一个函数或lambda表达式,表示等待的条件,当条件返回True时,等待结束。
  • timeout: 超时时间,等待的最长时间,单位为秒。默认为None,表示无限等待。
  • retry_interval: 重试间隔,每隔指定的时间(单位为秒)检查一次条件。默认为None,表示每隔一段时间检查一次。
  • count: 重试次数上限,如果超过指定次数仍未满足条件,等待结束。默认为None,表示不限制次数。

等待进程的cpu使用率低于某个阙值时才能通过 - wait_cpu_usage_lower()

此方法仅适用于整个应用程序进程,不适用与窗口和元素

  • 参数
    • threshold: 指定的 CPU 使用率阈值,当系统 CPU 使用率低于此阈值时返回。取值范围为 [0, 100]
    • timeout: 超时时间,即等待的最长时间。如果超过该时间,会引发异常。默认为 None,表示无限等待。
    • retry_interval: 重试间隔时间,即每次尝试之间的等待时间。
    • usage_monitor: 监测 CPU 使用率的对象,默认为 None
1
2
3
4
5
from pywinauto.timings import wait_cpu_usage_lower  
# ... 前面实现了点击某个元素效果,展示了新页面
new_dlg = app["新页面"] # 定位到新页面
# 等待进程占有率低于5%(超时时间为10s,每2s检查一次)
new_dlg.wait_cpu_usage_lower(threshold=5, timeout=10, usage_interval=2)