大家好,最近我看了许多关于桌面应用程序的文章,却发现很多文章都缺乏结构和条理,这让我感到困惑。所以我想整理一份输出总结,让大家对桌面应用程序有更为清晰的认识。在本文中,我会讨论一些关于桌面应用程序的重要方面,与大家分享我的观点和经验,并提供一些实用的技巧和建议,希望可以为大家提供帮助。
目录
简介
- 功能介绍
- 基本原理
控件控制入门
- 控件分析与可用参数
- 控件延迟搜索机制
简介
功能介绍
在常规的模拟鼠标和键盘操作,我们一般使用pyautogui,uiautomation模块不仅能直接支持这些操作,还能通过控件定位方式直接定位到目标控件的位置,而不需要自己去获取对应坐标位置。uiautomation模块不仅支持任意坐标位置截图,还支持目标控件的截图,缺点在于截取产生的图片对象难以直接与PIL库配合,只能导出文件后让PIL图像处理库重新读取。对于能够获取到其ScrollItemPattern对象的控件还可以通过ScrollIntoView方法进行视图定位,与游览器的元素定位效果几乎一致。
在常规的热键功能,我们一般使用pynput实现,但现在有了uiautomation模块,热键注册会比pynput更简单功能更强。uiautomation模块所支持的剪切板操作的功能也远远超过常规的专门用于剪切板复制粘贴的库。更牛的是uiautomation模块能直接支持让你的python程序实现管理员提权。
基本上这个库的功能超过好几个专门针对某个功能的库。
基本原理
uiautomation模块项目地址:https://github.com/yinkaisheng/Python-UIAutomation-for-Windows
uiautomation是yinkaisheng业余时间开发一个模块。封装了微软UIAutomation API,支持自动化Win32,MFC,WPF,Modern UI(Metro UI), Qt, IE, Firefox( version<=56 or >=60), Chrome谷歌游览器和基于Electron开发的应用程序(加启动参数–force-renderer-accessibility也能支持UIAutomation被自动化).
uiautomation只支持Python 3版本,依赖comtypes和typing这两个包,但Python不要使用3.7.6和3.8.1这两个版本,comtypes在这两个版本中不能正常工作(issue)。
UIAutomation的工作原理:
UIAutomation操作程序时会给程序发送WM_GETOBJECT消息,如果程序处理WM_GETOBJECT消息,实现UI Automation Provider,并调用函数
UiaReturnRawElementProvider(HWND hwnd,WPARAM wparam,LPARAM lparam,IRawElementProviderSimple *el),此程序就支持UIAutomation。
IRawElementProviderSimple 就是 UI Automation Provider,包含了控件的各种信息,如Name,ClassName,ContorlType,坐标等。
UIAutomation 根据程序返回的 IRawElementProviderSimple,就能遍历程序的控件,得到控件各种属性,进行自动化操作。若程序没有处理WM_GETOBJECT或没有实现UIAutomation Provider,UIAutomation则无法识别这些程序内的控件,不支持自动化。
很多DirectUI程序没有实现UIAutomation Provider,所以不支持自动化。
关于各控件所支持的控件模式,可参考:
https://docs.microsoft.com/zh-cn/windows/win32/winauto/uiauto-controlpatternmapping
在使用uiautomation模块前需要先安装:
pip install uiautomation
安装后会在python安装目录下的Scripts目录下得到一个automation.py脚本,可以使用它来准确获取目标窗口的控件结构信息。automation.py脚本也可以从https://github.com/yinkaisheng/Python-UIAutomation-for-Windows/raw/master/automation.py下载。
当然使用windows自带的inspect.exe图形化工具来观察控件的树形结构更加,通过everything可以很快在系统中找到该工具。
控件控制入门:
控件分析与可用参数
控件可用参数说明:
- searchFromControl = None:从哪个控件开始查找,如果为None,从根控件Desktop开始查找
- searchDepth = 0xFFFFFFFF: 搜索深度
- searchInterval = SEARCH_INTERVAL:搜索间隔
- foundIndex = 1 :搜索到的满足搜索条件的控件索引,索引从1开始
- Name:控件名字
- SubName :控件部分名字
- RegexName:使用re.match匹配符合正则表达式的名字,Name,SubName,RegexName只能使用一个,不能同时使用
- ClassName :类名字
- AutomationId: 控件AutomationId
- ControlType :控件类型
- Depth:控件相对于searchFromControl的精确深度
- Compare:自定义比较函数function(control: Control, depth: int)->bool
searchDepth和Depth的区别:
searchDepth在指定的深度范围内(包括1~searchDepth层中的所有子孙控件)搜索第一个满足搜索条件的控件
Depth只在Depth所在的深度(如果Depth>1,排除1~searchDepth-1层中的所有子孙控件)搜索第一个满足搜索条件的控件
定位控件的工具:
具体工具包含:UISpy\AccEvent\WSEdit\SPYXX\Inspect等等
链接:https://pan.baidu.com/s/1reLxSzU9VYVNCBtTYDEYVw
提取码:qfdn
查看帮助信息:
>python D:Miniconda3Scriptsautomation.py -h
UIAutomation 2.0.15 (Python 3.7.4, 64 bit)
usage
-h show commandhelp
-t delay time, default 3 seconds, begin to enumerate after Value seconds, this must be an integer
you can delay a few seconds and make a window active so automation can enumerate the active window
-d enumerate tree depth, this must be an integer, if it is null, enumerate the whole tree
-r enumerate from root:Desktop window, if it is null, enumerate from foreground window
-f enumerate from focused control, if it is null, enumerate from foreground window
-c enumerate the control under cursor, if depth is <0, enumerate from its ancestor up to depth
-a show ancestors of the control under cursor
-n show control full name, if it is null, show first 30 characters of control's name in console,
always show full name in log file @AutomationLog.txt
-p show process id of controls
if UnicodeError or LookupError occurred when printing,
try to change the active code page of console window by using chcp or see the log file @AutomationLog.txt
chcp, get current active code page
chcp 936, set active code page to gbk
chcp 65001, set active code page to utf-8
examples:
automation.py -t3
automation.py -t3 -r -d1 -m -n
automation.py -c -t3
控件延迟搜索机制
底层COM对象方法:
⚠️ Control.Element返回IUIAutomation底层COM对象IUIAutomationElement, 基本上Control的所有属性或方法都是通过调用IUIAutomationElement COM API和Win32 API实现的。
延迟搜索控件:
当我们创建一个Control对象时,uiautomation并不会马上开始搜索控件,而是当使用其属性或方法,并且内部的Control.Element是None时uiautomation才开始搜索控件。如果在uiautomation.TIME_OUT_SECOND(默认为10)秒内找不到控件,uiautomation就会抛出一个LookupError异常。
也可以调用Control.Refind立马或重新开始搜索控件,例如:
edit = notepadWindow.EditControl()
edit.Refind()
True
但是当控件不存在时,则会报出错误。
为了避免函数最终抛出异常,可以调用Control.Exists(maxSearchSeconds, searchIntervalSeconds, printIfNotExist)检查目标控件是否存在:
edit = notepadWindow.EditControl()
edit.Exists()
True
Control.Refind和Control.Exists均会使Control.Element无效并触发重新搜索逻辑。
另一种检查目标控件是否存在的方法是auto.WaitForExist(control, timeout)。