写点什么

Windows-Python 应用: 使用消息操作窗口

  • 2022 年 9 月 28 日
    北京
  • 本文字数:6205 字

    阅读完需:约 20 分钟

在 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 中。

  1. 消息的发送

消息的发送有 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_struct mii, 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个的ID    text_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++抓取。

  1. 输入框输入

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) + 1    buffer = 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
复制代码
  1. 完整代码

#coding:gbk
'''python3'''import osimport win32con, win32guiimport timeimport subprocessimport commctrl
#启动进程def startProgram(tool_path): try: cmd = r'%s' % tool_path rst = subprocess.Popen(cmd, shell = True) return rst except 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 itmehwnd hwnd = win32gui.FindWindowEx(hwndParent, None, wndClass, wndText) if not hwnd: print ('%s获取失败' % wndClass) return False return 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 False return 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 True return 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这个快捷键的虚拟代码,即Filter time.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")
复制代码

更多学习资料戳下方!!!

https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=infoQ&timestamp=1662366626&author=xueqi

用户头像

社区:ceshiren.com 2022.08.29 加入

微信公众号:霍格沃兹测试开发 提供性能测试、自动化测试、测试开发等资料、实事更新一线互联网大厂测试岗位内推需求,共享测试行业动态及资讯,更可零距离接触众多业内大佬

评论

发布
暂无评论
Windows-Python 应用:使用消息操作窗口_测试_测吧(北京)科技有限公司_InfoQ写作社区