【pywinauto】简单的UI自动化入门(基于Windows) 第二篇

本文详细介绍使用Pywinauto库进行自动化测试的方法,包括窗口实例化、对话框操作及控件交互技巧。通过示例代码讲解如何利用句柄、标题和属性选择目标窗口,并介绍print_control_identifiers()方法来获取窗口结构,便于精准控制控件。

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

这个教程现在来看还是给自己写的,防止以后阿尔兹海默症以后忘了,

所以会不断的引用官方文档和别人的博客,

其实主要还是我怎么写过文档没什么头绪和逻辑,只能根据从上往下的顺序讲了;

其实我在想我这样写文档我干嘛不把中文丢到git上,反正git上太监这么久了

 


之前写了实例化的窗口怎么连接,连接上以后就可以看看程序了,

首先可以把主要经常用到的窗口定个名字:

from pywinauto import Application

app = Application().start('TestWinForm.exe')
dlg_test = app[r'测试窗体']

这里的写法 dlg_test = app[r‘测试窗体’],看看官方文档:

Once the application instance knows what application it is connected to a dialog to work on needs to be specified.

There are many different ways of doing this. The most common will be using item or attribute access to select a dialog based on it’s title. e.g

一旦应用程序实例知道它连接到哪个应用程序,就可以开始指定要处理的dialog。

有很多方法可以,最常见的还是直接用名字访问:

dlg = app.Notepad

or equivalently  这里上下是等效的

dlg = app['Notepad']

The next easiest method is to ask for the top_window() e.g. 

另一个简单方法就是请求 top_window(),

Note: This is currently fairly untested so I am not sure it will return the correct window. It will definitely be a top level window of the application - it just might not be the one highest in the Z-Order.

作者这里表示这个没怎么测试过但是给你返回的肯定是z-order最高的

If this is not enough control then you can use the same parameters as can be passed to findwindows.find_windows() e.g.

如果都不行建议还是用 findwindows.find_windows()


top_window()能够返回常见的最顶部窗口,但是不是一定的,下面这篇关于z-order结构的介绍文章,写的特别好:

https://blog.youkuaiyun.com/baidu_34331290/article/details/88053650

原文地址


下面就是最常用的用法,声明了这个窗口

dlg = app.window(title_re="Page Setup", class_name="#32770")

Finally to have the most control you can use

dialogs = app.windows()

这里看返回的结果

<class 'pywinauto.application.WindowSpecification'>
<pywinauto.application.WindowSpecification object at 0x0550C5F0>

 

this will return a list of all the visible, enabled, top level windows of the application. You can then use some of the methods in handleprops module select the dialog you want. Once you have the handle you need then use

这里会返回一系列的值,还有程序当前顶层的窗体,如果你想对里面的控件/结构做什么,就需要用到handleprops,

只要你有了句柄你就可以为所欲为

这里其实作者讲了pywinauto的核心理念,就是对传递的抓取,这其中的原理我还不是很清楚,以后慢慢讲

下面就是依据句柄对某样东西进行直接绑定<-绑定这个词真好用,以后都这么说了

app.window(handle=win)

Note: If the title of the dialog is very long - then attribute access might be very long to type, in those cases it is usually easier to use

下方是一个使用的方法,通过title绑定,后面加上_re指使用正则规则,如果物件的名称过长,作者推荐是使用正则而不是全贴上去

app.window(title_re=".*Part of Title.*")

其实这里捆绑物件的方法非常多,我举几个例子:

login_dlg['Edit2'].set_text('admin')
login_dlg.Edit2.set_text('admin')
login_dlg.child_window(handle=0x00010DE0).set_text('admin')
login_dlg.child_window(auto_id='txtUserName', control_type="Edit").set_text('admin')

从准确和方便的角度来看最后一种是比较好的,但是如果是表格,多输入框这种情况,第二种又比较好。

这里看原文:

如何准确的去控制一个物件(Dialog)

How to specify a control on a dialog

There are a number of ways to specify a control, the simplest are

app.dlg.control
app['dlg']['control']

The 2nd is better for non English OS’s where you need to pass unicode strings e.g.

下面这种情况要特别注意,在非英语环境下注意unicode的问题

 app[u'your dlg title'][u'your ctrl title']

那么如何知道一个程序的具体结构呢?

网上一般推荐使用SPYXX,Inspect,UISpy这几样,

我觉得比较好用的是SPYXX

但是最好用的一个方法还是这个↓:(这里很重要)

app.YourDialog.print_control_identifiers()

def print_control_identifiers(self, depth=None, filename=None):

 

这个代码帮助你查询了窗体的结构,而且帮你给每个控件构建了多个标识符:

 

分别是:

title
friendly class
title + friendly class

如果控件的title文本为空(删除非char字符后),则不使用这个样式。 而是会寻找控件上方和右侧最接近的标题文本。 并附上class name。 所以样式变成了:

friendly class
closest text + friendly class

作者在这里说一旦创建了所有控件的标识符,那么久会消除所有不确定性(我没看懂什么意思)

这个方法完整引用为:WindowSpecification.print_control_identifiers() 

e.g.举例:

app.YourDialog.print_control_identifiers()

Sample output

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'

This example has been taken from test_application.py

Note The identifiers printed by this method have been run through the process that makes the identifier unique. So if you have two edit boxes, they will both have “Edit” listed in their identifiers. In reality though the first one can be refered to as “Edit”, “Edit0”, “Edit1” and the 2nd should be refered to as “Edit2”

上面是一个print_control_identifiers() 方法的举例,经过了唯一化(这个唯一化我也想要拥有),输出出来是这种效果,

实际上,如果您有两个编辑框,则它们的标识符中都会列出“Edit”。实际上,虽然第一个可以被称为“Edit”,“Edit0”,“Edit1”,

第二个应该被称为“Edit2”

事实确实是如此

实际上当时我自己摸索的时候情况是这样的↓

对我自己的测试窗口使用这个方法:

输出为:

WindowsForms10.Window.8.app.0.141b42a_r9_ad1 - '测试窗体'    (L33, T33, R843, B433)
['测试窗体', 'WindowsForms10.Window.8.app.0.141b42a_r9_ad1', '测试窗体WindowsForms10.Window.8.app.0.141b42a_r9_ad1']
child_window(title="测试窗体", auto_id="Form1", control_type="TestWinForm.Form1")
   | 
   | Edit - ''    (L383, T239, R478, B265)
   | ['中文按钮1Edit', 'Edit', 'Edit0', 'Edit1', '中文按钮1Edit0', '中文按钮1Edit1']
   | child_window(auto_id="textBox22", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L715, T209, R810, B235)
   | ['测试窗体Edit', 'Edit2', '测试窗体Edit0', '测试窗体Edit1']
   | child_window(auto_id="textBox21", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L606, T209, R701, B235)
   | ['中文按钮1Edit2', 'Edit3']
   | child_window(auto_id="textBox20", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L495, T209, R590, B235)
   | ['中文按钮1Edit3', 'Edit4']
   | child_window(auto_id="textBox19", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L383, T209, R478, B235)
   | ['中文按钮1Edit4', 'Edit5']
   | child_window(auto_id="textBox18", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L715, T178, R810, B204)
   | ['测试窗体Edit2', 'Edit6']
   | child_window(auto_id="textBox17", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L606, T178, R701, B204)
   | ['中文按钮1Edit5', 'Edit7']
   | child_window(auto_id="textBox16", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L495, T178, R590, B204)
   | ['中文按钮1Edit6', 'Edit8']
   | child_window(auto_id="textBox15", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L383, T178, R478, B204)
   | ['中文按钮1Edit7', 'Edit9']
   | child_window(auto_id="textBox14", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L715, T146, R810, B172)
   | ['测试窗体Edit3', 'Edit10']
   | child_window(auto_id="textBox13", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L606, T146, R701, B172)
   | ['中文按钮1Edit8', 'Edit11']
   | child_window(auto_id="textBox12", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L495, T146, R590, B172)
   | ['中文按钮1Edit9', 'Edit12']
   | child_window(auto_id="textBox11", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L383, T146, R478, B172)
   | ['中文按钮1Edit10', 'Edit13']
   | child_window(auto_id="textBox10", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L715, T115, R810, B141)
   | ['测试窗体Edit4', 'Edit14']
   | child_window(auto_id="textBox9", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L606, T115, R701, B141)
   | ['中文按钮1Edit11', 'Edit15']
   | child_window(auto_id="textBox8", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L495, T115, R590, B141)
   | ['中文按钮1Edit12', 'Edit16']
   | child_window(auto_id="textBox7", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L383, T115, R478, B141)
   | ['中文按钮1Edit13', 'Edit17']
   | child_window(auto_id="textBox6", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L715, T84, R810, B110)
   | ['测试窗体Edit5', 'Edit18']
   | child_window(auto_id="textBox5", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L606, T84, R701, B110)
   | ['测试窗体Edit6', 'Edit19']
   | child_window(auto_id="textBox4", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L495, T84, R590, B110)
   | ['测试窗体Edit7', 'Edit20']
   | child_window(auto_id="textBox3", control_type="System.Windows.Forms.TextBox")
   | 
   | Edit - ''    (L383, T84, R478, B110)
   | ['测试窗体Edit8', 'Edit21']
   | child_window(auto_id="textBox2", control_type="System.Windows.Forms.TextBox")
   | 
   | ListView - ''    (L225, T153, R359, B383)
   | ['ListView', '中文按钮1ListView']
   | child_window(auto_id="listView1", control_type="System.Windows.Forms.ListView")
   | 
   | Edit - ''    (L54, T84, R359, B110)
   | ['测试窗体Edit9', 'Edit22']
   | child_window(auto_id="textBox1", control_type="System.Windows.Forms.TextBox")
   | 
   | ListBox - ''    (L54, T153, R188, B383)
   | ['button1ListBox', 'ListBox']
   | child_window(auto_id="listBox1", control_type="System.Windows.Forms.ListBox")
   | 
   | Button - '中文按钮1'    (L225, T115, R358, B146)
   | ['中文按钮1', 'Button', '中文按钮1Button', 'Button0', 'Button1']
   | child_window(title="中文按钮1", auto_id="button2", control_type="System.Windows.Forms.Button")
   | 
   | Button - 'button1'    (L54, T115, R187, B146)
   | ['button1', 'Button2', 'button1Button']
   | child_window(title="button1", auto_id="button1", control_type="System.Windows.Forms.Button")

如果你的程序层级比较多,建议使用

print_control_identifiers(self, depth=None, filename=None)中的depth进行层数的限制,不然很容易一查就几万行,filename可以直接将结果保存为文件。

今天先写到这里,如果说print_control_identifiers的输出结果非常奇怪或是少,那么很可能就是

backend的问题,关于win32和uia的问题下次再讲

先干活了

<think>好的,用户现在让我介绍一下Python结合Pywinauto自动化桌面软件表单填写。之前他已经询问过自动填写桌面软件表单的通用方案,我给出了工具选择、实现逻辑、代码示例等内容,现在他可能对其中提到的Python库Pywinauto特别感兴趣,想要更深入的了解。 首先,我需要确定用户的使用场景。他可能是开发者或者自动化测试人员,希望用Python来实现自动化,因为Python有广泛的库支持和社区资源。Pywinauto是一个专门用于Windows GUI自动化的库,所以用户可能需要在Windows环境下操作标准控件,比如传统的Win32应用或者现代的UWP应用。 用户可能的需求包括:如何安装配置Pywinauto,如何定位窗口和控件,如何操作这些控件(输入文本、点击按钮等),以及如何处理可能遇到的问题,比如控件识别困难或者执行速度问题。此外,用户可能希望看到具体的代码示例,以便快速上手。 接下来,我需要整理Pywinauto的主要功能和优势。比如跨控件类型支持、面向对象的API、与Python生态的集成,比如结合pandas处理数据,或者使用OpenCV处理图像识别。同时,也要提到它的局限性,比如主要支持Windows,对非标准控件可能需要辅助工具,或者处理速度的问题。 用户可能没有说出来的深层需求可能包括:如何处理复杂的表单,比如动态加载的控件,或者如何集成到现有的自动化流程中。另外,用户可能关心如何调试脚本,比如使用print_control_identifiers()来查看控件结构,或者如何处理弹窗和异常情况。 我需要确保回答结构清晰,分点介绍,包括工具简介、核心功能、使用步骤、代码示例、优缺点以及常见问题解决。可能还需要对比其他工具,比如AutoIt,突出Python和Pywinauto的优势,比如灵活性和扩展性。 最后,要检查是否有遗漏的重要信息,比如安装命令、实际应用场景(如企业级应用、批量处理),以及进阶技巧,比如数据驱动测试或者日志记录。确保用户能够根据指南开始使用Pywinauto,并在遇到问题时知道如何解决。</think>以下是针对 **Python + Pywinauto** 的详细介绍,涵盖核心功能、使用方法和实际应用场景: --- ### **1. Pywinauto 是什么?** - **定位**:一个纯 Python 库,专为 Windows 桌面应用自动化设计。 - **特点**: - 支持 Win32(如记事本、计算器)、MFC、WPF、UWP 等控件。 - 提供面向对象的 API,可模拟键盘、鼠标操作。 - 无需依赖其他框架,直接通过控件属性(如标题、类名)定位元素。 -- ### **2. 核心功能** | 功能 | 示例代码/说明 | |--------| | **启动应用** | `app = Application().start("notepad.exe")` | | **连接已运行应用** | `app = Application().connect(title="无标题 - 记事本")` | | **定位控件** | `app.窗口标题.控件类名(控件标题).click()` | | **输入文本** | `app.窗口.edit.type_keys("Hello Pywinauto!")` | | **点击按钮** | `app.窗口.button.click()` | | **读取控件内容** | `text = app.窗口.edit.window_text()` | | **处理菜单** | `app.窗口.menu_item("文件->另存为").click()` | --- ### **3. 快速入门步骤** #### **(1) 安装 Pywinauto** ```bash pip install pywinauto ``` #### **(2) 定位控件结构** 使用 `print_control_identifiers()` 打印窗口所有控件信息: ```python from pywinauto import Application app = Application().start("notepad.exe") dlg = app.UntitledNotepad dlg.print_control_identifiers() # 输出控件的类名、标题、层级关系 ``` #### **(3) 示例:自动填写记事本并保存** ```python from pywinauto import Application from pywinauto.keyboard import send_keys # 启动记事本 app = Application().start("notepad.exe") dlg = app.UntitledNotepad # 输入文本 dlg.Edit.type_keys("Hello Pywinauto!{ENTER}第二行内容") # 保存文件 dlg.menu_item("文件->另存为...").click() save_dlg = app["另存为"] save_dlg.Edit.set_text("C:\\test.txt") # 输入路径 save_dlg["保存(S)"].click() # 处理覆盖确认弹窗(如果文件已存在) if app.window(title="确认另存为").exists(): app.window(title="确认另存为").Button2.click() ``` --- ### **4. 高级技巧** #### **(1) 模糊匹配控件** 使用 `best_match` 或正则表达式定位控件: ```python # 根据部分标题匹配 dlg.child_window(title_re=".*保存.*", control_type="Button").click() # 使用 best_match(自动匹配相似名称的控件) dlg.Save.click() # 等同于 dlg["Save"].click() ``` #### **(2) 处理动态控件** 通过 `wait/wait_not` 等待控件出现或消失: ```python dlg.wait("exists", timeout=10) # 等待窗口出现 dlg.child_window(title="正在加载...").wait_not("visible") # 等待加载完成 ``` #### **(3) 结合其他库增强功能** - **数据驱动**:用 `pandas` 读取 Excel 数据批量填写。 - **图像识别**:用 `OpenCV` 辅助定位无标准控件的元素。 - **异步操作**:用 `threading` 处理后台任务。 --- ### **5. 优缺点对比** | **优点** | **缺点** | |---------------------------------------|---------------------------------------| | 纯 Python 实现,易集成其他库 | 仅支持 Windows 系统 | | 无需编译,调试方便 | 对非标准控件(如游戏UI)支持有限 | | 支持现代 UI 框架(WPF/UWP) | 控件层级复杂时定位较繁琐 | --- ### **6. 常见问题解决** - **问题1:控件无法定位** - 使用 `Inspect.exe`(Windows SDK 工具)查看控件属性。 - 尝试 `child_window()` 结合多个属性(如 `class_name`, `control_type`)。 - **问题2:操作速度过快导致失败** - 添加等待时间:`time.sleep(0.5)` 或 `dlg.wait("ready")`。 - **问题3:中文输入乱码** - 改用 `type_keys()` 而非 `set_text()`,或切换系统输入法为英文。 --- ### **7. 适用场景** - **企业办公自动化**:批量填写 ERP/OA 系统表单。 - **测试验证**:自动化测试 Windows 桌面应用功能。 - **数据迁移**:从旧系统抓取数据并录入新系统。 如果需要更复杂的案例(如操作 Excel/WinForm 程序),可以进一步探讨! 🚀
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值