最近在实验室用ENVI标了一批图,好多操作都是机械的重复的,于是想有没有可能做一个自动化的小工具来简化操作。查了一些资料发现pywin32应该是可以。于是先尝试简单做一个小例子练练手。
这篇做的是打开记事本,输入helloworld,选择编码格式为utf-8并保存。
使用了spy++工具来辅助
这里有两种写法,用下面哪一句都可
def start_app(path):
win32api.ShellExecute(0, 'open', path,'','',1)
# os.startfile(path) # 也可用这条
每一个窗口都有唯一的一个handle,调用win32API的时候,这个handle是操作每一个窗口和控件必须的参数。
要找到窗口的handle,需要两个函数
参考https://blog.csdn.net/bluehawksky/article/details/44957627
首先要获取主窗口的handle
notepad_path = r'C:\Windows\notepad.exe'
start_app(notepad_path)
while True:
win = win32gui.FindWindow('Notepad','')
if win != 0:
win32api.Sleep(200)
break
这里用了一个循环是因为程序启动需要时间,后面出现有延迟的地方,都是在实际测试的时候,有时候窗口没有完全打开导致查询不到窗口。
封装了几个函数,可以直接调用
def printHandle(func):
def wrapper(*args, **kw):
handle = func(*args, **kw)
print('%x' % (handle))
return handle
return wrapper
def find_idxSubHandle(pHandle, winClass, index=0):
"""
已知子窗口的窗体类名
寻找第index号个同类型的兄弟窗口
"""
assert type(index) == int and index >= 0
handle = win32gui.FindWindowEx(pHandle, 0, winClass, None)
while index > 0:
handle = win32gui.FindWindowEx(pHandle, handle, winClass, None)
index -= 1
return handle
def find_subHandle(pHandle, winClassList):
"""
递归寻找子窗口的句柄
pHandle是祖父窗口的句柄
winClassList是各个子窗口的class列表,父辈的list-index小于子辈
"""
assert type(winClassList) == list
if len(winClassList) == 1:
return find_idxSubHandle(pHandle, winClassList[0][0], winClassList[0][1])
else:
pHandle = find_idxSubHandle(pHandle, winClassList[0][0], winClassList[0][1])
return find_subHandle(pHandle, winClassList[1:])
@printHandle
def find_handle_by_wndlist(pHandle, winClassList):
return find_subHandle(pHandle, winClassList)
说明:printHandle是一个装饰器,打印出返回的handle,结合spy++对比返回的窗口是不是我们想要的,主要用来调试。
这里结合spy++举个例子
比如我们想定位记事本的输入区域
打开spy++,右键刷新一下窗口,点击工具栏的望远镜,弹出窗口搜索,把靶心标志的图标拖到记事本的编辑区域,即返回句柄标题类等信息。
点击确定,定位出该窗口的位置
外面的“无标题-记事本”是最外面的窗口,Edit是他的一个子窗口
定位这个窗口就这样写就可以了,返回Edit窗口的handle
edit_handle = find_handle_by_wndlist(win, [('Edit', 0)])
如果有链式的关系就像这样写:
save_name_handle = find_handle_by_wndlist(save_win, [('DUIViewWndClassName',0),('DirectUIHWND',0),\
('FloatNotifySink',0),('ComboBox',0),('Edit',0)])
tuple中第一个参数是窗口的类名,第二个参数是寻找窗口时,如果有相同类名的窗口,这些窗口之间就有一个顺序,从0开始编,用不同的数字区分这些窗口。
win32这些几乎所有的操作都是通过消息,消息的参数太复杂了,我就把常用到的操作封装起来了
def fill_content(handle, content):
win32gui.SendMessage(handle, win32con.WM_SETTEXT, None, content)
这句的handle填Edit窗口的句柄,content是要输入的内容
封装了一个函数:
@printHandle
def get_menu_item(handle, posilist):
menu_handle = win32gui.GetMenu(handle)
for index in posilist:
if index is posilist[-1]:
cmd_ID = win32gui.GetMenuItemID(menu_handle, index)
break
menu_handle = win32gui.GetSubMenu(menu_handle, index)
return cmd_ID
handle是主窗口的handle,posilist是描述菜单栏中某一项的位置的,比如前面那张图,另存为的位置是[0, 3]。注意:如果有分割线,分割线也是计数的
这个函数这样使用:
saveas_posilist = [0, 3]
saveas_handle = get_menu_item(win, saveas_posilist)
使用这个函数
def open_menu_item(handle, menu_item_id):
win32gui.PostMessage(handle, win32con.WM_COMMAND, menu_item_id, 0)
handle是主窗口的handle,menu_item_id是上一步返回的结果
open_menu_item(win, saveas_handle)
点击确定按钮的时候会用到,下面两个函数测试过都可以
def leftClick(handle):
win32gui.SendMessage(handle, win32con.WM_LBUTTONDOWN, None, None)
win32gui.SendMessage(handle, win32con.WM_LBUTTONUP, None, None)
def send_click_msg(Mhandle, confirmBTN_handle):
win32api.SendMessage(Mhandle, win32con.WM_COMMAND, 1, confirmBTN_handle)
这里选择utf-8编码,具体操作参考了前面提到那篇文章,比较复杂,封装起来直接用:
def select_comboBox_item(PCB_handle, CB_handle, select_index):
if win32api.SendMessage(CB_handle, win32con.CB_SETCURSEL, select_index, 0) == select_index:
win32api.SendMessage(PCB_handle, win32con.WM_COMMAND, win32con.CBN_SELENDOK & 16+0, CB_handle) # 控件的ID是0,所以低位直接加0
win32api.SendMessage(PCB_handle, win32con.WM_COMMAND, win32con.CBN_SELCHANGE & 16+0, CB_handle)
else:
raise Exception("Change saving type failed")
这里PCB_handle是comboBox的主窗口的handle,CB_handle是comboBox的handle,select_index是定位选项的数
这里参考的文章写的PCB_handle是父窗口,我实际填进去的是主窗口(最外面的那一层,也就是“另存为”所在窗口),运行没有问题
import win32gui
import win32con
import win32api
def printHandle(func):
def wrapper(*args, **kw):
handle = func(*args, **kw)
print('%x' % (handle))
return handle
return wrapper
def find_idxSubHandle(pHandle, winClass, index=0):
"""
已知子窗口的窗体类名
寻找第index号个同类型的兄弟窗口
"""
assert type(index) == int and index >= 0
handle = win32gui.FindWindowEx(pHandle, 0, winClass, None)
while index > 0:
handle = win32gui.FindWindowEx(pHandle, handle, winClass, None)
index -= 1
return handle
def find_subHandle(pHandle, winClassList):
"""
递归寻找子窗口的句柄
pHandle是祖父窗口的句柄
winClassList是各个子窗口的class列表,父辈的list-index小于子辈
"""
assert type(winClassList) == list
if len(winClassList) == 1:
return find_idxSubHandle(pHandle, winClassList[0][0], winClassList[0][1])
else:
pHandle = find_idxSubHandle(pHandle, winClassList[0][0], winClassList[0][1])
return find_subHandle(pHandle, winClassList[1:])
@printHandle
def find_handle_by_wndlist(pHandle, winClassList):
return find_subHandle(pHandle, winClassList)
def fill_content(handle, content):
win32gui.SendMessage(handle, win32con.WM_SETTEXT, None, content)
def start_app(path):
win32api.ShellExecute(0, 'open', path,'','',1)
# os.startfile(path) # 也可用这条
def select_comboBox_item(PCB_handle, CB_handle, select_index):
if win32api.SendMessage(CB_handle, win32con.CB_SETCURSEL, select_index, 0) == select_index:
win32api.SendMessage(PCB_handle, win32con.WM_COMMAND, win32con.CBN_SELENDOK & 16+0, CB_handle) # 控件的ID是0,所以低位直接加0
win32api.SendMessage(PCB_handle, win32con.WM_COMMAND, win32con.CBN_SELCHANGE & 16+0, CB_handle)
else:
raise Exception("Change saving type failed")
@printHandle
def get_menu_item(handle, posilist):
menu_handle = win32gui.GetMenu(handle)
for index in posilist:
if index is posilist[-1]:
cmd_ID = win32gui.GetMenuItemID(menu_handle, index)
break
menu_handle = win32gui.GetSubMenu(menu_handle, index)
return cmd_ID
def open_menu_item(handle, menu_item_id):
win32gui.PostMessage(handle, win32con.WM_COMMAND, menu_item_id, 0)
def leftClick(handle):
win32gui.SendMessage(handle, win32con.WM_LBUTTONDOWN, None, None)
win32gui.SendMessage(handle, win32con.WM_LBUTTONUP, None, None)
def send_click_msg(Mhandle, confirmBTN_handle):
win32api.SendMessage(Mhandle, win32con.WM_COMMAND, 1, confirmBTN_handle)
if __name__ == '__main__':
save_path = r'd:\test.txt'
notepad_path = r'C:\Windows\notepad.exe'
start_app(notepad_path)
saveas_posilist = [0, 3]
while True:
win = win32gui.FindWindow('Notepad','')
if win != 0:
win32api.Sleep(200)
break
edit_handle = find_handle_by_wndlist(win, [('Edit', 0)])
fill_content(edit_handle, 'hello world')
saveas_handle = get_menu_item(win, saveas_posilist)
open_menu_item(win, saveas_handle)
while True:
if win32gui.FindWindow(None, '另存为')!=0:
win32api.Sleep(2000)
break
else:
win32api.Sleep(200)
save_win = win32gui.FindWindow(None, '另存为')
save_name_handle = find_handle_by_wndlist(save_win, [('DUIViewWndClassName',0),('DirectUIHWND',0),\
('FloatNotifySink',0),('ComboBox',0),('Edit',0)])
fill_content(save_name_handle, save_path)
comboBox_handle = find_handle_by_wndlist(save_win, [('ComboBox', 0)])
select_comboBox_item(save_win,comboBox_handle,3)
save_btn_handle = find_handle_by_wndlist(save_win, [('Button', 0)])
send_click_msg(save_win, save_btn_handle)
运行结果:打开新的文本文档,输入hello world,保存为utf-8编码,存在d:\test.txt