1.pywinauto
操作控件需要以下几个步骤:
第一步 实例化要操作的进程:得到的app是Application对象。
第二步 选择窗口 :app.window('一个或多个筛选条件') 得到的窗口是WindowSpecification对象
第三步:基于WindowSpecification对象使用其方法再往下查找,定位到具体的控件
第四步:使用控件的方法属性执行我们需要的操作。
WindowSpecification源码中有一些自带的方法可以直接使用,也有注释说到:
WindowSpecification 说明
该对象中__getattribute__和__getitem__两个魔术方法,隐式地记录一些私有方法
我们可以继续往下一层一层的查找,下面一层一层的控件其实是各种各样的wrapper对象,wrapper有很多种是一系列对象,对象源码都在pywinauto源码的controls目录中
1.1链接/打开应用
在我们安装好Pywinauto之后,首先要确定哪种可访问性技术(pywinauto的backend)可以用于我们的应用程序,在windows上受支持的辅助功能技术有两种:
-
Win32 API (backend="win32") 默认backend
-
MS UI Automation (backend="uia")
可以借助于GUI对象检查工具来确定程序到底适用哪种backend,常用的检查工具有Inspect.exe,Spy++ 等,如果Spy++可定位到的元素信息更多,则使用win32;如果inspect.exe定位到的元素更多,则使用uia
from pywinauto import application # 方式一:创建应用程序时可以,指定应用程序的合适的backend,start方法中指定启动的应用程序
app = application.Application(backend='uia').start('notepad.exe')
from pywinauto import application
# 方式二:查看要打开的程序进程号,通过process指定进程号连接
app = application.Application().connect(process=19035)
1.2窗口选择
# 方式一 :不适用于窗口名为中文的
wind_1 = app.窗口名
# 方式二 :窗口名可以为中文
wind_2 = app["窗口名"]
# 方式三 :窗口名可以为中文
app.window(class_name = ‘Notepad’) # 关键字 title, title_re,class_name_re等
# 案例使用:选择上面打开的计算器程序窗口
wind_calc = app['无标题 - 记事本']
以下为只支持窗口模式的控件
dlg.close() # 关闭界面
dlg.minimize() # 最小化界面
dlg.maximize() # 最大化界面
dlg.restore() # 将窗口恢复为正常大小,比如最小化的让他正常显示在桌面
dlg.get_show_state() # 正常0,最大化1,最小化2
dlg.exists(timeout=None, retry_interval=None) # 判断是否存在
#timeout:等待时间,一般默认5s
#retry_interval:timeout内重试时间
dlg.wait(wait_for, timeout=None, retry_interval=None) # 等待窗口处于特定状态
dlg.wait_not(wait_for_not, timeout=None, retry_interval=None) # 等待窗口不处于特定状态,即等待消失
# wait_for/wait_for_not:
# * 'exists' means that the window is a valid handle
# * 'visible' means that the window is not hidden
# * 'enabled' means that the window is not disabled
# * 'ready' means that the window is visible and enabled
# * 'active' means that the window is active
# timeout:等待多久
# retry_interval:timeout内重试时间
# eg: dlg.wait('ready')
1.3控件定位
元素定位工具
-
Inspect
-
ViewWizard
-
Spy++
层级查找控件的方法
window(**kwargs) # 用于窗口的查找
child_window(**kwargs) # 可以不管层级的找后代中某个符合条件的元素,最常用
parent() # 返回此元素的父元素,没有参数
children(**kwargs) # 返回符合条件的子元素列表,支持索引,是BaseWrapper对象(或子类)
iter_children(**kwargs) # 返回子元素的迭代器,是BaseWrapper对象(或子类)
descendants(**kwargs) # 返回符合条件的所有后代元素列表,是BaseWrapper对象(或子类)
iter_children(**kwargs) # 符合条件后代元素迭代器,是BaseWrapper对象(或子类)
可以通过print_control_identifiers()这个方法,来获取这个窗口下的直接子控件
app["窗口名"]["控件名"] # 基于title定位
app.window(class_name = ’Notepad’).window(class_name = ‘#32770’) # 层级定位
app.window(class_name = ‘Notepad’).child_window(class_name = ‘#32770’)
app_window.children()[1].children()[0].children()[0] .children()[2] #定位用户名输入框控件(序号从0开始查)
app.window("窗口名").window(class_name='ClassName', found_index=0)# 通过className定位,选择符合条件的第1个控件
可用查找条件关键字
Possible values are:
* **class_name** Elements with this window class
* **class_name_re** Elements whose class matches this regular expression
* **parent** Elements that are children of this
* **process** Elements running in this process
* **title** Elements with this text
* **title_re** Elements whose text matches this regular expression
* **top_level_only** Top level elements only (default=True)
* **visible_only** Visible elements only (default=True)
* **enabled_only** Enabled elements only (default=False)
* **best_match** Elements with a title similar to this
* **handle** The handle of the element to return
* **ctrl_index** The index of the child element to return
* **found_index** The index of the filtered out child element to return
* **predicate_func** A user provided hook for a custom element validation
* **active_only** Active elements only (default=False)
* **control_id** Elements with this control id
* **control_type** Elements with this control type (string; for UIAutomation elements)
* **auto_id** Elements with this automation id (for UIAutomation elements)
* **framework_id** Elements with this framework id (for UIAutomation elements)
* **backend** Back-end name to use while searching (default=None means current active backend)
例如
wind_1 = app["窗口名"]
# 按name值定位
wind_1.window(title="name").click()
# 通过calssName定位,单击符合此className的第一个控件
wind_1.window(class_name='className', found_index=0).click()
# 定位符合条件的控件的父元素,并左键双击
wind_1.window(title="name").parent().double_click_input()
#多个关键字组合定位
wind_1.child_window(title_re='name', class_name="Edit").click_input()
控件的常用属性
ctrl.children_texts() # 所有子控件的文字列表,对应inspect中Name字段
ctrl.window_text() # 控件的标题文字,对应inspect中Name字段
ctrl.class_name() # 控件的类名,对应inspect中ClassName字段,有些控件没有类名
ctrl.element_info.control_type # 控件类型,inspect界面LocalizedControlType字段的英文名
ctrl.is_child(parent) # ctrl是否是parent的子控件
ctrl.legacy_properties().get('Value') # 可以获取inspect界面LegacyIAccessible开头的一系列字段,在源码uiawraper.py中找到了这个方法,非常有用
按坐标定位控件
绝对坐标/相对坐标
1.4常用鼠标操作
# ctrl即定位到的控件
ctrl.click()# 左键单击
ctrl.click_input() # 左键单击
ctrl.right_click_input() # 鼠标单击
# 键盘输入,底层还是调用keyboard.send_keys
ctrl.type_keys(keys, pause = None, with_spaces = False,)
# keys:要输入的文字内容
# pause:每输入一个字符后等待时间,默认0.01就行
# with_spaces:是否保留keys中的所有空格,默认去除0
ctrl.double_click_input(button ="left", coords = (None, None)) # 左键双击
ctrl.press_mouse_input(coords = (None, None)) # 指定坐标按下左键,不传坐标默认左上角
ctrl.release_mouse_input(coords = (None, None)) # 指定坐标释放左键,不传坐标默认左上角
ctrl.move_mouse_input(coords=(0, 0)) # 将鼠标移动到指定坐标,不传坐标默认左上角
ctrl.drag_mouse_input(dst=(0, 0)) # 将ctrl拖动到dst,是press-move-release操作集合
pywinauto自带的鼠标操作有些时候并不能完全满足要求,可以调用mouse的方法
导入
from pywinauto import mouse
常见操作:
# 移动鼠标
mouse.move(coords=(x, y))
# 指定位置,鼠标左击
mouse.click(button='left', coords=(40, 40))
# 鼠标双击
mouse.double_click(button='left', coords=(140, 40))
# 将属性移动到(140,40)坐标处按下
mouse.press(button='left', coords=(140, 40))
# 将鼠标移动到(300,40)坐标处释放,
mouse.release(button='left', coords=(300, 40))
# 右键单击指定坐标
mouse.right_click(coords=(400, 400))
# 鼠标中键单击指定坐标(很少用的到)
mouse.wheel_click(coords=(400, 400))
# 滚动鼠标 wheel_dist指定鼠标滚轮滑动,正数往上,负数往下。
mouse.scroll(coords=(1200,300),wheel_dist=-3)
示例:
# 以控件中心为起点,滚动
def mouse_scroll(control, distance):
rect = control.rectangle()
cx = int((rect.left+rect.right)/2)
cy = int((rect.top + rect.bottom)/2)
mouse.scroll(coords=(cx, cy), wheel_dist=distance)
mouse_scroll(control=wind_1.window(title="name", distance=-5)
1.5常用键盘操作
和控件自己的type_keys方法效果一样,但是更快,那个是从前到后啪啪啪的输入,这个是一下就出来了那种
在发送文件和图片的时候可以使用键盘模块,复制粘贴,比啪啪啪输入路径再发送速度快多了
并且该模块可以适配很多表情等特殊符号
import keyboard
import io
for line in io.StringIO(msg):
keyboard.write(line.strip()) #
keyboard.send('ctrl+enter')
keyboard.write(chat_name)
keyboard.send('enter')
keyboard.send('ctrl+v')
想要通过pywinauto模拟操作键盘,需要重新导入库
from pywinauto.keyboard import send_keys
源码
def send_keys(keys,
pause=0.05,
with_spaces=False,
with_tabs=False,
with_newlines=False,
turn_off_numlock=True,
vk_packet=True):
"""Parse the keys and type them"""
keys = parse_keys(
keys, with_spaces, with_tabs, with_newlines,
vk_packet=vk_packet)
for k in keys:
k.run()
time.sleep(pause)
SendKeys = deprecated(send_keys)
示例
from pywinauto.keyboard import send_keys
from pywinauto import Application
import time
app = Application().start('notepad.exe')
# 通过支持的控件输入内容
app['无标题 - 记事本'].Edit.type_keys('测试')
time.sleep(2)
# 回车
send_keys('{ENTER}')
# F5
send_keys('{VK_F5}')
# ctrl+a
send_keys('^a')
# 也可以把多个键盘输入写在一起
send_keys('{ENTER}'
'{VK_F5}'
'^a'
)
以下是一些特殊键盘
按键名称 | 对应符号 |
SHIFT | + |
CTRL | ^ |
ALT | % |
SPACE | {SPACE} |
BACKSPACE | {BACKSPACE} {BS} or{BKSP} |
BREAK | {BREAK} |
CAPS LOCK | {CAPSLOCK} |
DEL or DELETE | {DELETE} or {DEL} |
DOWN ARROW | {DOWN} |
END | {END} |
ENTER | {ENTER} or ~ |
ESC | {ESC} |
HELP | {HELP} |
HOME | {HOME} |
INS or INSERT | {INSERT} or {INS} |
LEFT ARROW | {LEFT} |
NUM LOCK | {NUMLOCK} |
PAGE DOWN | {PGDN} |
PAGE UP | {PGUP} |
PRINT SCREEN | {PRTSC} |
RIGHT ARROW | {RIGHT} |
SCROLL LOCK | {SCROLLLOCK} |
TAB | {TAB} |
UP ARROW | {UP} |
+ | {ADD} |
- | {SUBTRACT} |
* | {MULTIPLY} |
/ | {DIVIDE} |
1.6等待
隐式等待
wait(wait_for, timeout = None, retry_interval = None) # visible,ready: visible + enable
wait_not(wait_for_not,timeout = None,retry_interval = None)
1、等待法。
先预估一个转换所需的最长时间,保证此时操作已经完成,然后让程序等待这么长时间后再进行下一步。
import time
...
time.sleep(100)
ctrl.click()
2、 查询法。
写个循环,一直查询是否存在目标控件,若存在,则退出循环。
...
while(True):
if app.window(title=r'name',class_name='ClassName').exists():
break
wind_1.click()
注意,在查询的时候,最好不要用app[‘name’].exists()。这个匹配不精准,如下图中的最后一个句柄。这个句柄在开启程序后就一直存在,且由于我们要找的对话框title一样,所以我们在查找的时候需要加上class_name。
3、查询等待法。
查询有个缺点就是如果一直没出现,就会一直等待。所以我们最好设置一个等待时间限。
使用模块中自带的wait函数就可以实现该功能了,了解更多Waiting for Long Operations。
wind_1.Wait('enabled',timeout=300)
wind_1.click()
1.7菜单栏
app['窗口名或类名'].menu_select(Edit -> Replace)
示例
from pywinauto import application
app = application.Application(backend="win32") # 默认为win32,设置成‘uia’出错
app.start(r"notepad.exe")
app['Notepad'].wait('ready') # 'Notepad'为类名,用标题名“无标题 - 记事本”也可以,app.UntitledNotepad 也可以
app['Notepad'].menu_select("文件->页面设置...") # 不用加“.click()”,已经点击,“...”不能少
app['页面设置']['ComboBox1'].select(4) # ComboBox1是第一个,ComboBox2是第二个,select从0开始
app['页面设置']['ComboBox1'].select("A5") #直接选择里面的项
app['页面设置']['取消'].click() # 按钮点击
app['页面设置']['Edit3'].set_edit_text("68") # Edit 置文本
app['页面设置']['Edit2'].type_keys("86") # Edit 输入按键(输入的插入到前面)
1.8勾选
check() # 勾选checkbox
uncheck() # 不勾选checkbox
is_checked() # 勾选返回true,未勾选返回false,不定返回None
get_check_state() # 返回checkbox的勾选状态(0没勾选,1勾选,2不定)
get_toggle_state() # 返回checkbox的勾选状态(0没勾选,1勾选),可用
1.9对控件截图保存
需要先安装PIL模块
# 对窗口/控件进行截图并保存
# PIL is required for capture_as_image
wind_1.capture_as_image().save(file_path)