赞
踩
操作控件需要以下几个步骤:
第一步 实例化要操作的进程:得到的app是Application对象。
第二步 选择窗口 :app.window('一个或多个筛选条件') 得到的窗口是WindowSpecification对象
第三步:基于WindowSpecification对象使用其方法再往下查找,定位到具体的控件
第四步:使用控件的方法属性执行我们需要的操作。
WindowSpecification源码中有一些自带的方法可以直接使用,也有注释说到:
WindowSpecification 说明
该对象中__getattribute__和__getitem__两个魔术方法,隐式地记录一些私有方法
我们可以继续往下一层一层的查找,下面一层一层的控件其实是各种各样的wrapper对象,wrapper有很多种是一系列对象,对象源码都在pywinauto源码的controls目录中
在我们安装好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)
- # 方式一 :不适用于窗口名为中文的
- 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')

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中找到了这个方法,非常有用
绝对坐标/相对坐标
- # 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操作集合
导入
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)
和控件自己的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} |
- 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()
写个循环,一直查询是否存在目标控件,若存在,则退出循环。
- ...
- while(True):
- if app.window(title=r'name',class_name='ClassName').exists():
- break
- wind_1.click()
注意,在查询的时候,最好不要用app[‘name’].exists()。这个匹配不精准,如下图中的最后一个句柄。这个句柄在开启程序后就一直存在,且由于我们要找的对话框title一样,所以我们在查找的时候需要加上class_name。
查询有个缺点就是如果一直没出现,就会一直等待。所以我们最好设置一个等待时间限。
使用模块中自带的wait函数就可以实现该功能了,了解更多Waiting for Long Operations。
- wind_1.Wait('enabled',timeout=300)
- wind_1.click()
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 输入按键(输入的插入到前面)
- check() # 勾选checkbox
- uncheck() # 不勾选checkbox
- is_checked() # 勾选返回true,未勾选返回false,不定返回None
- get_check_state() # 返回checkbox的勾选状态(0没勾选,1勾选,2不定)
- get_toggle_state() # 返回checkbox的勾选状态(0没勾选,1勾选),可用
需要先安装PIL模块
- # 对窗口/控件进行截图并保存
- # PIL is required for capture_as_image
- wind_1.capture_as_image().save(file_path)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。