Windows-Python 应用: 使用消息操作窗口
在 Windows 端经常会使用到一些带界面的工具,但在实现自动化的时候对这些工具的自动化如果使用位置点击的方式,那么自动化的稳定性不太好,并且实现起来也不方便,除了界面点击的方式是否还有其它更简单、稳定的方式呢?今天就介绍一种通过消息操作界面的方法。
Windows 系统是建立在事件驱动的机制上的,通过消息的传递来实现的,消息提供了应用程序之间、应用程序与 windows 系统之间进行通信的手段。
Windows 窗口
窗口是位于屏幕中的一个区域,它用于接收用户的输入,然后以文本或图形的形式显示输出。比如一个应用程序,一般都是以图形界面的形式展示,这个图形界面就是应用程序的窗口,其中可能还包括工具栏、按钮、滚动条、输入框等等,这些也是窗口,通常被称为“子窗口”或“控件窗口”或“子窗口控件”。
HELLOWIN.C/--------------------------------------------------------------------------------------------HELLOWIN.C——Displays“Hello, Windows 98!”in client area(c) Charles Petzold, 1998---------------------------------------------------------------------------------------------/
include < windows.h >
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){static TCHAR szAppName [] = TEXT (" HelloWin ");HWND hwnd;MSG msg;WNDCLASS wndclass;//窗口类 wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc=WndProc;wndclass.cbClsExtra=0;wndclass.cbWndExtra=0;wndclass.hInstance=hInstance;wndclass.hIcon=LoadIcon (NULL, IDI_APPLICATION);wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);wndclass.hbrBackground=(HBRUSH) GetstockObject (WHITE_BRUSH);wndclass.lpszMenuName=NULL;wndclass.lpszclassName= szAppName;if (!RegisterClass (&wndclass)) //窗口类的注册{MessageBox (NULL, TEXT(“This program requires Windows NT !*”),SzAppName, MB_ICONERROR);return 0;}//窗口的创建 hwnd = CreateWindow ( szAppName,//窗口类名 TEXT(“Hello 程序”),//窗口标题 WS_OVERLAPPEDWINDOW,//窗口风格 CW_USEDEFAULT,// x 的初始位置 CW_USEDEFAULT,// y 的初始位置 CW_USEDEFAULT,//初始 x 大小 CW_USEDEFAULT,//初始 y 大小 NULL,//父窗口句柄 NULL,//窗口菜单句柄 hInstance,//程序实例句柄 NULL);//创建参数 ShowWindow (hwnd, iCmdShow); //窗口的显示 Updatewindow (hwnd); //窗口客户区重绘//消息循环,从消息队列中获取消息 while (GetMessage (&msg, NULL, 0, 0)){TranslateMessage (&msg);DispatchMessage (&msg);}return msg.wParam;}
//窗口过程,决定了窗口客户区的显示内容以及窗口如何对用户的输入做出响应。即消息的处理。LRESULT CALLBACK WndProc (HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam){HDC hdc;PAINTSTRUCT ps;RECT rect;switch(message){case WM_CREATE:PlaySound (TEXT (" hellowin .wav"), NULL, SND_FILENAME I SND_ASYNC);return 0;case MWM_PAINT:hdc = BeginPaint (hwnd, &ps);GetClientRect (hwnd,&rect);Drawtext (hdc, TEXT("Hello, Windows 98 !”), -1, &rect ,DT_SINGLELINE | DT_CENTER | DT_VCENTER);EndPaint (hwnd, &ps);return0;case MWM_DESTROY:PostQuitMessage(0);return 0;}return DefwindowProc (hwnd, message, wParam, lParam);}
• 系统消息队列
• 应用程序消息队列
发生事件时,Windows 先将触发的消息放入系统消息队列,之后根据消息的 hwnd 值将消息复制到相应的应用程序消息队列,接着应用程序中的消息循环在其消息队列中检索每个消息并发送给相应的窗口处理函数。
Windows 消息
typedef struct tagMsg{HWND hwnd; //接受该消息的窗口句柄 UINT message; //消息常量标识符,也就是我们通常所说的消息号 WPARAM wParam; //32 位消息的特定附加信息,确切含义依赖于消息值 LPARAM lParam; //32 位消息的特定附加信息,确切含义依赖于消息值 DWORD time; //消息创建时的时间 POINT pt; //消息创建时的鼠标/光标在屏幕坐标系中的位置}MSG;
①. 系统定义的消息:非用户定义的消息,范围在【0x0000,0x03ff】之间,又可以分为三小类:
窗口消息:与窗口的内部运作有关,创建窗口,绘制窗口,销毁窗口,一般以 WM_开头,如 WM_CREATE, WM_SIZE, WM_MOUSEMOVE 等标准的 Windows 消息
命令消息:一般特指 WM_COMMAND 消息,与处理用户请求有关,通常由控件或者菜单产生。
通知消息:特指 WM_NOTIFY 消息。通常指一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件(按钮、列表框、组合框、编辑框,以及化公共控件树状视图、列表视图)。
②. 应用定义的消息
WM_USER : 【0X0400-0X7FFF】, 用户自定义的消息范围。
WM_APP : 【0X8000-0XBFFF】,用于程序之间的消息通信。
RegisterWindoMessage :【0XC000-0XFFFF】消息类型的定义在 Winuser.h 中。
消息的发送消息的发送有 3 种方式:发送、寄送和广播
发送消息的函数有 SendMessage、SendMessageCallback、SendNotifyMessage、SendMessageTimeout;
寄送消息的函数主要有 PostMessage、PostThreadMessage、PostQuitMessage;
广播消息的函数有 BroadcastSystemMessage、BroadcastSystemMessageEx。
常用的发送消息的函数是 PostMessage 和 SendMessage,但为防止程序卡住可以使用 SendMessageTimeout 代替 SendMessage。三个函数的定义是:
参数:hWnd:其窗口程序接收消息的窗口的句柄。Msg:指定被寄送的消息。wParam:指定附加的消息特定的信息。IParam:指定附加的消息特定的信息。返回值:如果函数调用成功,返回非零值:如果函数调用失败,返回值是零。若想获得更多的错误信息,请调用 GetLastError 函数。备注:如果发送一个低于 WM_USER 范围的消息给异步消息函数(PostMessage.SendNotifyMessage,SendMesssgeCallback),消息参数不能包含指针。否则,操作将会失败。函数将再接收线程处理消息之前返回,发送者将在内存被使用之前释放。
函数原型:LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);
参数:hWnd:其窗口程序将接收消息的窗口的句柄。Msg:指定被发送的消息。wParam:指定附加的消息指定信息。IParam:指定附加的消息指定信息。返回值: 返回值指定消息处理的结果,依赖于所发送的消息。是同步消息函数。
函数功能:该函数将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,并且,如果指定的窗口属于不同的线程,直到窗口程序处理完消息或指定的超时周期结束函数才返回。如果接收消息的窗口和当前线程属于同一个队列,窗口程序立即调用,超时值无用。
参数:hWnd:其窗口程序将接收消息的窗口的句柄。Msg:指定被发送的消息。wParam:指定附加的消息指定信息。IParam:指定附加的消息指定信息。fuFlags:指定如何发送消息。此参数可为下列值的组合:SMTO_ABORTIFHUNG:如果接收进程处于“hung”状态,不等待超时周期结束就返回。SMTO_BLOCK:阻止调用线程处理其他任何请求,直到函数返回。SMTO_NORMAL:调用线程等待函数返回时,不被阻止处理其他请求。SMTO_NOTIMEOUTIFNOTHUNG:Windows 95 及更高版本:如果接收线程没被挂起,当超时周期结束时不返回。uTimeout:为超时周期指定以毫秒为单位的持续时间。如果该消息是一个广播消息,每个窗口可使用全超时周期。例如,如果指定 5 秒的超时周期,有 3 个顶层窗回未能处理消息,可以有最多 15 秒的延迟。IpdwResult:指定消息处理的结果,依赖于所发送的消息。返回值:如果函数调用成功,返回非零值。如果函数调用失败,或超时,返回值是零。若想获得更多的错误信息,请调用 GetLastError 函数。如果 GetLastError 返回零,表明函数超时。
代码示例
PostMessage,SendMessage,SendMessageTimeout 调用 win32gui 中的接口,返回值与上文中函数原型有区别,具体可参考
https://yiyibooks.cn/trs/meikunyuan6/pywin32/pywin32/PyWin32/win32gui.html
以 Dbgview.exe 打开 Filter 在 exclude 中写入过滤字符串,举例:
def buttonClickFunc1(hwnBbutton):'''点击按钮方法 1,使用 WM_LBUTTONDOWN 和 WM_LBUTTONUP 完成一次点击,hwnBbutton 是要点击的按钮的句柄。SendMessage 也可用。'''rstDown = win32gui.PostMessage(hwnBbutton, win32con.WM_LBUTTONDOWN, 0, 0)rstUp = win32gui.PostMessage(hwnBbutton, win32con.WM_LBUTTONUP, 0, 0)
def buttonClickFunc2(hwndParent, buttonID):'''点击按钮方法 2,使用命令消息 WM_COMMAND ,hwndParent 是按钮所在的父窗口,SendMessage 也可用。buttonID 传入 10 进制,如果是 16 进制要用 int("buttonID", 16)转换一下,如 int("3EC", 16) '''rst = win32gui.PostMessage(hwndParent, win32con.WM_COMMAND, win32con.BN_CLICKED<<16 | buttonID, buttonID)//备注:buttonID 可以通过 Spy++查看。
def getMenuItemText(menu, idx):'''获取 menu 中标题的内容'''import win32gui_structmii, extra = win32gui_struct.EmptyMENUITEMINFO()#新建一个空结构 win32gui.GetMenuItemInfo(menu, idx, True, mii)#将子菜单内容取到结构中 #解包结构 fytpe, fstate, wid, hsubmenu, hbmpchecked, hbmpuchecked, dwitemdata, text, hbmpitem = win32gui_struct.UnpackMENUITEMINFO(mii)return text
def opeMenu(hwndParent, indexTab, num, textStr):'''indexTab:菜单中第 indexTab 个 tab,tab 是从 0 开始编号的;num:第 indexTab 个 tab 中第 num 个菜单项,菜单项是从 0 开始编号的,注意,在菜单中横线也要计数;textStr:第 num 个菜单项的标题内容;'''hwnd_menu = win32gui.GetMenu(hwndParent)#取得 menu 句柄 hwnd_menu_sub_file = win32gui.GetSubMenu(hwnd_menu, indexTab)#取到 menu 的子菜单第 indexTab 项的句柄 ID_Setting = win32gui.GetMenuItemID(hwnd_menu_sub_file, num)#取到子菜单第 indexTab 项第 num 个的 IDtext_Setting = getMenuItemText(hwnd_menu_sub_file, num)if text_Setting == textStr:#取到标题的内容对比,一致继续操作 win32gui.PostMessage(hwndParent, win32con.WM_COMMAND, ID_Setting, 0)return True 如果有快捷键的菜单项,可以通过快捷键选择,这种操作比上面的方法简单很多:
win32gui.PostMessage(phwnd, win32con.WM_COMMAND, ID, 0)
ID 就是快捷键的代码。可以通过 Spy++抓取。
输入框输入 win32gui.SendMessageTimeout(hwndEdit, win32con.WM_SETTEXT, 0, testStr, win32con.SMTO_NORMAL, 1000)#
def geteditdata(hwnd,id):hwnd_file = win32gui.GetDlgItem(hwnd,id)bufLen = win32gui.SendMessage(hwnd_file, win32con.WM_GETTEXTLENGTH, 0, 0) + 1buffer = array.array('b', b'\x00\x00' * bufLen)text_len = win32gui.SendMessage(hwnd_file, win32con.WM_GETTEXT, bufLen, buffer)text = win32gui.PyGetString(buffer.buffer_info()[0], bufLen - 1)return text
完整代码 #coding:gbk
'''python3'''import osimport win32con, win32guiimport timeimport subprocessimport commctrl
#启动进程 def startProgram(tool_path):try:cmd = r'%s' % tool_pathrst = subprocess.Popen(cmd, shell = True)return rstexcept Exception as e:print (e)return#获取父窗口句柄 def findParentWindow(wndClass=None, wndText=None):hwnd = win32gui.FindWindow(wndClass, wndText)#print(hwnd)return hwnd#获取子窗口句柄 def findSubWindow(hwndParent, ID = None, wndClass=None, wndText=None):if ID:itmehwnd = win32gui.GetDlgItem(hwndParent, ID)print (itmehwnd)return itmehwndhwnd = win32gui.FindWindowEx(hwndParent, None, wndClass, wndText)if not hwnd:print ('%s 获取失败' % wndClass)return Falsereturn hwnd#点击按钮 def buttonClickFunc1(hwnBbutton):'''点击按钮方法 1,SendMessage 也可用'''rstDown = win32gui.PostMessage(hwnBbutton, win32con.WM_LBUTTONDOWN, 0, 0)rstUp = win32gui.PostMessage(hwnBbutton, win32con.WM_LBUTTONUP, 0, 0)if rstDown and rstUp is not None:return Falsereturn True#输入框输入字符串 def editSetText(hwndEdit, testStr):#WM_SETTEXT 不能用 PostMessage,PostMessage 参数中不能有指针。异步消息,不等待 rst = win32gui.SendMessageTimeout(hwndEdit, win32con.WM_SETTEXT, 0, testStr, win32con.SMTO_NORMAL, 1000)if rst[0]==1 and rst[1]==1:return Truereturn False#用快捷键打开菜单项 def opeToolTab(hwndParent, ID):rst = win32gui.PostMessage(phwnd, win32con.WM_COMMAND, ID, 0)print(rst)
if name == 'main':tool_path = r'C:\Users\Administrator\Desktop\Dbgview.exe'rst = startProgram(tool_path)if not rst:print("step 1:start Fail")sys.exit()print("step 1:start OK")time.sleep(3)phwnd = findParentWindow("dbgviewClass")#以窗口类名查找父窗口 print('step 2: find phwnd %s' % phwnd)opeToolTab(phwnd, 40022)#打开菜单项。40022 是 Dbgview 中 Ctrl+L 这个快捷键的虚拟代码,即 Filtertime.sleep(3)hwndsub = findParentWindow("#32770", "DebugView Filter")#查找 Filter 子窗口 print ("step 4:find hwndsub %s" % hwndsub)hwndEdit = findSubWindow(hwndsub, ID = 0x3EC, wndClass="ComboBox", wndText=None)# Filter 子窗口中 exclude 对应的 edit 是 combobox 的子窗口,可以用 combobox 直接 settext。所以这里找的是 combobox 的句柄 print ("step 5:find hwndEdit %s" % hwndsub)editSetText(hwndEdit, 'testStr')#将'testStr'字符串设置到 exclue 中 print ("step 6:setText")time.sleep(3)hwnBbutton = findSubWindow(hwndsub, ID = None, wndClass="Button", wndText="&OK")#查找 Filter 子窗口中 OK 按钮的句柄 print ("step 7:find hwnBbutton %s" % hwnBbutton)buttonClickFunc1(hwnBbutton) #点击 Filter 子窗口中 OK 按钮关闭子窗口 print ("step 8:click button")
点击下方链接免费领取:性能测试+接口测试+自动化测试+测试开发+测试用例+简历模板+测试文档
http://qrcode.testing-studio.com/f?from=infoQ&url=https://ceshiren.com/t/topic/22265
评论