使用pywinauto进行UI自动化

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)

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值