pywinauto使用笔记

本文详细介绍了如何使用Python库pywinauto进行Windows应用程序的自动化操作,包括启动应用、定位窗口和控件、鼠标操作以及处理滚动条和控件存在的问题,为开发者提供了实用的技巧和示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

打算使用代码段+有序列表解释的形式记录使用细节,目的是记录下来一套之后可以复刻的自动化流程

pywinauto官方文档Contents — pywinauto 0.6.8 documentation


application

from pywinauto.application import Application
app = Application(backend="uia").start("notepad.exe")
# 附带命令行参数的指令
app = Application(backend='uia').start('notepad.exe "C:\\path\\to\\your\\file.txt"')
  1. 使用application来定位要自动化操作的应用程序
  2. Application()创建一个Application对象,设置backend来选择uia技术或win32技术,win32 api支持(MFC、VB6、VCL、简单的WinForms控件和大多数旧的遗留应用程序),uia支持(WinForms,WPF,应用商店,Qt5,浏览器)
  3. Application().start()可以打开参数指定的应用程序并返回一个Application对象,参数可以传绝对地址,相对地址(登记在环境变量中),或者一个附带命令行参数的指令

window specification

from pywinauto import mouse

dlg = app.window(title_re="[\s\S]*Microsoft​ Edge[\s\S]*")

# 打印控件树
dlg.print_control_identifiers()

# 方法1 
new_page_spec = dlg.child_window(title_re="[\s\S]*新建标签页[\s\S]*", control_type="Button")
# 方法2
new_page_spec = dlg.chile_window(auto_id="view_29")

# 方法1 能确定大致位置后,可以用来点击难定位的控件
rect = new_page_spec.recangle()
center_x = (rect.left + rect.right) // 2
center_y = (rect.top + rect.bottom) // 2
mouse.move(coorders=(center_x, center_y))
mouse.click(coorders=(center_x, center_y))
# 方法2
new_page_spec.click_input()
  1.  window()定位窗口 用title或者title_re找到窗口就好,window()返回WindowSpecification对象
  2. child_window用来找窗口下具体的控件,控件的定位方法可以用title_re, control_type, auto_id, class_name, found_index等来定位;其中found_index是当匹配到多个控件时决定 所选控件的索引
  3. auto_id可以精准定位到控件,而且相对稳定,长期内不会变化,但是有些控件没有auto_id(AutomationId)
  4. 也可以引用pywinauto.mouse控制鼠标对特定坐标进行点击
  5. 知晓桌面程序内部的控件信息可以使用pywinauto自带的方法(但是打印出来的信息有限,而且较深的控件树可能没办法全部展示到输出中,不知道怎么用比较好);win自带的Inspect.exe是windows SDK提供的工具之一,可以到Windows SDK - Windows app development | Microsoft Developer这个网址,只勾选"Debugging Tools for Windows"来下载,优点是可以像F12一样用鼠标来直接将眼见的某一控件在Inspect中从控件树里定位出来(跟踪焦点)。

watch focus/watch cursor

  1. 跟踪焦点)watch focus ,图中第一个红框标记出来的虚线正方形图标,控制跟踪焦点功能的开关,打开以后tree view和data view会根据鼠标点击的焦点切换显示对象。
  2. 跟踪光标)watch cursor,图中第二个红框标记出来的鼠标图标,控制跟踪光标的开关,打开以后tree view和data view根据鼠标停留的位置切换显示对象
  3. 跟踪光标不一定能准确地定位到想要的控件,比如期望的控件是按钮,实际上定位的是按钮上的文本(不过点击操作的话大概是一样的?实际没试过)。
  4. 可以通过关闭跟踪功能来锁住想看的控件信息

wrapper

spec.wrapper_object().minimize() # while debugging
spec.minimize() # in production

none_spec=app.child_window(title='我不存在') #不会报错
none_spec.wrapper_object() #ElementNotFound

# 常用操作
spec.click() #点击 页面上可能看不到该控件 鼠标不会移动到控件上面 无法触发鼠标相关ui
spec.click_input() #点击 鼠标瞬移到控件上并点击
rect = spec.rectangle() #获取矩形信息 包括rect.left rect.right rect.top rect.bottom在内的绝对坐标 以屏幕像素为单位
spec.minimize() #最小化
spec.restore() #将窗口置顶
spec.maximize() #最大化窗口
spec.exists() #判断控件是否存在 十分好用
spec.is_enabled() #看控件是否启用 可以用在分页表格,不断点击下一页直到按钮不可用(即全部点完)
spec.window_text() #看控件标题 可以用在时间等可以用正则确定的控件上,定位后取页面上的实际数值
  1. window()和child_window() 返回的是WindowSpecification对象
  2. WindowSpecification需要通过wrapper_object()来操作实际的窗口(或者控件),该方法返回一个wrapper对象
  3. python允许省略wrapper_object()的调用,所以上述1,2行效果是一样的
  4. 当获取一个页面上不存在的控件时,child_window()不会报错,wrapper_object()会报ElementNotFound错误

control_type

UIA后端常用控件类型
control_type参数值中文名Inspect内部显示为
Button按钮UIA_ButtonControlTypeId
Text文本UIA_TextControlTypeId
List列表UIA_ListControlTypeId
ListItem列表项UIA_ListItemControlTypeId 
Hyperlink链接UIA_HyperlinkControlTypeId
Menu菜单UIA_MenuControlTypeId
RadioButton单选按钮UIA_RadioButtonControlTypeId
Edit编辑UIA_EditControlTypeId

遇到问题及使用技巧

def getMidPoint(rect):
    return (rect.left + rect.right)//2, (rect.top + rect.bottom)//2

# 4 点击溢出ListItem
# spec为带滚轮的List控件
def getMidPoint(rect):
    return (rect.right+rect.left)//2, (rect.top+rect.bottom)//2

item = spec.child_window(title=title, control_type='ListItem')
spec_rect = spec.rectangle()
if item.exists():
   rect = item.rectangle()
   while rect.left == 0: # 当ListItem在窗口中溢出时,获取到的rectangle为(0,0,0,0)
       mouse.move(coords=(spec_rect.right - 4, spec_rect.bottom - 4))
       mouse.click(coords=(spec_rect.right - 4, spec_rect.bottom - 4)) # 点击滚动槽向下滚动
       item = spec.child_window(title=title, control_type='ListItem')
       rect = item.rectangle()

   mouse.move(getMidPoint(rect)) # while循环结束 鼠标会移动到溢出ListItem上


# 5
# 5-2
for i in range(n):
    item = spec.child_window(title="重复窗口名", found_index=i)
    spec_rect = spec.rectangle()
    rect = item.rectangle()
    while rect.left == 0:
        mouse.move(coords=getMidPoint(spec_rect))
        mouse.scroll(coords=getMidPoint(spec_rect), wheel_dist=-1) # -1是向下滚动滚轮一次 比点击滚动槽稳定一些
        item = spec.child_window(title="重复窗口名", found_index=i) # ElementNotFoundError
        rect = item.rectangle()
    item.click_input()

#5-4
for i in range(10):
    print(f"{i} {spec.child_window(title="重复窗口名", control_type='ListItem', found_index=i).exists()}")
# print: 
# 0 True
# 1 True
# 2 True
# 3 True
# 4 True
# 5 True
# 6 True
# 7 False
# 8 False
# 9 False

#5-5
for i in range(n):
    item = spec.child_window(title="重复窗口名", found_index=i)
    spec_rect = spec.rectangle()
    rect = item.rectangle()
    scroll = rect.left == 0
    while scroll:
        mouse.move(coords=getMidPoint(spec_rect))
        mouse.scroll(coords=getMidPoint(spec_rect), wheel_dist=-1)
        rect = item.rectangle()
        # 实际子窗口可能有不存在,或者存在但是rect.left==0 需要考虑两种需要滚动的情况
        # 还要考虑到子窗口完全显示出来以后再点击,否则点击的位置可能在spec窗口之外
        scroll = (not spec.child_window(title="重复窗口名", found_index=i).exists()
            or rect.left == 0 or rect.bottom > spec_rect.bottom)
    mouse.move(coords=getMidPoint(rect))
    mouse.click(button='left', coords=getMidPoint(rect))

WindowSpecification.child_window()不只能搜索到当前对象的直接子层的对象,也能搜索到更深层次的对象。在实际使用时可能会遇到各种各样的问题,当无法搜索到想要定位的对象时,需要检查自己是否有如下问题:

  1. 名称是否写对 使用title参数时,要求控件的Name要和参数值完全匹配,当识别不到相应控件时,检查控件Name前后是否包含空格字符。(建议使用title时直接从Inspect.exe中复制Name值到代码中)
  2. 匹配到多个对象 当匹配到多个对象的时候,直接执行操作会报错(比如There are 2 elements that match the criteria
  3. 搜索条件太宽泛时会报错pywinauto.timings.TimeoutError
  4. 点击溢出ListItem)带有滚动条的List中,所有ListItem都在控件树中,即item_spec.exists() == True,但是溢出在窗口外的ListItem,无法使用item_spec.click_input()点击;可以使用rectangle()来判断ListItem是否溢出,存在但页面上找不到的元素rectangle()会返回(0,0,0,0),如果ListItem确定溢出则想办法向下滚动;对于带有滚动条的窗口,可以通过点击滚动槽的方式将窗口向下滚动(主窗口可以直接用pyautogui键入space);实在滚动不了的窗口就考虑用click()直接点击,不移动鼠标,缺点是需要相关鼠标ui时无法触发。
  5. 带有滚动条的窗口中点击满足同一规则的多个子窗口的问题
    1. 需求:我希望在一个List中,依次点击同一类的List,由于List过长,所以我需要边检查某个ListItem是否存在边向下滚动滚动条
    2. 想法:通过found_index=None来访问子窗口,并捕获There are n elements that match the criteria错误,使用re库来获得重复的子窗口数n(貌似pywinauto没有提供直接获取重复元素数的方法);遍历i从0到n-1,访问found_index=i的子窗口;在当某一子窗口的rectangle().left==0时,说明子窗口不在当前窗口中,需要向下滚动滚动条,并重新获取这个子窗口
    3. 问题:当重复地给item赋值时,再次执行的child_window因为滚动过窗口(可能导致了控件树变化),无法识别出新显现出的子窗口。比如捕获到There are 7 elements, 但窗口只放得下前3个子窗口,当滚动过窗口后,再次执行chile_window则只能匹配到这3个窗口,当调用child_window(title='重复窗口名', found_index=3)即第四个子窗口时,会报loading:found_index is specified as 3, but 3 window/s found错误
    4. 验证:在没有滚动滚动条,即spec窗口初次加载时,对7个子窗口进行存在验证,发现此时全部子窗口都是存在的,区别只是rectangle() == (0,0,0,0)
    5. 实操:避免对item重复赋值,向下滚动并对item调用rectangle()来判断子窗口是否出现
  6. 使用aa = ord('从inspect Name字段复制下来的某特殊字符')来查看特殊字符的编码,并用unicode格式输出:f"U{aa:04X}"; 得到Unicode编码以后,用title='\uxxxx'即可找到对应特殊字符的控件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值