在介绍 pywinauto 前,先说一下这个工具的使用方法,就是如何去操作目标应用程序,第一步:实例化要操作的进程,得到 Application 对象;跟 App 自动化一样,window 上面有很多应用程序,先指定要操作哪个程序。第二步:选择窗口,得到一个 WindowSpecification 对象;应用程序打开,就是一个窗口,进入到这个窗口,就能获取当前窗口的元素信息。第三步:基于 WindowSpecification 对象,定位具体的控件(元素)。第四步:对控件执行相应的操作。
使用之前先安装该工具,很简单,cmd命令行输入:pip install pywinauto 即可。
基于上面的思路,就可以开展自动化工作了。首先获取 Application 对象,有2种方式:1、启动一个应用程序;2、直接连接已经打开的应用程序。在这之前,要确定当前应用程序的的开发技术,要不然无法获取程序信息。pywinauto 支持 win32(MFC、VB6、VCL等开发语言) 和 MS UI Automation API(WinForms、WPF、Store apps QT5、浏览器),默认是 win32。那么,怎么才能知道当前应用程序使用哪种开发技术,这里推荐2款工具:inspect.exe、Accessibility Insights。以 inspect.exe 为例,打开该工具,左上角使用 UI Automation,如果有相关信息,就是uia ,如果拒绝链接,就是win32。这类工具网上很多,如果找不到,可以找我要。
find_elements() 方法获取应用窗口,该方法适合已经打开的窗口,不传参数,默认查找所有窗口,关键字参数见下方介绍,可以缩小范围。这样在使用 connect() 方法获取 Application 对象就比较方便。
from pywinauto import Application,findwindows
findwindows.find_elements()
==> [ <win32_element_info.HwndElementInfo - '无标题 - Notepad', Notepad, 854118>, <win32_element_info.HwndElementInfo - 'PyWinAuto - xxx@163.com - 印象笔记', Qt5152QWindowIcon, 788708> ]
findwindows.find_window(title="无标题 - Notepad")
==> 854118
启动一个新的应用:app=Application(backend=“uia”).start(‘notepad.exe’),start 方法传入要打开的应用程序,如果找不到,就要输入全路径。这里就是打开了本机的记事本。
连接已经打开的应用:app=Application(backend=“uia”).connect(title_re=‘.印象笔记.’),connect 方法连接已经打开的程序,有好几种方式,我介绍比较常用的:
process | 可以使用pid 进行绑定; |
handle | 可以使用窗口句柄绑定; |
path | 可以使用程序路径绑定; |
title_re | 根据窗口标题名字绑定; |
class_name | 根据窗口类名绑定; |
这里我比较喜欢用 title_re 参数连接应用程序,每个应用程序打开,在左上角都会有一个 titile,title_re 是利用正则匹配窗口,也可以直接使用 title 参数,但是,如果名字比较长,显得麻烦;使用 process、handle、calss_name 去连接,还需要去查,不如 title_re 方便。具体看情况使用。
到这里,就可以获取到一个 Application 对象,然后可以操作该对象的方法,例如:
- app.process:获取进程 id;
- app.kill:强制关闭该应用程序;
- app.top_window():返回顶层窗口对象;.dump_tree() 方法可以获取当前窗口结构信息,跟后文的 print_control_identifiers() 方法一样;
- app.windows():获取当前应用所有窗口;
- app.cpu_usage():返回指定秒数期间的CPU使用百分比;
- app.wait_cpu_usage_lower(threshold=2.5, timeout=None, usage_interval=None):等待进程CPU使用率百分比小于指定的阈值threshold.
- app.is_process_running():当前进程是否运行中;
有了Application对象,还需要获取窗口对象,app.windows() 是获取所有窗口对象,返回一个列表,包含窗口的标题、类、句柄,这些信息对于连接到具体的窗口很有用。获取窗口对象:app.window(title=“DingTalk”),也可以使用 app[“Dialog”],我比较喜欢使用 window()方法,该方法有很多关键字参数可用:
class_name | 类名; |
title | 控件标题,就是inspect 中的name; |
auto_id | inspect 中的automationId 属性; |
control_type | inspect 中 LocalizedControlType属性(不是中文,是英文,control_type值有MenuBar、Button、Edit、MenuItem等); |
title_re | title正则匹配; |
best_match | 模糊匹配title; |
handle | NativeWindowHandle; |
这里的关键字参数,不仅在 window() 方法可用,在 connect() 方法、child_window() 方法中也可以使用,还可以联合使用,大家可以多试试。
# 获取钉钉窗口对象
dlg = app.window(title="钉钉")
dlg = app.window(class_name="Dialog")
dlg = app["Dialog"]
窗口对象WindowSpecification 有一些常用的方法,如下:
- dlg.maximize():窗口最大化;
- dlg.minimize():窗口最小化;
- dlg.restore():还原窗口;
- dlg.get_show_state():获取窗口显示状态,最大化是1,正常是0;
- dlg.close():关闭窗口;
- dlg.rectangle():获取窗口坐标;
- draw_outline(colour=“red”) :给窗口/控件加外框,便于识别;
- exists(timeout=None, retry_interval=None):判断窗口是否存在;
- wait(state, timeout=None, retry_interval=None):等待窗口;
- wait_not(state, timeout=None, retry_interval=None):等待窗口;
- dlg.print_control_identifiers(depth=None):获取窗口结构信息,depth 指定结构深度;除了通过工具获取控件(元素)信息,这个方法也是重要的途径,工作中也会经常用到。
通过窗口对象可以获取当前窗口的结构信息,然后就可以获取窗口中的控件,方法如下:
- dlg[“控件名称”],这种方式常用,但是注意中文乱码问题。
- dlg.xxx,这种方式不建议,如果控件的名称中间有空格,那就不好使了。
- dlg.child_window(title=,best_match=,…),上面的常见关键字参数都可以使用,获取窗口指定控件(递归查找)found_index 指定索引,如果有多个相同的控件,可以加该参数。我常用这种方式。
- dlg.children():获取 窗口/控件 子元素(直系),返回一个list;
- dlg.descendants() 查找所有符合条件的子孙元素,返回一个list,可以添加条件缩小范围;
app = Application("uia").connect(title="钉钉")
dlg = app.window(title="钉钉")
search = dlg.child_window(title="搜索", control_type="Edit")
search.click_input()
search.type_keys("Alan")
time.sleep(1)
SendKeys("{ENTER}")
chat = dlg.child_window(control_type = "Document",class_name="Chrome_RenderWidgetHostHWND")
chat.wait("ready",10,1)
time.sleep(2)
content = chat.descendants(control_type = "ListItem")
当然,APP、UI里面的层级定位、下标定位也是支持的;
层级定位:
new_dlg = dlg.child_window(best_match="Form")
new_dlg.child_window(best_match="Edit").set_text("123")
下标定位
# 定位 记事本 输入框,并输入内容,跟上图一样
dlg.children()[0].children()[0].type_keys("层级定位")
在关键字参数中,介绍了 control_type,在 inspect.exe 工具显示的是中文,但是不能使用,要使用英文类型,这个参数也常用,所以给大家列举一下常见的控件类型;
Button | 按钮控件 |
Calendar | 日历控件,例如日期选取器 |
CheckBox | 复选框控件 |
ComboBox | 组合框控件 |
Custom | 标识一个控件,该控件不属于已定义的控件类型之一 |
DataGrid | 数据网格控件 |
DataItem | 数据项控件 |
Document | 文档控件 |
Edit | 编辑控件,例如一个文本框 |
Group | 其他控件的容器的组控件 |
Header | 标题控件,它是一个用作信息的行和列标签的容器 |
HeaderItem | 标题项,它是信息的行或列的标签 |
Hyperlink | 超链接控件 |
Image | 图像控件 |
List | 列表控件,例如列表框 |
ListItem | 列表项控件,它是列表控件的子项 |
Menu | 菜单控件,例如应用程序窗口中的一个顶级菜单 |
MenuBar | 菜单栏控件,它通常包含一组顶级菜单 |
MenuItem | 菜单项控件 |
Pane | 格控件 |
ProgressBar | 进度条控件,该控件以可视方式指示一个长时间操作的进度 |
RadioButton | 单选按钮控件,这种选择机制只允许在组中选择一项 |
ScrollBar | 滚动条控件,例如应用程序窗口中的滚动条 |
Separator | 分隔符 |
Slider | 滑块控件 |
Spinner | 微调控件 |
SplitButton | 拆分按钮,该按钮将执行默认操作,并且还可以扩展到其他可能操作的列表。 |
StatusBar | 状态栏控件 |
Tab | 选项卡控件 |
TabItem | 选项卡项控件,它代表选项卡控件的一页 |
Table | 表 |
Text | 编辑控件,例如一个文本框或多格式文本框 |
Thumb | 标识滚动条中可拖到不同位置的控件 |
TitleBar | 窗口上的标题栏 |
ToolBar | 工具栏,例如应用程序窗口中包含一组命令按钮的控件 |
ToolTip | 工具提示控件,将指针移动到一个控件上方时或有时使用键盘 Tab 键切换到一个控件时将出现此信息窗口。 |
Tree | 树控件 |
TreeItem | 标识 TreeItem 控件中的一个节点 |
Window | 包含子对象的窗口框架 |
有关控件的属性及操作在下文一一讲解,谢谢大家的关注!