-想整个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 ) 打开或打印一个文件。 Parametershwnd : 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的这个一样的效果。。。。。