Python-pywinauto控件操作
桌面软件自动化(二)
🎯 内容回顾
是时候展示真正的技术了!
🎯 内容回顾
我们先回顾下通过pywinauto操作控件需要以下几个步骤:
- 第一步:创建实例化对象,得到的app是Application对象
- 第二步:选择窗口,得到的窗口是WindowSpecification对象
- 第三步:基于WindowSpecification对象使用其方法再往下查找,定位到具体的控件
- 第四步:使用控件的方法属性执行我们需要的操作
接下来我们先看一个实例代码,(确保电脑版微信已经登录且前台显示在桌面上)按照上面的步骤获取微信的搜索文本框并点击,代码如下:
from pywinauto import Application
# 第一步连接已有微信进程创建实例化对象(进程id在任务管理器-详细信息可以查看)
wechat_app = Application(backend='uia').connect(process=18364)
# 第二步拿到微信主窗口
wechat_window = wechat_app.window(class_name='WeChatMainWndForPC')
# 第三步通过child_window方法查找搜索文本框
search_box = wechat_window.child_window(control_type='Edit', title='搜索')
# 第四步进行操作点击控件
search_box.click_input()
打印元素
inspect可以查看窗口控件的所有层级结构,同样我们也可以通过代码将控件下的所有子控件及其属性以树形结构打印出来。 上代码:
dlg.print_control_identifiers(depth=None, filename=None)
- depth: 打印的深度,缺省时打印最大深度。
- filename: 将返回的标识存成文件(生成的文件与当前运行的脚本在同一个路径下)
比如:print_control_identifiers(filename='层级关系.txt')
from pywinauto import Application
# 连接已有微信进程(进程号在任务管理器-详细信息可以查看,项目中一般根据进程名称自动获取)
wechat_app = Application(backend='uia').connect(process=13116)
# 拿到微信主窗口
wechat_window = wechat_app.window(class_name='WeChatMainWndForPC')
wechat_window.restore()
# 打印当前窗口的所有controller(控件和属性)
# 源码内部函数名链式赋值了,print_ctrl_ids = dump_tree = print_control_identifiers, 都能用
wechat_window.print_control_identifiers(depth=None, filename='微信结构.txt')
打印出来的文档树就是inspect中的控件树完全展开的样子,都是有层级的,和微信程序中的各个元素是一一对应的:

控件常用的方法属性
上一篇我们玩转了最常用的两个方法:
ctrl.click_input()- 最常用的点击方法ctrl.type_keys()- 键盘输入方法
下面总结了大部分常用方法,基本可以满足所有场景的操作,如下:
# 点击操作
ctrl.click_input() # 最常用的点击方法,一切点击操作的基本方法(底层调用只是参数不同),左键单击,使用时一般都使用默认不需要带参数
# 键盘输入,底层还是调用keyboard.send_keys
ctrl.type_keys(keys, pause=None, with_spaces=False,)
# keys:要输入的文字内容
# pause:每输入一个字符后等待时间,默认0.01就行
# with_spaces:是否保留keys中的所有空格,默认去除0
# 鼠标键盘操作
# 下面只列举常用形式,他们有很多默认参数但不常用,可以在源码中查看
ctrl.right_click_input() # 鼠标右键单击
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操作集合
# 控件的常用属性
ctrl.children_texts() # 所有子控件的文字列表,对应inspect中Name字段
ctrl.window_text() # 控件的标题文字,对应inspect中Name字段
# ctrl.element_info.name
ctrl.class_name() # 控件的类名,对应inspect中ClassName字段,有些控件没有类名
# ctrl.element_info.class_name
ctrl.element_info.control_type # 控件类型,inspect界面LocalizedControlType字段的英文名
ctrl.is_child(parent) # ctrl是否是parent的子控件
ctrl.legacy_properties().get('Value') # 可以获取inspect界面LegacyIAccessible开头的一系列字段,在源码uiawraper.py中找到了这个方法,非常有用,如某些按钮显示值是我们想要的,但是window_text获取到的是固定文字'修改群昵称',这个值才是我们修改后的新名字
# 控件常用操作
ctrl.draw_outline(colour='green') # 控件外围画框,便于查看,支持'red', 'green', 'blue'
ctrl.print_control_identifiers(depth=None, filename=None) # 打印其包含的元素,详见打印元素
ctrl.scroll(direction, amount, count=1,) # 滚动
# direction :"up", "down", "left", "right"
# amount:"line" or "page"
# count:int 滚动次数
ctrl.capture_as_image() # 返回控件的 PIL image对象可使用save()方法保存
ret = ctrl.rectangle() # 控件上下左右坐标,(L123, 525, R600, B561)
接下来我们对其中一些方法进行举例:
1. 清空文本框
我们前面学习了利用type_keys() 方法输入文本,但是如果当前输入框中有文本时,输入的字符串会拼接在已有的文本后面,所以我们需要先清空文本框再输入。清空原理很简单就是先通过ctrl+a选中所有,然后再type_keys()替换,和我们选中然后修改一样的。代码如下:
from pywinauto import Application
import win32gui
# 根据应用程序窗口名获得句柄
hwnd = win32gui.FindWindow(None, '微信')
# 通过句柄连接已有微信进程
app = Application(backend='uia').connect(handle=hwnd)
dlg = app.window(class_name='WeChatMainWndForPC')
search_box = dlg.child_window(control_type='Edit', title='搜索')
# 先点击搜索框获取焦点
search_box.click_input()
# 全选并删除
search_box.type_keys('^a') # Ctrl+A 全选
search_box.type_keys('{DELETE}') # 删除
# 或者直接用替换方式
search_box.type_keys('^a{DELETE}新文本')
2. 获取控件属性
from pywinauto import Application
import win32gui
hwnd = win32gui.FindWindow(None, '微信')
app = Application(backend='uia').connect(handle=hwnd)
dlg = app.window(class_name='WeChatMainWndForPC')
search_box = dlg.child_window(control_type='Edit', title='搜索')
# 获取控件文本
print(search_box.window_text())
# 获取控件类名
print(search_box.class_name())
# 获取控件类型
print(search_box.element_info.control_type)
# 获取控件坐标
print(search_box.rectangle())
3. 截图保存
from pywinauto import Application
import win32gui
hwnd = win32gui.FindWindow(None, '微信')
app = Application(backend='uia').connect(handle=hwnd)
dlg = app.window(class_name='WeChatMainWndForPC')
# 对窗口截图
img = dlg.capture_as_image()
img.save('微信截图.png')
# 对特定控件截图
search_box = dlg.child_window(control_type='Edit', title='搜索')
img = search_box.capture_as_image()
img.save('搜索框截图.png')
实战案例:微信自动发送消息
下面我们实现一个完整的微信自动发送消息的脚本:
import time
from pywinauto import Application
import win32gui
import keyboard
def 发送消息(联系人, 消息内容):
"""
给指定联系人发送消息
:param contact: 联系人名称
:param message: 要发送的消息
"""
# 获取微信窗口句柄
hwnd = win32gui.FindWindow(None, '微信')
if hwnd == 0:
print('未找到微信窗口')
return False
# 连接微信进程
app = Application(backend='uia').connect(handle=hwnd)
dlg = app.window(class_name='WeChatMainWndForPC')
# 确保窗口在前台
dlg.restore()
time.sleep(0.5)
# 点击搜索框
search_box = dlg.child_window(control_type='Edit', title='搜索')
search_box.click_input()
time.sleep(0.2)
# 清空并输入联系人
search_box.type_keys('^a{DELETE}')
time.sleep(0.1)
keyboard.write(contact)
time.sleep(0.5)
# 按回车选择联系人
keyboard.send('enter')
time.sleep(0.5)
# 输入消息内容
keyboard.write(message)
time.sleep(0.2)
# 发送消息
keyboard.send('enter')
time.sleep(0.5)
print(f'已向 {contact} 发送消息:{message}')
return True
if __name__ == '__main__':
# 使用示例
send_message('文件传输助手', 'Hello, World!')
批量发送消息
import time
from pywinauto import Application
import win32gui
import keyboard
def batch_send(contact_list, message):
"""
给多个联系人发送相同消息
:param contact_list: 联系人名称列表
:param message: 要发送的消息
"""
for contact in contact_list:
try:
send_message(contact, message)
time.sleep(1) # 间隔1秒,避免发送过快
except Exception as e:
print(f'向 {contact} 发送消息失败:{e}')
if __name__ == '__main__':
contact_list = ['文件传输助手', '张三', '李四']
message = '这是一条测试消息'
batch_send(contact_list, message)
代码写起来!
点击查看参考代码
挑战:用pywinauto自动打开记事本并写入内容
from pywinauto import Application
import time
def test_notepad():
"""测试用pywinauto操作记事本"""
# 第一步:启动记事本
print("正在打开记事本...")
app = Application(backend='uia').start('notepad.exe')
# 第二步:等待记事本窗口加载
# 不同Windows版本的记事本标题可能不同
dlg = app.window(title_re='.*记事本.*|.*Notepad.*')
dlg.wait('ready', timeout=5)
print("记事本已打开")
# 第三步:找到编辑区域
# 记事本的编辑区域是一个Edit控件
edit = dlg.child_window(control_type='Edit')
# 第四步:输入内容
edit.click_input() # 先点击获取焦点
edit.type_keys('Hello, pywinauto!')
edit.type_keys('{ENTER}') # 换行
edit.type_keys('这是用pywinauto自动输入的内容')
print("内容已输入")
# 第五步:截图保存
img = dlg.capture_as_image()
img.save('notepad_test.png')
print("截图已保存: notepad_test.png")
# 第六步:关闭记事本(不保存)
dlg.close()
print("记事本已关闭")
if __name__ == '__main__':
test_notepad()
思路解析:
这个练习帮你熟悉pywinauto的基本操作流程:
- 启动程序:
Application(backend='uia').start('程序名')可以启动任何程序 - 等待窗口:
dlg.wait('ready', timeout=5)确保窗口加载完成再继续 - 查找控件:
child_window(control_type='Edit')按控件类型查找 - 输入内容:
type_keys()可以输入文字和特殊按键(如{ENTER}换行) - 截图:
capture_as_image()对窗口截图,返回PIL Image对象 - 关闭程序:
dlg.close()关闭窗口
调试技巧:
- 如果找不到控件,先用
dlg.print_control_identifiers()打印控件树 title_re支持正则表达式,适合标题不确定的情况- 输入中文时如果
type_keys()不行,可以试试keyboard.write()
这一章咱们把pywinauto的控件操作玩明白了,来捋一捋都学了啥:
打印控件树咋弄的? 用 print_control_identifiers() 方法能把窗口下所有控件的层级结构打印出来,跟inspect工具看到的一样。这个特别实用,写代码前先用这个方法摸清控件结构,找控件就方便多了。还能用 filename 参数把结果保存到文件里,慢慢看。
控件怎么点击和输入? 点击用 click_input(),输入用 type_keys()。这两个是最常用的方法。type_keys() 里有些特殊写法:^a 是Ctrl+A,{DELETE} 是删除键,^a{DELETE} 组合起来就是全选后删除,用来清空文本框特别好用。
怎么获取控件属性? 控件有很多属性可以获取:window_text() 获取控件上的文字,class_name() 获取控件类名,element_info.control_type 获取控件类型,rectangle() 获取控件坐标。还有个特别实用的 legacy_properties().get('Value'),能获取到控件的实际值,比如修改后的昵称,而不是固定的按钮文字。
截图怎么截的? 用 capture_as_image() 方法,可以对整个窗口截图,也可以对某个控件截图。返回的是PIL Image对象,直接用 .save() 就能保存成图片文件。这个比前面学的pywin32截图简单多了,不用搞什么设备上下文、位图那些复杂的东西。
鼠标操作还有哪些? 除了点击,还有 right_click_input() 右键点击、double_click_input() 双击、press_mouse_input() 按下、release_mouse_input() 释放、move_mouse_input() 移动、drag_mouse_input() 拖拽。这些方法底层都是调用同一个基础方法,只是参数不同。
清空文本框的套路是啥? 先点击获取焦点,然后 type_keys('^a{DELETE}') 全选删除,再输入新内容。这个套路在自动化填表、搜索等场景里经常用到,记住就行。
微信自动发消息咋实现的? 核心流程是:找到微信窗口 → 点搜索框 → 输入联系人 → 回车选择 → 输入消息 → 回车发送。这里用了 keyboard 库来模拟键盘输入,比pywinauto的 type_keys() 更灵活,特别是输入中文的时候。
掌握了这些控件操作方法,基本上能自动化操作大部分Windows桌面程序了。不管是填表、发消息、还是批量操作,都能搞定。
动手试试
选择题
1、pywinauto中控件的点击方法是哪个?
- A.
click() - B.
click_input() - C.
press() - D.
tap()
答案:B
解析: pywinauto里点击控件用的是 click_input() 方法。这个方法会模拟真实的鼠标点击操作,包括移动鼠标到控件位置、按下、释放等步骤。那 click() 呢?click() 是另一种点击方式,它发送的是Windows消息,不移动鼠标,速度快但有些控件可能不响应。所以一般情况下推荐用 click_input(),更可靠。如果追求速度且控件支持消息点击,可以用 click()。
2、获取控件标题文字的方法是哪个?
- A.
get_text() - B.
window_text() - C.
title() - D.
caption()
答案:B
解析: 获取控件上显示的文字用的是 window_text() 方法。这个方法对应的是inspect工具里的Name字段。比如一个按钮上写着"确定",window_text() 就能拿到"确定"这两个字。另外还有个 children_texts() 方法,能拿到所有子控件的文字列表。如果要获取控件的实际值(比如输入框里的内容、修改后的昵称),可以用 legacy_properties().get('Value'),这个在某些场景下比 window_text() 更准确。
编程挑战
3、编写一个程序,使用pywinauto自动打开计算器并计算 123 + 456。
参考代码:
from pywinauto import Application
import time
# 第一步:启动计算器
app = Application(backend='uia').start('calc.exe')
# 第二步:选择计算器窗口
# 等待计算器窗口加载完成
dlg = app.window(title='计算器')
dlg.wait('ready', timeout=5)
# 第三步:依次点击按钮输入 123 + 456 =
# 方法一:通过按钮文字查找并点击
def click_button(button_text):
"""点击计算器上的按钮"""
btn = dlg.child_window(title=button_text, control_type='Button')
btn.click_input()
time.sleep(0.1) # 每次点击后稍作等待
# 输入 123
click_button('1')
click_button('2')
click_button('3')
# 输入 +
click_button('加') # 中文系统显示为"加",英文系统为"Plus"
# 输入 456
click_button('4')
click_button('5')
click_button('6')
# 输入 =
click_button('等于') # 中文系统显示为"等于",英文系统为"Equals"
# 等待计算完成
time.sleep(0.5)
# 第四步:获取结果显示
# 计算器的结果显示在一个Text控件中
result_text = dlg.child_window(control_type='Text', found_index=0)
result = result_text.window_text()
print(f'计算结果:123 + 456 = {result}')
# 截图保存
img = dlg.capture_as_image()
img.save('计算器结果.png')
print('结果已保存到 计算器结果.png')
思路解析:
这道题考察的是pywinauto操作控件的基本功,从启动程序到查找控件再到获取结果。
首先,启动计算器用 start('calc.exe') 就行,calc.exe是Windows自带的计算器程序。启动后要等窗口加载完,用 dlg.wait('ready', timeout=5) 等5秒,确保窗口就绪。
其次,计算器的每个按钮都是一个Button类型的控件,通过按钮上的文字就能找到。用 child_window(title='按钮文字', control_type='Button') 就能定位到具体按钮,然后 click_input() 点击。这里要注意,中文Windows系统里按钮显示的是"加"、"等于"等中文,英文系统里是"Plus"、"Equals",要根据实际系统调整。
然后,输入完表达式后,结果显示在一个Text控件里。用 child_window(control_type='Text', found_index=0) 找到第一个Text控件,再用 window_text() 获取显示的文字就是计算结果。
最后,用 capture_as_image() 截图保存,方便查看和验证结果。
几个实用小技巧:
- 如果不确定按钮叫什么名字,先用
print_control_identifiers()把所有控件打印出来看看。 - 每次点击之间加点延时(0.1秒左右),给计算器反应的时间,不然可能点太快漏掉。
- 如果按钮找不到,可能是计算器窗口还没完全加载,加大等待时间或者用
dlg.wait('ready')等窗口就绪。
浙公网安备 33010602011771号