使用windows api函数捕获SAP session的左下角消息句柄

  背景:SAP session的左下角消息很是有用,咱们在作SAP的自动化脚本时能够设法读到这个消息的内容,做为程序后续动做的判断条件。以下图:数据库

      

  好比小爬以前给财务的同事制做了一个批量导出SAP各种报表的脚本工具:基于公司IT团队用ABAP编写的这几张表,SAP每次执行完导表动做,数据传输过程,左下角消息为“Transferring package1 of 1...”,当表格数据完整导出后,则显示“已传输N个字节”。咱们的脚本能够去捕获“已传输...”这个消息,来判断报表内容是否已经完整导出,来决定是否要导出下一张报表。windows

  事实上,经过原生的“脚本录制与回放”,咱们能够获得这个消息:语法为:sapMessage=session.findById("wnd[0]/sbar").text。很是简单实用!实际编写脚本过程当中遇到的问题是,当咱们的脚本动态地顺序往下执行到等待报表出来的过程,控制权交到了导出的excel文件,咱们的sapMessage=session.findById("wnd[0]/sbar").text没法得到执行,若是该消息文本刚好做为脚本中循环退出的条件,则脚本程序由于循环没法退出,致使界面卡死。api

  我想到的解决方法是,前期的参数输入等都经过脚本录制功能生成代码,到了点击“执行(F8)”这个动做,改由window api sendMessage控制,那么后续的控制权都在VB脚本下,咱们再经过window api来捕获sap的窗口消息,而非sap原生提供的“session.findById("wnd[0]/sbar").text”方法。网络

  咱们须要捕获 sap session的窗口句柄,而后对其发送快捷键“F8”便可。在VBA中能够这样写:session

Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal Hwnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Public Const WM_KEYUP = &H101 '释放
Public Const WM_KEYDOWN = &H100 '按下
Public Const VK_F8 = &H77
button_F8 = FindWindowEx(0, 0, "SAP_FRONTEND_SESSION", vbNullString) ‘类名经过spy++捕获
SendMessage button_F8, WM_KEYDOWN, VK_F8, 0
SendMessage button_F8, WM_KEYUP, VK_F8, 0

  这段代码执行完后,程序就至关于给SAP发送了”F8“快捷键,执行导表。考虑到网络、数据库压力等缘由,每次导表须要的时间并不肯定,咱们须要拿到报表已经完整导出的标记,而后关闭SAP自动打开的报表(若是每次SAP自动打开的excel报表不关闭,一次导出的报表数量过多时,电脑和程序都有可能卡死),方能执行下一次导表动做。函数

  如今重点来了,如何捕获SAP左下角的消息”已传输N个字节“来做为程序判断的条件。工具

小爬一开始使用spy++来捕获该消息句柄,如图:oop

  惋惜每次执行SAP程序,该类名是动态变化的,因此基于类名来Findwindow显然定位不到它。学习

小爬接着观察spy++:测试

  它老是出现”SAP 轻松访问“这个主session窗口句柄的第7个子窗口,因而咱们能够循环使用7次FindwindowEX,就能找到该消息窗口的句柄msgHwnd,如:

Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal Hwnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Public Function mGetWindow(ByVal cName As String, ByRef fText As String) As Long
    Dim myStr As String * 100
    Dim strLen As String
    Dim bWnd As Long
    Dim bWndback As Long
    Dim a As Integer
    strLen = Len(myStr)
    mGetWindow = 0
    Do        
        '获取子窗口标志
        bWnd = FindWindowEx(0, bWndback, cName, vbNullString)
        'List1.AddItem bWnd
        If bWnd <> 0 Then
        '获取子窗口标题
            GetWindowText bWnd, myStr, strLen
                myStr = Trim(myStr)            
            If InStrRev(myStr, fText) Then
                mGetWindow = bWnd
                Exit Do            
            Else
                Sleep 200
            End If          
        End If
     bWndback = bWnd
    Loop
End Function

    sessionHwnd = mGetWindow("SAP_FRONTEND_SESSION", "SAP 轻松访问")
    '获得左下角消息文本对应句柄,该句柄在SAP登陆后不会变化
    EnumChildWind = -1
    Do
    hwdChild = FindWindowEx(sessionHwnd, hwdChild, vbNullString, vbNullString) '水平滚动条的句柄
        EnumChildWind = EnumChildWind + 1
    Loop Until EnumChildWind = 6    'sap左下角消息控件位于主界面下的child索引号老是为6(从0开始)
    msgHwnd = hwdChild

  小爬给同事用的脚本工具稳定地管了一年多。上个月财务月结,却陆续有同事告诉我工具无法使用,动不大就卡死了。可这一年时间,公司的SAP版本(74.0)还有excel版本(excel2010)以及系统版本(win7 X64)都没有发生变化。小爬思前想后,未果!

  近期,小爬捋了捋思路,仔细比对,终于发现其中端倪。原来是时间久了,不少同事用腻了原生的sap 主题和配色、字体等,而后选择了其余主题。您可能问了,主题变了,也会影响页面元素?整个捕获的逻辑不都是基于id、索引这些相对固定的东西定位的吗?

  还真就巧了,通过SPY++探测窗口句柄发现,这个消息句柄在改动主题后的sap内,是位于sap session的第6个子窗口,而非先前的第7个。

  小爬本想编写严格的操做手册,让用户改回默认的SAP主题和字体,去解决此问题。可是考虑到每一个用户审美不一样,且长时间只用一个主题确实会审美疲劳进而影响工做效率。小爬决定仍是克难攻坚,使该脚本能兼容不一样的SAP主题。

  仔细观察SPY++,知道“SAP 轻松访问”的窗口句柄后,定位到“Afx Wnd110”类名的窗口,顺序FindwindowEx,当类名再也不是“Afx Wnd110"后的第二个子窗口,就是咱们要找的”sap左下角消息窗口“,这个逻辑在不一样SAP主题下恒成立。代码层面该如何实现呢?

 

  想要基于类名判断,那如何获得窗口的类名呢?这就须要涉及GetClassName 这个api函数了:

Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Integer) As Integer
Public Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
Public Const GW_HWNDNEXT = &H2 '寻找下一个具备相同关系的窗口,或寻找下一个具备相同关系的顶级窗口
Public sessionHwnd As Long, hwdChild As Long, msgHwnd As Long
sessionHwnd = mGetWindow("SAP_FRONTEND_SESSION", "SAP 轻松访问")

    '获得左下角消息文本对应句柄,该句柄在SAP登陆后不会变化

    hwdChild = FindWindowEx(sessionHwnd, hwdChild, vbNullString, vbNullString) '获得第一个子窗口句柄
    Do
        hwdChild = FindWindowEx(sessionHwnd, hwdChild, vbNullString, vbNullString) '水平滚动条的句柄
         '得到实际的类名字符长度
        iLEN = GetClassName(hwdChild, sCN, 256)
         '提取实际的类名
        sCN = Left(sCN, iLEN)
    Loop Until sCN <> "AfxWnd110"    '从上往下,最后一个类名为AfxWnd110的窗口往下两个窗口就是咱们要找的sap消息窗口
    msgHwnd = GetWindow(hwdChild, GW_HWNDNEXT)  '获得sap消息窗口的句柄,全局变量

  上面的代码中,GetWindow函数第二个参数GW_HWNDNEXT 表明:寻找下一个具备相同关系的窗口,或寻找下一个具备相同关系的顶级窗口。

经过上面的代码,咱们终于获得了SAP的左下角消息句柄,紧接着,咱们的程序在导表过程当中等待时长就能够这样判断:

   Do While InStr(msg01, "") = 0
        SendMessage msgHwnd, WM_GETTEXT, 128, ByVal msg01
        Sleep 500
    Loop

  之因此用Instr函数,是由于”已传输N个字节“的N值每次不一样,须要模糊匹配字符串。脚本每隔500毫秒就取出msgHwnd的文本,与预设的”已传输..."做模糊匹配,而代码中的""则是由于win7下sap的字符集解码问题,咱们能够不用解决字符集的解码,只需在脚本的DO While判断条件中将错就错,便可。

  通过一番测试和验证,终于解决了该自动化脚本针对SAP不一样主题的兼容性问题,大功告成!无数次的经验告诉咱们,只会用sap元素的脚本录制与回放功能,对于自动化工做是远远不够的,不少时候,咱们要把它和windows底层的user32 api 函数结合来使用,才能解决大多数实际问题。上面的代码只是解决问题的其中一个思路,供借鉴,一块儿学习!

相关文章
相关标签/搜索