pywin32:入门

本文介绍如何使用pywin32模块实现自动化操作,包括启动记事本、写入文字、另存为等功能。详细讲解了win32api、win32gui等模块的使用方法,以及消息机制和窗口句柄的概念。

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

-想整个QQ自动发消息的,于是找到了这个,这个模块呢就是用来调用window系统的API,来打开个程序,自动发个消息之类的,更高级的线程进程啥的咱也不会。。。

链接: https://pan.baidu.com/s/1mqrFYh3TGUVQ8gIcXoJ1BQ 提取码: fr9x pywin32 的帮助文档,,,
链接: https://pan.baidu.com/s/1U26Q23JqJblrUIfvGBZSQw 提取码: h5vq spy++的

window 消息机制

参考脑补链接
消息是系统定义的一个32位值,定义了一个事件。消息的结构(MSG):

typedef struct tagMsg
{
       HWND    hwnd;            //接受该消息的窗口句柄,就是消息所属的窗口,一般是16进制的一堆数,表示这资源内存的标识号
       UINT    message;         //消息常量标识符,也就是我们通常所说的消息号,其实就是一些常量,也就五千多个吧。。
       WPARAM  wParam;     //32位消息的特定附加信息,确切含义依赖于消息值,
       LPARAM  lParam;        //32位消息的特定附加信息,确切含义依赖于消息值
       DWORD   time;            //消息创建时的时间
       POINT   pt;                  //消息创建时的鼠标/光标在屏幕坐标系中的位置
}MSG;

消息标识符:WM_NULL—0x0000 空消息。具体的查询就要看MSDN了(微软的开发文档)
这里面还可以查询所有消息的,wParam and lParam。感觉不错。。。。就是没有中文。。。

当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由
Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。

pywin32

网上实在找不到全面的教程,,,至于帮助文档,,各种模块的方法倒是很全,但是完全没讲怎么和消息的结合,自带的例子,,呵呵看不懂。所以就尽量把所有能找到的实例研究一遍,估计常用的方法也差不多了,毕竟超超超极多的消息和方法也没打算全记住。。。

win32api 提供了常用的用户API

win32clipboard 提供了有关粘贴板的API

win32gui 提供了有关windows用户界面图形操作的API

win32file 提供了有关文件操作的API

win32con 有关的消息常量,,,感觉这些模块都好用欸,,一步一步来吧

spy++ 的使用

这个软件是来看窗口的句柄,类名的,很好用啊,不过人家说是 VC 自带的,我咋没有呢。。。
参考脑补连接

栗子一:启动记事本写入文字再另存为

参考脑补连接

import win32gui         # 先来小小的体验一下
import win32con
win = win32gui.FindWindow('Notepad','新建文本文档.txt - 记事本')          # 找到这个文件,里面的参数都是再spy++ 里面找的
tid = win32gui.FindWindowEx(win,None,'Edit',None)                       # 主窗口下的子窗口
win32gui.SendMessage(tid, win32con.WM_SETTEXT, None, '呐呐呐')  # 写入字段
win32gui.PostMessage(tid,win32con.WM_KEYDOWN,win32con.VK_RETURN,0)       # 插入一个回车

在这里插入图片描述
下面就是我再这个例子中学到的一些函数和消息。参考这位大佬写的四篇文章,,可惜不更新了

启动记事本

win32api.ShellExecute int = ShellExecute(hwnd, op , file , params ,
dir , bShow ) 打开或打印一个文件。 Parameters

hwnd : PyHANDLE

父窗口的句柄,如果没有父窗口,则为0。该窗口接收应用程序生成的任何消息框(例如,用于错误报告)。

op : string

要执行的操作。可以是“打开”,“打印”或“无”,默认为“打开”。

file : string 文件名

params : string

如果文件名包含可执行文件,则传递的参数。对于文档文件,应为None。

dir : string

应用程序的初始目录。

bShow : int

指定在打开应用程序时是否显示该应用程序。如果lpszFile参数指定一个文档文件,则此参数为零。

win32api.Sleep
int = Sleep(time, bAlterable )

在指定的时间内暂停当前线程的执行。

Parameters

time : int

The number of milli-seconds to sleep for,

bAlterable=0 : int

指定该函数是否可能由于I / O完成回调函数而提前终止。

Return Value 如果指定的时间间隔到期,则返回值为零。


win32gui.FindWindow PyHANDLE = FindWindow(ClassName, WindowName )

检索顶级窗口的句柄,该窗口的类名和窗口名与指定的字符串匹配。Parameters

ClassName : 要查找的窗口类的名,可以为None

WindowName : 查找窗口的标题,可以为None


win32gui.FindWindowEx PyHANDLE = FindWindowEx(Parent, ChildAfter , ClassName , WindowName )

Parameters

Parent : PyHANDLE 将搜索其子窗口的窗口。如果为0,则假定为桌面窗口。

ChildAfter : PyHANDLE 以Z顺序搜索之后的子窗口,可以为0以搜索所有子窗口

ClassName :

WindowName :

save_path = r'C:\Users\NERO\Desktop\栗子一.txt' # 这个也是以后保存的文件名
notepad_path=r'C:\Windows\notepad.exe'       # 这个是记事本应用的路径
win32api.ShellExecute(0,'open',notepad_path,'','',1)
sasveas_posilst=[0,3]
while True:
    win = win32gui.FindWindow('Notepad','')     # 来个循环,等待窗口的开启。
    if win != 0:                         # 如果开启win 就是这个窗口的句柄了。
        win32api.Sleep(200)
        print('启动成功',win)
        break

写入文字

win32gui.SendMessage int = SendMessage(hwnd, message , wparam , lparam )

Sends a message to the window.

Parameters

hwnd : int

The handle to the Window

message : int

The ID of the message to post

wparam=None : int/str

Type depends on the message

lparam=None : int/str

Type depends on the message

edit_handle = win32gui.FindWindowEx(win,0,'Edit',None)                 # 找到子窗口,输入文字,这个Edit 再spy++ 里获得。
print(edit_handle)
win32gui.SendMessage(edit_handle,win32con.WM_SETTEXT,None,'现在我很困啊')

WM_SETTEXT message
wParam
不使用此参数。
lParam
指向以空值结尾的字符串的指针,该字符串是窗口文本。

将指定的消息发送到一个或多个窗口。该SendMessage函数的函数调用指定的窗口的窗口过程,并不会返回,直到窗口过程已经处理了该消息。 要发送消息并立即返回,请使用SendMessageCallback或SendNotifyMessage函数。

要将消息发布到线程的消息队列中并立即返回,只是把消息放入队列,不管其他程序是否处理都返 回,然后继续执行,请使用PostMessage或PostThreadMessage函数。

在控制别的应用程序的时候,经常需要等待直到某个功能结束,例如:
打开一个窗口–>等待直到窗口结束
这 个时候就可以用到SendMessage --------------------------------------------> SendMessage消息是同步的
如果在打开这个窗口后仍然需要对该窗口的界面进行设置,比如Edit的value等等,比如:
打 开一个窗口–>控制窗口的control的属性
这个时候就需要PostMessage --------------------------------------------> PostMessage消息是异步的

win32gui.PostMessage PostMessage(hwnd, message, wparam, lparam) 在消息队列中加入为指定的窗体加入一条消息,并马上返回,不等待线程对消息的处理。 Parameters

hwnd : int

The handle to the Window

message : int

The ID of the message to post

wparam=0 : int

An integer whose value depends on the message

lparam=0 : int

An integer whose value depends on the message

菜单操作

在这里插入图片描述

GetMenu(hwnd)            
描述:获取窗口的菜单句柄。
参数:
hwnd:整型,需要获取菜单的窗口的句柄。
说明:获取的是插图中黄色的部分。

GetSubMenu(hMenu, nPos)
描述:获取菜单的下拉菜单或者子菜单。
参数:
hMenu:整型,菜单的句柄,从GetMenu获得。
nPos:整型,下拉菜单或子菜单的的索引,从0算起。
说明:这个可以获取插图中蓝色的部分z;如描述所述,这个不仅可以获取本例中的下拉菜单,还可以获取子菜单。

GetMenuItemID(hMenu, nPos)
描述:获取菜单中特定项目的标识符。
参数:
hMenu:整型,包含所需菜单项的菜单句柄,从GetSubMenu获得。
nPos:整型,菜单项的索引,从0算起。
说明:这个获取的就是红色区域中的项目啦,注意,分隔符是被编入索引的,所以退出就是8,那几条线也算的。
menu_handle = win32gui.GetMenu(win)
menu_handle = win32gui.GetSubMenu(menu_handle,0)
com_ID = win32gui.GetMenuItemID(menu_handle,3)
win32gui.PostMessage(win,win32con.WM_COMMAND,com_ID,0)      # 不明白 WM_COMMAND的参数这么些,去MSDN搜吧,,
# 通过postmessage 来打开了另存为这个窗口。
while True:
    if win32gui.FindWindow(None, '另存为')!=0:
        win32api.Sleep(2000)
        print('另存为打开成功')
        break
    else:
        win32api.Sleep(200)

可以看到,当WM_COMMAND操作菜单时,wparm就是菜单标识符(高字节低字节的问题一会再说),Iparam0
在这里插入图片描述

选择utf-8 编码(comboBox下拉组合框操作),写入文件名,点击确定

本来不想用函数的,奈何人家写的函数太香了,,,,总的来一遍吧。。

save_path = r'C:\Users\NERO\Desktop\test.txt'
notepad_path=r'C:\Windows\notepad.exe'
win32api.ShellExecute(0,'open',notepad_path,'','',1)
sasveas_posilst=[0,3]
while True:
    win = win32gui.FindWindow('Notepad','')
    if win != 0:
        win32api.Sleep(200)
        print('启动成功',win)
        break
edit_handle = win32gui.FindWindowEx(win,0,'Edit',None)
print(edit_handle)
win32gui.SendMessage(edit_handle,win32con.WM_SETTEXT,None,'现在我很困啊')

menu_handle = win32gui.GetMenu(win)
menu_handle = win32gui.GetSubMenu(menu_handle,0)
com_ID = win32gui.GetMenuItemID(menu_handle,3)
win32gui.PostMessage(win,win32con.WM_COMMAND,com_ID,0)           # 这里用SendMessage 为啥不行呢。。。就打不开这个窗口
				# 我感觉应该是SendMessage 要等消息返回才继续,否则处于堵塞状态吧。。。											
while True:
    if win32gui.FindWindow(None, '另存为')!=0:        # 这里的循环就是等待postmessage发送的消息执行
        win32api.Sleep(2000)
        print('另存为打开成功')
        break
    else:
        win32api.Sleep(200)

在这里插入图片描述

save_win = win32gui.FindWindow(None, '另存为')
# save_name_handle = win32gui.FindWindowEx(save_win,0,'Edit','*.txt')
# win32gui.SendMessage(save_name_handle,win32con.WM_SETTEXT,None,save_path)
# 姑且试了下,那个文件名的框框再好几层下面,,直接是找不到的要一层一层下去,这就麻烦了,不过人家用递归写的函数,,精彩!!!

save_name_handle = find_handle_by_wndlist(save_win, [('DUIViewWndClassName',0),('DirectUIHWND',0),\
                                                         ('FloatNotifySink',0),('ComboBox',0),('Edit',0)])
win32gui.SendMessage(save_name_handle,win32con.WM_SETTEXT,None,save_path)      # 这里就可以把我们的文件名填进去了

comboBox_handle = find_handle_by_wndlist(save_win, [('ComboBox', 0)])  # 找到comboBox的句柄

select_comboBox_item(save_win,comboBox_handle,3)               # 这就是对编码的选择

save_btn_handle = find_handle_by_wndlist(save_win, [('Button', 0)])       # 找到确定按钮的句柄
win32api.SendMessage(save_win,win32con.WM_COMMAND,win32con.VK_LBUTTON,save_btn_handle)     # 执行点击操作,看不懂可以看靠下面的解析。

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:])

def find_handle_by_wndlist(pHandle, winClassList):
    return find_subHandle(pHandle, winClassList)

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) # ID在 spy里找就行
        print('改变完成')
    else:
        raise Exception("Change saving type failed")

CB_SETCURSEL 消息
描述:下拉框操作
参数:
wParam:以0起始的待选选项的索引;如果该值为-1,将从组合框列表中删除当前选项,并使当前选项为空
lParam:未使用。0
返回值:
更改选择成功将返回所设置选项的索引号。


CBN_SELENDOK 通知(notification code)
描述:当用户选择了有效的列表项时发送,提示父窗体处理用户的选择。父窗体通过WM_COMMAND消息接收这个通知。
参数:(作为WM_COMMAND的参数)
wParam:LOWORD为组合框的ID. HIWORD为CBN_SELENDOK的值。
lParam:组合框的句柄。


CBN_SELCHANGE 通知(notification code)
描述:当用户更改了列表项的选择时发送,不论用户是通过鼠标选择或是通过方向键选择都会发送此通知。父窗体通过WM_COMMAND消息接收这个通知。
参数:(作为WM_COMMAND的参数)
wParam:LOWORD为组合框的ID. HIWORD为CBN_SELCHANGE的值。
lParam:组合框的句柄。

也就是说,如果要改变下拉框的选项,要使用这三个消息。

众所周知,,,消息的32位的,而一个消息都有两个参数 ,wparam lparam,当参数不够用时,wparam就分成两半,高字节和低字节,

CBN_SELENDOK notification code
Parameters
wParam
.高字节指定通知代码,低字节就是控件ID(再spy++里找)
lParam
combo box.句柄

所以来个位运算 <<16 左移16位,低位加ID,就行啦

然后就是那个点击的操作,,,win32api.SendMessage(save_win,win32con.WM_COMMAND,win32con.VK_LBUTTON,save_btn_handle)
下面就是WM_COMMAND 参数所有的情况(网页自动翻译的,,可能不准吧)
在这里插入图片描述
这应该就是控制操作了,虚拟按键
VK_LBUTTON 0x01
鼠标左键,,,,至于为啥使用 win32api的SendMessage()我也不知道,测试了一下跟win32gui的这个一样的效果。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值