原文地址:https://github.com/pywinauto/pywinauto/blob/master/docs/HowTo.txt
pywinauto是个非常不错的windows自动化程序,也就是完成类似案件精灵功能
下面我就翻译一下怎么用吧
========
如何使用
========
如何指定一个应用程序实例
---------------------------------------------Application()实例是你想要自动执行程序的重点,所以 Application需要连接到一个进程上。
有两种方法来生成 Application:
::
start(self, cmd_line, timeout=app_start_timeout) # instance method:
或者
::
connect(self, **kwargs) # instance method:
``start()`` 主要用在程序还未启动,用程序自己打开,下面是实例:
::
app = Application().start(r"c:\path\to\your\application -a -n -y --arguments")
timeout 参数是可选的,如果程序程序启动需要很长时间,那么有可能会用到它。
``connect()`` 用在程序已经启动的情况。连接接到已经启动的程序,下面是实例:
:process:程序的id(此处应该是指PID),比如
::
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")
提示:即将要启动的程序,可以使用connect*(),但不会提示超时和重连。
所以不是使用pywinauto模块启动的程序,通过sleep或一个循环等待来保证程序已经彻底启动是非常有必要。
如何指定程序的对话框
------------------------------------------如果连接的应用实例是一个对话框,需要被定义才能使用。
指定对话框有很多方法可以实现。最常见的方法是把对话框的标题作为组件(item)或属性(attribute)来使用,比如:
::
dlg = app.Notepad
或等价于
::
dlg = app['Notepad']
最简单的方法是调用top_window()`函数,比如
::
dlg = app.top_window()
这个方法可以返回程序Z顺序(Z-order)中最高的最上层的窗口。
注意:目前我还不能够确认一定能够返回你需要的窗口。返回一定会程序最上层的窗口,但也许不一定是Z-order里最高的。
如果上述方法部分满足你的需求,那么可以使用findwindows.find_windows()函数,参数传递与上面相同。
::
dlg = app.window(title_re="Page Setup", class_name="#32770")
最后介绍最灵活的控制方法
::
dialogs = app.windows()
这种方法将以列表(list)的形式返回程序中所有可见(visible)、允许(enabled)、上层(top level )窗口。
::
app.window(handle=win)
注意:如果对话框的标题(title )非常长,通过属性(attribute )来连接输入很麻烦,可以使用以下方法来使用更简单
::
app.window(title_re=".*Part of Title.*")
如何指定对话框中的控件
------------------------------------
有多种方法,最简单的是
::
app.dlg.control
app['dlg']['control']
上面的的第二种方法更适合在非英语的操作系统,你可以使用unicode字符,比如app[u'your dlg title'][u'your ctrl title']
每个控件可以使用以下方面来建立标识符:
- title标题
- friendly class友好类名- title + friendly class标题+友好类名
如果控件的text是空的(不包括非显示字符)或没有使用text。
替代方案是我们可以找控件离该控件最右上的控件,并且加上友好类名,列表可以表示为:
- friendly class
- closest text + friendly class
对话框中所有控件一旦被创建,可以用一个集合(set)来表示,我可以使用它来消除歧义。
使用`WindowSpecification.print_control_identifiers()`方法来实现,比如
::
app.YourDialog.print_control_identifiers()
实例输出的结果:
::Button - Paper (L1075, T394, R1411, B485)
'PaperGroupBox' 'Paper' 'GroupBox'
Static - Si&ze: (L1087, T420, R1141, B433)
'SizeStatic' 'Static' 'Size'
ComboBox - (L1159, T418, R1399, B439)
'ComboBox' 'SizeComboBox'
Static - &Source: (L1087, T454, R1141, B467)
'Source' 'Static' 'SourceStatic'
ComboBox - (L1159, T449, R1399, B470)
'ComboBox' 'SourceComboBox'
Button - Orientation (L1075, T493, R1171, B584)
'GroupBox' 'Orientation' 'OrientationGroupBox'
Button - P&ortrait (L1087, T514, R1165, B534)
'Portrait' 'RadioButton' 'PortraitRadioButton'
Button - L&andscape (L1087, T548, R1165, B568)
'RadioButton' 'LandscapeRadioButton' 'Landscape'
Button - Margins (inches) (L1183, T493, R1411, B584)
'Marginsinches' 'MarginsinchesGroupBox' 'GroupBox'
Static - &Left: (L1195, T519, R1243, B532)
'LeftStatic' 'Static' 'Left'
Edit - (L1243, T514, R1285, B534)
'Edit' 'LeftEdit'
Static - &Right: (L1309, T519, R1357, B532)
'Right' 'Static' 'RightStatic'
Edit - (L1357, T514, R1399, B534)
'Edit' 'RightEdit'
Static - &Top: (L1195, T550, R1243, B563)
'Top' 'Static' 'TopStatic'
Edit - (L1243, T548, R1285, B568)
'Edit' 'TopEdit'
Static - &Bottom: (L1309, T550, R1357, B563)
'BottomStatic' 'Static' 'Bottom'
Edit - (L1357, T548, R1399, B568)
'Edit' 'BottomEdit'
Static - &Header: (L1075, T600, R1119, B613)
'Header' 'Static' 'HeaderStatic'
Edit - (L1147, T599, R1408, B619)
'Edit' 'TopEdit'
Static - &Footer: (L1075, T631, R1119, B644)
'FooterStatic' 'Static' 'Footer'
Edit - (L1147, T630, R1408, B650)
'Edit' 'FooterEdit'
Button - OK (L1348, T664, R1423, B687)
'Button' 'OK' 'OKButton'
Button - Cancel (L1429, T664, R1504, B687)
'Cancel' 'Button' 'CancelButton'
Button - &Printer... (L1510, T664, R1585, B687)
'Button' 'Printer' 'PrinterButton'
Button - Preview (L1423, T394, R1585, B651)
'Preview' 'GroupBox' 'PreviewGroupBox'
Static - (L1458, T456, R1549, B586)
'PreviewStatic' 'Static'
Static - (L1549, T464, R1557, B594)
'PreviewStatic' 'Static'
Static - (L1466, T586, R1557, B594)
'Static' 'BottomStatic'
这个例子来自test_application.py文件
注意:使用该方法打印出的标识在整个进程中能够保证独一无二。
当有两个输入框(edit boxes),他们标识列表中都有"Edit",第一个可以使用"Edit", "Edit0"来指定,
但"Edit1"指的是第二个
注意:不一定非得精确指定!比如下面的一个实例
::
Button - Margins (inches) (L1183, T493, R1411, B584)
'Marginsinches' 'MarginsinchesGroupBox' 'GroupBox'
你一定不喜欢以下指定的方法
- ``GroupBox``-名字太普通,能代表任何group box
- ``Marginsinches`` and ``MarginsinchesGroupBox`` -这两个看上去不错,
如果把'inches' 省略就更好了。
这样!代码最好能够通过使用正确的表达式来匹配到对话框中所需控件。
举个例子,如果你通过调试器下断点就能看出用不同标识的区别了。
::
(Pdb) print app.PageSetup.Margins.window_text()
Margins (inches)
(Pdb) print app.PageSetup.MarginsGroupBox.window_text()
Margins (inches)
这样将输出错误信息。如果你想操作的对话框中一个控件和另一个很相似,比如有两个标识很相似,不小心的错误编码就会出错。
如何在非英语程序中使用pywinauto
------------------------------------------------------------------
你无法使用属性(attribute )去连接一个控件,因为python代码不支持unicode标识符,
所以要么使用组件(item ),要么要么显示调用window()
所以下面的情况
::
app.dialog_ident.control_ident.click()
你可以这样写
::
app['dialog_ident']['control_ident'].click()
或显示调用window()函数
::
app.window(title_re="NonAsciiCharacters").window(title="MoreNonAsciiCharacters").click()
实例可以查看``examples\misc_examples.py get_info()``
如何处理与不按常理响应的控件(比如OwnerDraw控件)
------------------------------------------------------------------------------------
有些控件(特别是指OwnerDraw控件)不按常理响应事件。
比如你打开HLP(help)文件,在Index Tab上(单击搜索按钮)将会显示一个listbox(列表)。
使用Spy或Winspector工具搜索这个,它确实是一个list box,但它是自绘(ownerdrawn)的。
这意味着软件的开发者已经告诉windows那些控件的显示将被重载,行为自定义。
这种情况下,就会造成那些字符串无法被返回。:-(.
那么这种情况会出现什么问题呢?
::
app.HelpTopics.ListBox.texts() # 1
app.HelpTopics.ListBox.select("ItemInList") # 2
1、会返回一个空字符串,这意味着pywinauto 将不能获取listbox中的字符串。
2、会返回IndexError错误,因为ListBox控件的select(string) 方法要获取项目(item )中的文字,组件的索引是能被选择的。
下面变通的方法可以用在这个控件
::
app.HelpTopics.ListBox.select(1)
这样将选择listbox中第二个项目(item ),由于不使用字符串所以运行正常。
不幸的是即使这样能够运行。软件的开发者也可以让控件不响应标准的事件(events )比如Select。
这种情况下,你只能通过键盘模拟TypeKeys()来在listbox 选择项目(item )。
这样就可以可以给控件发送键盘按键。比如选第三个项目(item )
::
app.Helptopics.ListBox1.type_keys("{HOME}{DOWN 2}{ENTER}")
- ``{HOME}`` will make sure that the first item is highlighted.
- ``{HOME}`` 保证第一个项目(item)
- ``{DOWN 2}`` will then move the highlight down two items
- ``{DOWN 2}`` 向下标记两个项目(item)
- ``{ENTER}`` will select the highlighted item
- ``{ENTER}`` 选择被标记的项目如果你的程序使用大量相同种类的控件,从ListBox继承一个新类会事半功倍,这些你编写的特殊程序将涉及一些额外知识。
比如在WinHelp例子中,在list view中每个被标记项目(item)的文本都会显示在Edit control 上面,所以可以从这个项目获取文本
::
# 打印list box中选择的项目
# (只要你没有在Edit control输入内容!)
print app.HelpTopics.Edit.texts()[1]
如何连接系统托盘(也被称作SysTray或通知区域)(译者注:win系统左下角的区域)
------------------------------------------------------------------------------------在系统时钟区域有些图标是正在运行的程序。此区域通常被称为“系统托盘”。
事实上,这个区域有很多不同的windows控件。包含图标的控件一般是toolbar。
它是Pager 控件的子类,在含有TrayNotifyWnd类的窗口内部。
他们是位于其他窗口中,包含了Shell_TrayWnd的,这些窗口其实是Explorer 实例的一部分。
幸运的是,我们不需要明白也能操作。:-).
最重要的事情是寻找"Explorer.exe"程序中包含 "Shell_TrayWnd"类的窗口,这些窗口中Toolbar控件的标题(title)是"Notification Area".。
下面是一种操作操作方法。
::
import pywinauto.application
app = pywinauto.application.Application().connect(path="explorer")
systray_icons = app.ShellTrayWnd.NotificationAreaToolbar
taskbar模块提供了连接到系统托盘的初步方法。
定义的变量如下:
:explorer_app: 定义了连接到explorer进程的.Application()对象。我们不一定要直接用它。
:TaskBar: 任务条(task bar)的句柄 (bar包含开始按钮、快捷启动图标、正在运行的任务等等)
the QuickLaunch icons, running tasks, etc
:StartButton: "开始点我" :-)我觉得你应该知道它!
:QuickLaunch: 包含快捷启动图标的Toolbar 。:SystemTray: 包含时钟和系统托盘图标的窗口。
:Clock: 时钟
:SystemTrayIcons: 指向系统托盘图标的toolbar。
这里提供两种点击系统托盘图标的方法:
:``ClickSystemTrayIcon(button)``: 可以使用此方法左键单击系统托盘内的可见图标。
我这里说可见图标是因为还有很多不可见图标是不能被点击的。Button参数可以是任意整数。
比如指定3那么就会找到第三个可见按钮并点击。
(错误的点击大部分情况不会提示,但这个方法在未来很可能会被转移或重命名)
:``RightClickSystemTrayIcon(button)``: 和`ClickSytemTrayIcon``相似但是是右键单击。
一般情况下,左/右键单击图标,将会显示一个弹出式菜单。需要注意到是这个弹出式菜单是被自动化运行程序的,
而不是explorer的一部分。
比如:
::
# 连接到outlook
outlook = Application.connect(path='outlook.exe')
# 单击outlook图标
taskbar.ClickSystemTrayIcon("Microsoft Outlook")
# 选择弹出式菜单的一个项目
outlook.PopupMenu.Menu().get_menu_path("Cancel Server Request")[0].click()