pywinauto简介
一、pywinauto简介
pywinauto官方文档链接如下。
https://pywinauto.readthedocs.io/en/latest/
https://www.kancloud.cn/gnefnuy/pywinauto_doc
- Dialog 是一个窗口,包含其他几个GUI元素/控件,如按钮,编辑框等。对话框不一定是主窗口。 主窗体顶部的消息框也是一个对话框。 主窗口也被pywinauto视为对话框。
- control 控件是层次结构的任何级别的GUI元素。 该定义包括窗口,按钮,编辑框,表格,表格单元格,工具栏等。
- Win32 API技术(pywinauto中的“win32”后端)为每个控件提供标识符。 这是一个名为handle的唯一整数.
- UI Automation API(pywinauto中的“uia”后端)可能不为每个GUI元素提供窗口handle。 “win32”后端看不到这样的元素。 但是
Inspect.exe
可以显示属性NativeWindowHandle
,如果它可用的话。
二、访问技术选择
pywinauto要操作应用,首先需要访问应用,主要有两种访问技术。WIN32访问技术支持MFC、VB6、VCL、简单WinForms控件开发的应用,MS UI Automation访问技术支持WinForms、WPS、QT5、WPF、Store apps、browsers等开发的应用。
分析一个应用软件可以用哪种访问技术,有如下常用工具:
1、SPY++,用于WIN32 API。当SPY++可以显示所有的控件时,访问技术应该选择"win32"
2、Inspect.exe:如果Inspect.exe的模式设置为UIA模式,可以比SPY++显示更多的控件,则访问技术应该选择"uia"
3、py_inspect:支持win32和uia两种访问技术,是SWAPY的替代。
https://github.com/pywinauto/py_inspect
4、UISPY:支持uia访问技术
5、SWAPY:只支持win32访问技术。https://github.com/pywinauto/SWAPY
下图是用py_inspect查看的secureCRT控件,可以看到如果选择uia格式,可以看到更多的控件,所以访问技术应该选择‘uia’
其它常用的
三、访问应用方法
你可以打开一个应用,或者连接一个已经存在的应用实例。都是通过Application对象完成的。它不是对subprocess.Popen的克隆,而是应用自动化的接入点。这里主要是限制自动化控制进程的范围。如一个程序有多个实例,自动化控制一个实例,而保证其他实例(进程)不受影响。主要有两种对象可以建立这种入口点Application() ,Desktop()。 Application的作用范围是一个进程,如一般的桌面应用程序都为此类。 Desktop的作用范围可以跨进程。主要用于像win10的计算器这样包含多个进程的程序。这种目前比较少见。
1、Application对象
1.1、打开一个应用
connect(self, **kwargs) # instance method:
timeout参数是可选的,只有在应用程序需要很长时间才能启动时才需要使用它。
from pywinauto.application import Application
app = Application(backend="uia").start('notepad.exe')
# describe the window inside Notepad.exe process
dlg_spec = app.UntitledNotepad
# wait till the window is really open
actionable_dlg = dlg_spec.wait('visible')
1.2、连接一个应用
connect(self, **kwargs) # instance method:
connect()
在要启动自动化应用程序时使用。 要指定已在运行的应用程序,您需要指定以下之一:
process:应用程序的进程ID,例如app = Application().connect(process=2341)
handle:应用程序窗口的窗口句柄,例如,app = Application().connect(handle=0x010f0c)
path:进程的可执行文件的路径(GetModuleFileNameEx用于查找每个进程的路径并与传入的值进行比较),例如:app = Application().connect(path=r"c:\windows\system32\notepad.exe")
或者指定窗口的参数的任意组合,这些都被传递给pywinauto.findwindows.find_elements()
函数。 例如
app = Application().connect(title_re=".*Notepad", class_name="Notepad")
2、Desktop对象
from subprocess import Popen
from pywinauto import Desktop
Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')
四、访问窗口方法
这是高级pywinauto API的核心概念。 您可以近似或更详细地描述任何窗口或控件,即使它尚不存在或已经关闭。 窗口规范还保留有关将用于获得真实窗口或控件的匹配/搜索算法的信息。
一个详细的窗口规范如下:
>>> dlg_spec = app.window(title='Untitled - Notepad')
>>> dlg_spec
<pywinauto.application.WindowSpecification object at 0x0568B790>
>>> dlg_spec.wrapper_object()
<pywinauto.controls.win32_controls.DialogWrapper object at 0x05639B70>
实际窗口查找由wrapper_object()
方法执行。 它返回实际现有窗口/控件的一些包装器或引发ElementNotFoundError
。 这个包装器可以通过发送动作或检索数据来处理窗口/控件。
但是Python可以隐藏这个wrapper_object()
调用,这样你就可以在程序中拥有更紧凑的代码。 以下描述完全相同:
dlg_spec.wrapper_object().minimize() # while debugging
dlg_spec.minimize() # in production
创建窗口规范有许多可能的标准。 下面只是几个例子。可以在pywinauto.findwindows.find_elements()
函数中找到可能的标准列表。
# 可以是多层次的
app.window(title_re='.* - Notepad$').window(class_name='Edit')
# 可以结合标准
dlg = Desktop(backend="uia").Calculator
dlg.window(auto_id='num8Button', control_type='Button')
1.通过窗体名确定窗体
dlg = app.Notepad
dlg = app['Notepad']
2、top_window()
这将返回具有应用程序顶级窗口的最高Z顺序的窗口。
3、通过多层次的描述指定一个窗口,如(a),或使用组合参数指定一个窗体如(b),下面两条语句确定的是同一个窗体
dlg = app.window(title_re="Page Setup").window(class_name="#32770")
dlg = app.window(title_re="Page Setup", class_name="#32770")
4、通过findwindows
pywinauto.findwindows.find_windows()
pywinauto.findwindows.find_elements()返回所有的已运行程序的win32_element_info.HwndElementInfo
5、dialogs = app.windows()
这将返回应用程序的所有可见,启用的顶级窗口的列表。 然后,您可以使用handleprops
模块中的一些方法选择所需的对话框。 一旦掌握了所需的句柄,就可以使用
app.window(handle=win)
注意: 如果对话框的标题很长 - 那么输入的属性访问可能会很长,在这种情况下通常更容易使用
app.window(title_re=".*Part of Title.*")
五、访问控件
1、属性解析魔法
Python通过动态解析对象属性简化了创建窗口规范。 但是一个属性名称与任何变量名称具有相同的限制:没有空格,逗号和其他特殊符号。 但幸运的是pywinauto使用“最佳匹配”算法来查找拼写错误和小变化。
app.UntitledNotepad # 相当于 app.window(best_match='UntitledNotepad')
通过类似字典的项目访问,可以使用Unicode字符和特殊符号。
app['Untitled - Notepad'] # 是相同的 app.window(best_match='Untitled - Notepad')
2、如何知道魔法属性名称
如何将“最佳匹配”附加到控件上有几个原则。 如果窗口规范接近其中一个名称,您将获得成功的名称匹配。
- 按标题(窗口文字,名称):
app.Properties.OK.click()
- 按标题和控件类型:
app.Properties.OKButton.click()
- 按控件类型和编号:
app.Properties.Button3.click()
(注意: Button0和Button1匹配相同的按钮,Button2是下一个,等等。) - 按左上角标签和控件类型:
app.OpenDialog.FileNameEdit.set_text("")
- 按控件类型和项目文本:
app.Properties.TabControlSharing.select("General")
通常并非所有这些匹配的名称都可以同时使用。 要检查指定对话框的这些名称,可以使用print_control_identifiers()
方法。 可能的“best_match”名称显示为树中每个控件的Python列表。 也可以从方法输出中复制更详细的窗口规范。 比方说 app.Properties.child_window(title="Contains:", auto_id="13087", control_type="Edit")
。
3、如何在对话框上指定控件
有许多方法可以指定控件,最简单的方法是
app.dlg.control app['dlg']['control']
第二个更适合非英语操作系统,你需要传递unicode字符串,例如 app [u'对话框标题'] [u'控件标题']
该代码根据以下内容为每个控件构建多个标识符:
- title
- friendly class
- title + friendly class
如果控件的标题文本为空(删除非char字符后),则不使用此文本。 相反,我们寻找控件上方和右侧最接近的标题文本。 并追加友好类。 所以列表变成了
- friendly class
- closest text + friendly class
一旦为对话框中的所有控件创建了一组标识符,我们就消除它们的歧义。
使用 WindowSpecification.print_control_identifiers() 方法
注意 通过此方法打印的标识符已经过使标识符唯一的过程。 因此,如果您有两个编辑框,则它们的标识符中都会列出“Edit”。 实际上,虽然第一个可以被称为“Edit”, “Edit0”, “Edit1”和第二个应该被称为“Edit2”
六、操作控件
https://pywinauto.readthedocs.io/en/latest/code/code.html#controls-reference
具体操作方法参考官方介绍,注意两点
1、对于同一个控件,用win32查出来的访问路径和用uia查询出来的访问路径不同
2、对于同一个控件,win32访问时可用的方法和用uia访问时可用的方法不同。
七、其他
1、如何将pywinauto与英语以外的应用程序语言一起使用
由于Python不支持代码中的unicode标识符,因此您无法使用属性访问来引用控件,因此您必须使用项访问权或对window()
进行显式调用。
所以不要写作
app.dialog_ident.control_ident.click()
你必须写
app['dialog_ident']['control_ident'].click()
或者明确地使用window()
app.window(title_re="非Ascii字符").window(title="非Ascii字符").click()
2、如何处理未按预期响应的控件(例如OwnerDraw控件)
某些控件(尤其是Ownerdrawn控件)不会按预期响应事件。 例如,如果您查看任何HLP文件并转到“索引”选项卡(单击“搜索”按钮),您将看到一个列表框。 在此运行Spy或Winspector将向您显示它确实是一个列表框 - 但它是ownerdrawn。 这意味着开发人员告诉Windows他们将覆盖项目的显示方式并自行完成。 在这种情况下,他们已经使它无法检索字符串:-(。
那导致什么问题呢?
app.HelpTopics.ListBox.texts() # 1 app.HelpTopics.ListBox.select("ItemInList") # 2
- 将返回一个空字符串列表,这一切都意味着pywinauto无法获取列表框中的字符串
- 这将因IndexError而失败,因为ListBox的select(string)方法在文本中查找项目以了解它应该选择的项目的索引。
以下解决方法将适用于此控件
app.HelpTopics.ListBox.select(1)
这将选择列表框中的第二项,因为它不是字符串查找,它可以正常工作。
不幸的是,即便这样也无济于事。 开发人员可以使控件不响应Select等标准事件。 在这种情况下,您可以在列表框中选择项目的唯一方法是使用TypeKeys()的键盘模拟。
这允许您将任何击键发送到控件。 所以要选择你要使用的第3个项目
app.Helptopics.ListBox1.type_keys("{HOME}{DOWN 2}{ENTER}")
{HOME}
将确保突出显示第一个项目。{DOWN 2}
然后将突出显示两个项目{ENTER}
将选择突出显示的项目
如果您的应用程序广泛使用类似的控件类型,那么您可以通过从ListBox派生新类来更轻松地使用它,这可以使用有关您的特定应用程序的额外知识。 例如,在WinHelp示例中,每次在列表视图中突出显示某个项目时,其文本都会插入到列表上方的Edit控件中,并且您可以从那里获取该项目的文本,例如:
# 打印列表框中当前所选项目的文本 # (只要你没有输入编辑控件!) print app.HelpTopics.Edit.texts()[1]
更多推荐
所有评论(0)