自动化 Windows 窗体

摘要:您将在本文档中学习如何使用 Name 属性唯一标识 Microsoft Windows 窗体控件。本文档还将向您展示如何对 Visual Test 进行升级来处理 Windows 窗体。文档包括可供参考和应用的源代码,用于对现有自动化框架进行类似升级。文档还列举了一些 Windows 窗体本不支持的 Microsoft Win32 API。本文档未提供实现 Windows 窗体自动化的完全解决方案,未针对每个对 Windows 窗体控件不起作用的 Visual Test 方法提供替代方法,也未提供 Visual Test 所不具备的用于实现 Windows 窗体控件自动化的 Visual Test 类似接口。

适用范围:

Microsoft Visual Studio®
Windows 窗体
Visual Test
Active Accessibility

*

本页内容

引言 
永久标识问题 
问题的传统解决方案 
控件 ID 
标题和类名 
Windows 层次结构顺序 
Windows 窗体的推荐解决方案 
WM_GETCONTROLNAME Message 的正式规范 
使用 Visual Test 6.5 实现 Windows 窗体的自动化 
WFSupport.inc 源代码 
结论 
参考资料 

引言

自从 Windows 窗体首次发行以来,自动化框架对于开发人员来说一直是一种独特的挑战。现有的自动化框架很少能够不经过修改便实现 Windows 窗体的自动化。Windows 类名的动态特性以及缺少对常用的 Win32® API 的支持,导致自动化框架任务非常困难,特别是 Visual Test。为了简化问题,Windows 窗体向外部进程公开了控件的 Name 属性。这使得用户能够在测试中方便地标识控件。在本文档中,您将学习如何将标识控件的能力应用于 Visual Test,以提高 Windows 窗体的自动测试功能。

永久标识问题

为 Windows UI 编写自动测试时,在窗体中唯一标识控件非常重要,这样可在以后测试运行时找到控件并操纵它们。测试时应用程序的多个实例中标识符必须保持一致。理想情况是,标识符不应包括与区域相关的字符串,也不应该依赖于在产品生命周期中频繁更改的值,例如窗口层次结构顺序。

问题的传统解决方案

控件 ID

传统上来讲,UI 的标识符就是控件 ID。这是与区域不相关的值,在窗体的多个示例中保持一致,且对于百分之九十的时间都是唯一的。但是,在 Windows 窗体中,控件 ID 是该特定控件 HWND 的镜像。因此,每次启动窗体时控件 ID 都不同,所以不能使用它作为 UI 标识符。

标题和类名

控件 ID 不能实现此点,Windows UI 的另一最佳永久标识符是窗口标题名和类名的组合。这种解决方案在控件 UI 力不能及的大多数情况下都能生效,前提是在进行测试的所有不同区域中,您能够调整所查找的标题。这会增加额外的工作,而且它也不是完全的解决方案;某些控件的标题会有所改变,或者根本就没有标题。如果对话框中有多个控件标题和类名相同,则需要获取特定“标题+类名”组合的“Nth”实例。Windows 窗体的窗口类名的后一部分是动态的,这使问题更加复杂。根据用于自动化 UI 的框架的不同,有时根本无法通过窗口类名搜索控件,因为它是动态生成的,例如 WindowsForms10.BUTTON.app 3a。类名的“3a”部分是生成的,很有可能下次使用此按钮启动窗体时便有所不同。所以,除非框架能够进行类名子字符串匹配,否则这不是很好的选择。

AccName 和 AccRole第三种选择是 AccessibleName + AccessibleRole(MSAA 的概念)。类似于“标题+类名”,AccName 通常是本地化字符串,AccRole 也远不是唯一的。另外,搜索 MSAA 层次结构很慢,如果需要调用 Windows API 以获得 MSAA 接口未提供的信息,则调用 WindowFromAccessibleObject 以转换为 HWND 将非常繁琐。总之,如果框架使用本地化的 AccessibleNames,而且在多个元素具有相同名称和角色的情况下,您不介意通过搜索 AccessibleName + Role 组合的“Nth”来解决重复问题,对 Windows 窗体使用 PersistentID 则是可行的解决方案。

Windows 层次结构顺序

最后一个选择是在 Windows 树状层次结构中使用子控件顺序。换句话说,如果知道要单击的按钮是父主窗体的第三个子控件,可以使用带有合适参数的 GetWindow() API 查找按钮的 HWND,以得到第一个子控件的第二个同辈控件。在 Windows 窗体中,这当然能生效。但是,它非常容易出错,因为开发人员很容易将按钮移到 GroupBox 控件或面板中,从而向窗体添加其他控件打乱顺序,甚至为树状层次结构添加新级别。而且,Windows 不保证子控件顺序在操作系统的不同版本中保持一致,因此有些 Microsoft 操作系统可能在将来能改变子控件顺序,导致测试与操作系统不兼容。

Windows 窗体的推荐解决方案

Windows 窗体内部提供了解决永久标识问题的方法。解决方法是将窗体上控件的 Name 属性公开给进程外运行的自动工具。通过调用标准的 SendMessage() API 并传递WM_GETCONTROLNAME 消息可以实现这一点。本文档提供了示例代码,总结其过程为:必须注册 WM_GETCONTROLNAME 消息、浏览 Windows 树状层次结构,并向遇到的每个 HWND 发送消息。控件的内部 Name 属性将返回到 SendMessage() 调用的 LPARAM 中的缓冲区。然后可以比较此缓冲区与搜索的控件名称。Name 属性存储在应用程序的代码中,它没有本地化。另外,Visual Studio 设计时环境通过保持 Name 属性和代码中的实际变量标识符(例如 Button1、TreeView2 等)同步来强制属性的唯一性。因此,如果此属性不唯一,代码通常无法编译。

注意 这一规则也有例外,例如运行时动态生成的控件。

如果应用程序的开发人员没有使用 Visual Studio,或者在窗体中动态生成控件并将其添加到控件集中,则控件可能没有 Name 属性集。这将导致 SendMessage 调用在 LPARAM 中返回空字符串。这种情况下应采用的方法是强制开发人员将这些控件的 Name 属性设置为唯一字符串,或者使用文档前面提到的传统永久标识机制。

WM_GETCONTROLNAME Message 的正式规范

应用程序发送 WM_GETCONTROLNAME 消息以将Windows 窗体控件的对应名称复制到调用方提供的缓冲区中。

语法

要发送信息,调用下表显示的 SendMessage 功能。

lResult = SendMessage(

// 在 lResult 中返回 LRESULT

(HWND) hWndControl,

// 处理目标控件

(UINT) WM_GETCONTROLNAME,

// 消息 ID

(WPARAM) wParam,

// = (WPARAM) () wParam;

(LPARAM) lParam

// = (LPARAM) () lParam;

);

 

参数

  • WParam:指定要复制的 TCHAR 字符的最大数目,包括终止空字符。

  • LParam:指向用于接收控件名的缓冲区的指针。

返回值

返回值是复制的 TCHAR 字符的数目,不包括终止空字符。

使用 Visual Test 6.5 实现 Windows 窗体的自动化

如前所述,使用 Visual Test 的新实例实现 Windows 窗体的自动化很困难。而且在本文档出版时,Visual Test 不支持本地 AMD64 平台。但是,通过练习使用 Visual Test 来操作 Windows 窗体,您将理解如何升级现有的工具来处理 Windows 窗体的自动化。

可以使用 WM_GETCONTROLNAME 消息搜索控件。下面的示例展示了在给出特定 HWND 的情况下,如何获得 Windows 窗体控件名。

Function GetWindowsFormsID(wnd As Long) As String 
    ' 定义最终要包括的缓冲区
    ' 组件名。
    Dim bytearray as String * 65535
    Dim msg as Long
    Dim size As Long 
    ' 要分配的内存数量。
    Dim retLength As Long
    Dim retVal as String

    size = 65536 'Len(bytearray)

    msg = RegisterWindowMessage("WM_GETCONTROLNAME")
    ' 向控件的 HWND 发送消息以获取特定的
    ' 控件名
    retLength = SendMessage(wnd, msg, size, bytearray)

    ' 字符串作为 Unicode 返回。转换为多字节并存储在
    ' retVal. 中
    WideCharToMultiByte(CP_ACP, 0, bytearray, -1, retVal, retLength + 1, null, null)

    GetWindowsFormsID = retVal
End Function

要点 由于 Windows 不允许使用 SendMessage 跨越进程封送字符串,需要开发的实际代码比示例中显示的更复杂。WFSupport.inc 里的实际代码使用共享内存。但是,一旦有了此函数,编写递归例程浏览 Windows 树状层次结构并查找需要的控件就不太复杂了。这里包含了完整的函数递归部分,但是开发实际代码时必须将此函数放在超时循环中,以便支持多次搜索,从而减少测试中的超时问题。

Function FindWindowsFormsControlRecursive(startWnd As Long, controlName As String) as Long
    Dim childWnd As Long
    Dim tmpWnd As Long
    Dim retVal As Long
     ' 从第一个子控件开始。
    childWnd = GetWindow(startWnd, GW_CHILD)
    While childWnd <> 0 And retVal = 0
        ' 比较 WindowsFormsID 并查看是否为该控件。
        If GetWindowsFormsID(childWnd) <> controlName Then
            tmpWnd = childWnd
            ' 在子控件上进行深度优先递归。
            retVal = FindWindowsFormsControlRecursive(tmpWnd, controlName)
            childWnd = GetWindow(childWnd, GW_HWNDNEXT)
        Else
            ' 找到了。
            retVal = childWnd
            Exit While
        End if

    Wend
    
    FindWindowsFormsControlRecursive = retVal
End Function

如果给定了开始的 HWND 和控件名,可以使用此例程查找任何 Windows 窗体控件的 HWND,如下所示。

Dim controlHandle As Long
controlHandle = FindWindowsFormsControlRecursive( myStartWnd, "Button1")

下一个重要问题是 Windows 窗体控件中窗口类名的动态特点。控件的 Visual Test API 执行验证以确保您传递的 HWND 实际引用了该类型的已知控件。例如,如果在引用了SysTreeView32 的 HWND 上调用 WButtonClick(),这将不起作用。首先必须使用 WButtonSetClass() 将类名注册为有效类型的按钮。

如果 Windows 窗体按钮控件有静态控件名,只需调用 WButtonSetClass("WindowsForms10.BUTTON"),所有的 WButton* API 便能生效。但是,由于按钮控件具有动态类名而且 WButtonSetClass() 不支持前缀匹配,在测试时必须确定确切的类名。通过在 FindWindowsFormsControlRecursive 调用外部使用 FindControlAndClassName() 方法,可以实现这一点,后者将使用引用的参数返回按钮的 HWND 和类名。返回类名后,只需将其传递到 WButtonSetClass(),Visual Test 便可以与 Windows 窗体按钮进行交互。下面的示例显示了 WFndWFButton()

Function WFndWFButton(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WButtonSetClass(className)
    WFndWFButton = controlWnd
End Function

警告 前面的示例中增加了超时支持,所以如果控件需要花费几秒钟来显示,例程不会立即出错。

一些标准 Windows 窗体控件不支持所有的 Win32 API。这会导致 Visual Test 问题,因为 Win32 API 是 Visual Test 库后的主要驱动程序。例如,BM_GETSTATE 是不受支持的 Win32 API,它用于获得按钮状态、复选框、单选按钮等。在这种情况下,WFSupport.inc 中提供了附加方法来代替 Visual Test 的相应方法。大多数方法使用 MSAA 提取所需信息。下面的示例显示了用于代替 Visual Test 的 WCheckState() 的源代码。

Function WFCheckState(controlHwnd As String, timeout% = -1) as Integer
    Dim state as String
    
    state = AAGetState(controlHWnd, timeout)
    If Instr(state,"checked") > 0 Then
        WFCheckState = CHECKED
    ElseIf Instr(state, "mixed") > 0 Then
        WFCheckState = GRAYED
        Else
        WFCheckState = UNCHECKED
    End If

End Function

有关已知的不起作用的 Visual Test 函数,请参阅 WFSupport.inc 源代码中最后部分的替代函数。

WFSupport.inc 源代码

下面的示例包含用于帮助 Visual Test 自动化 Windows 窗体的支持例程。在任何项目中均可以使用这些例程并调用这些方法,以便搜索和操纵 Windows 窗体控件。

' 文件名:WFSupport.inc'$Include 'winapi.inc'
 Declare Function VirtualAllocEx Lib "kernel32" Alias "VirtualAllocEx" 
(hProcess As Long, lpAddress As Any, dwSize As Long, flAllocationType As 
Long, flProtect As Long) As Long
Declare Function VirtualFreeEx Lib "kernel32" Alias "VirtualFreeEx" 
(hProcess As Long, lpAddress As Any, dwSize As Long, dwFreeType As Long) 
As Long
Declare Function WriteProcessMemoryEx Lib "kernel32" Alias 
"WriteProcessMemory" (hProcess As Long, lpBaseAddress As Any, lpBuffer As 
Long, nSize As Long, lpNumberOfBytesWritten As Long) As Long
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, 
pSrc As Any, ByteLen As Long)
Declare Sub CopyMemoryA Lib "kernel32" Alias "RtlMoveMemory" (lpvDest As 
Any, lpvSource As Any, cbCopy As Long)
Declare Function WideCharToMultiByte Lib "kernel32" Alias 
"WideCharToMultiByte" (CodePage As Long, dwFlags As Long, lpWideCharStr As 
String, cchWideChar As Long, lpMultiByteStr As String, cchMultiByte As 
Long, lpDefaultChar As String, lpUsedDefaultChar As Long) As Long
'============NT 共享内存常量======================
Const PROCESS_VM_OPERATION = &H8
Const PROCESS_VM_READ = &H10
Const PROCESS_VM_WRITE = &H20
Const PROCESS_ALL_ACCESS = 0
Const VER_PLATFORM_WIN32_WINDOWS = 1
   Function FindWindowsFormsControlRecursive(startWnd As Long, controlName As String) as Long
    Dim childWnd As Long
    Dim tmpWnd As Long
    Dim retVal As Long
        ' 从第一个子控件开始。
          childWnd = GetWindow(startWnd, GW_CHILD)
         While childWnd <> 0 And retVal = 0
        ' 比较 WindowsFormsID 并查看是否为该控件
        ' 之后。
        If GetWindowsFormsID(childWnd) <> controlName Then
            tmpWnd = childWnd
            ' 在子控件上进行深度优先递归。
            retVal = FindWindowsFormsControlRecursive(tmpWnd, controlName)
            childWnd = GetWindow(childWnd, GW_HWNDNEXT)
        Else
            ' 找到了。
            retVal = childWnd
            Exit While
        End if

    Wend
    
    FindWindowsFormsControlRecursive = retVal
End Function

    Function FindWindowsFormsControl(startWnd As Long, controlName As String, timeout% = 50) as Long
    Dim retVal As Long
    Dim tmpWnd As Long
    Dim originalTimeout as Integer


    If timeout = -1 Then 
        ' 为什么 Visual Test 没有
        ' GetDefaultWaitTimeout 方法?
        originalTimeout = SetDefaultWaitTimeout(5)
        timeout = originalTimeout
        ' 重置超时。
        SetDefaultWaitTimeout(originalTimeout)
    End If
    
    While retVal = 0 And timeout > 0
        retVal = FindWindowsFormsControlRecursive(startWnd, controlName)
        
        ' 未找到,休眠并重试。
        If retVal = 0 Then
            Sleep 1
            timeout = timeout - 1
        End if
    Wend
    If retVal = 0 Then
        ' 需要再搜索一次(包括
        ' 超时为 0 的情况)。
        retVal = FindWindowsFormsControlRecursive(startWnd, controlName)
    End If
    
    FindWindowsFormsControl = retVal
End function

Function ByteArrayToString(bytes As String, length As Long) As String
    Dim retVal as String

    If IsWin9x() Then
        retVal = Left(bytes, Instr(1, bytes, Chr(0)) - 1)
    Else
        retVal = String$(length + 1, Chr(0))

        WideCharToMultiByte(CP_ACP, 0, bytes, -1, retVal, length + 1, null, null)
    End If

    ByteArrayToString = retVal

End Function
     '''-----------------------------------------------------------------------------
     ''' <summary>
     ''' 确定是否为 Win9x 机器
     ''' </summary>
     '''    <returns>如果是 Win9x,则为 True </returns>
     '''-----------------------------------------------------------------------------
      Function IsWin9x() as Bool

    Dim osVerInfo as OSVERSIONINFO

    osVerInfo.dwOSVersionInfoSize = 128 + 4 * 5

    GetVersionEx(osVerInfo)

    IsWin9x = osVerInfo.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS

End Function
     '''-----------------------------------------------------------------------------
     ''' <summary>''' 这种方法从给定的 HWND 提取 Windows 窗体的 Name 属性。''' </summary>'''    <param    name="wnd">目标窗口 </param>'''    <returns>使用字符串作为控件名</returns>
     '''-----------------------------------------------------------------------------
Function GetWindowsFormsID(wnd As Long) As String
    Dim PID As Long 'pid of the process that contains the control
    Dim msg as Long
       ' 定义最终要包括需要的   
       ' 组件名的缓冲区。    Dim bytearray as String * 65535
       ' 在目标进程中为缓冲区分配空间作为共享
       ' 内存。
     Dim bufferMem As Long 
      ' 缓冲区分配区的基址。
    Dim size As Long 
      ' 要分配的内存量。
    Dim written As Long 
      ' 写入内存的字节数。
    Dim retLength As Long
    Dim retVal As Long
    Dim errNum As Integer
    Dim errDescription As String


    size = 65536 'Len(bytearray)

    ' 完成创建和读取共享内存区域
    ' 在 Win9x 中与在新的 Oss 中有所不同。
    Dim processHandle As Long
    Dim fileHandle As Long

    msg = RegisterWindowMessage("WM_GETCONTROLNAME")

    If Not IsWin9x() Then
        On Local Error Goto Error_Handler_NT
            GetWindowThreadProcessId(wnd, VarPtr(PID))
            
            processHandle = OpenProcess(PROCESS_VM_OPERATION Or 
            PROCESS_VM_READ Or PROCESS_VM_WRITE, 0, PID)
            If processHandle = 0 Then
                Error Err, "OpenProcess API Failed"
            End If

            bufferMem = VirtualAllocEx(processHandle, 0, size, 
            MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE)
            If bufferMem = 0 Then
                Error Err, "VirtualAllocEx API Failed"
            End If

            ' 向控件的 HWND 发送消息以获取
            ' 指定控件的名称。
            retLength = SendMessage(wnd, msg, size, bufferMem)

            
            ' 从共享内存位置读取组件名。
            retVal = ReadProcessMemory(processHandle, bufferMem, bytearray, size, VarPtr(written))
            If retVal = 0 Then
                Error Err, "ReadProcessMemory API Failed"
            End If
            Error_Handler_NT:
            errNum = Err
            errDescription = Error$
            ' 释放分配的内存。
            retVal = VirtualFreeEx(processHandle, bufferMem, 0, MEM_RELEASE)
            If retVal = 0 Then
                Error Err, "VirtualFreeEx API Failed"
            End If
            CloseHandle(processHandle)
            If errNum <> 0 Then
                On Local Error Goto 0
                Error errNum, errDescription
            End If
        On Local Error Goto 0

    
    Else
       On Local Error Goto Error_Handler_9x

            fileHandle = CreateFileMapping(INVALID_HANDLE_VALUE, null, 
            PAGE_READWRITE, 0, size, null)
            If fileHandle = 0 Then
                Error Err, "CreateFileMapping API Failed"
            End If
            bufferMem = MapViewOfFile(fileHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0)
            If bufferMem = 0 Then
                Error Err, "MapViewOfFile API Failed"
            End If
            
            CopyMemory(bufferMem, bytearray, size)

            ' 向树状视图控件的 HWND 发送消息以获取
            ' 指定控件的名称。
            retLength = SendMessage(wnd, msg, size, bufferMem) 

            ' 从指定的共享内存为缓冲区读取控件名
            ' 控件名。
            CopyMemoryA(bytearray, bufferMem, 1024)

        Error_Handler_9x:
            errNum = Err
            errDescription = Error$

            ' 取消映射并关闭文件。
            UnmapViewOfFile(bufferMem)
            CloseHandle(fileHandle)

            If errNum <> 0 Then
                On Local Error Goto 0
                Error errNum, errDescription
            End If
        On Local Error Goto 0

    End If

    ' 获取控件名的字符串值。
    GetWindowsFormsID = ByteArrayToString(bytearray, retLength)

End Function
      Sub FindControlAndClassName(startWnd As Long, controlName As String, 
controlWnd As Long, className As String, timeout% = 1)
    Dim controlHandle As Long
    Dim info As INFO
    
    controlHandle = FindWindowsFormsControl(startWnd, controlName, timeout)
    WGetInfo controlHandle, info
    className = info.Class

    controlWnd = controlHandle

End Function
      '''-----------------------------------------------------------------------------
      '''    <name> WFndWF*</name>
      ''' <summary>
      ''' 这些是用于查找 Windows 窗体控件的 HWND 的函数。
      ''' </summary>
      '''    <param name="startWnd">要开始搜索的窗口句柄
      '''            注意:搜索中不包括此窗口,只包括其后代</param>
      '''    <param name="controlName">这是控件的 WindowsFormsID。
      '''                使用 Windows Forms Spy 工具获得 ID。注意这也是
      '''                代码中 Windows 窗体控件的 Name 属性。</param>
      '''    <returns>控件的窗口句柄</returns>
      '''-----------------------------------------------------------------------------
Function WFndWFCheck(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WCheckSetClass(className)
    WFndWFCheck = controlWnd
End Function

Function WFndWFCombo(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WComboSetClass(className)
    WFndWFCombo = controlWnd
End Function

Function WFndWFButton(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WButtonSetClass(className)
    WFndWFButton = controlWnd
End Function

Function WFndWFEdit(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WEditSetClass(className)
    WFndWFEdit = controlWnd
End Function

Function WFndWFHeader(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WHeaderSetClass(className)
    WFndWFHeader = controlWnd
End Function

Function WFndWFList(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WListSetClass(className)
    WFndWFList = controlWnd
End Function

Function WFndWFView(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WViewSetClass(className)
    WFndWFView = controlWnd
End Function

Function WFndWFMonthCal(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WMonthCalSetClass(className)
    WFndWFMonthCal = controlWnd
End Function

Function WFndWFOption(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WOptionSetClass(className)
    WFndWFOption = controlWnd
End Function

Function WFndWFPicker(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WPickerSetClass(className)
    WFndWFPicker = controlWnd
End Function

Function WFndWFProgress(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WProgressSetClass(className)
    WFndWFProgress = controlWnd
End Function

Function WFndWFScroll(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WScrollSetClass(className)
    WFndWFScroll = controlWnd
End Function

Function WFndWFSlider(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WSliderSetClass(className)
    WFndWFSlider = controlWnd
End Function

Function WFndWFSpin(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WSpinSetClass(className)
    WFndWFSpin = controlWnd
End Function

Function WFndWFStatic(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WStaticSetClass(className)
    WFndWFStatic = controlWnd
End Function

Function WFndWFStatus(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WStatusSetClass(className)
    WFndWFStatus = controlWnd
End Function

Function WFndWFTab(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WTabSetClass(className)
    WFndWFTab = controlWnd
End Function

Function WFndWFToolbar(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WToolbarSetClass(className)
    WFndWFToolbar = controlWnd
End Function

Function WFndWFTips(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WTipsSetClass(className)
    WFndWFTips = controlWnd
End Function

Function WFndWFTree(startWnd As Long, controlName As String, timeout% = -1) As Long
    Dim controlWnd as Long
    Dim className As String
    FindControlAndClassName(startWnd, controlName, controlWnd, className, timeout)
    WTreeSetClass(className)
    WFndWFTree = controlWnd
End Function
      '''-----------------------------------------------------------------------------
      ''' <summary>
      ''' WCheckState 函数的 Windows 窗体替代函数
      ''' </summary>
      '''    <param name="controlHwnd">该控件的传统 HWND
       Visual Test representation (e.g. "=1234")</param>
      '''    <returns>选中、未选中或变灰</returns>
      '''-----------------------------------------------------------------------------
      Function WFCheckState(controlHwnd As String, timeout% = -1) as Integer
    Dim state as String
    
    state = AAGetState(controlHWnd, timeout)
    If Instr(state,"checked") > 0 Then
        WFCheckState = CHECKED
    ElseIf Instr(state, "mixed") > 0 Then
        WFCheckState = GRAYED
    Else
        WFCheckState = UNCHECKED
    End If

End Function

      '''-----------------------------------------------------------------------------
      ''' <summary>
      ''' WoptionState 函数的 Windows 窗体替代函数
      ''' </summary>
      '''    <param name="controlHwnd">该控件的传统 HWND
       Visual Test representation (e.g. "=1234")</param>
      '''    <returns>选中或未选中</returns>
      '''-----------------------------------------------------------------------------
Function WFOptionState(controlHwnd As String, timeout% = -1) as Integer
    Dim state as String
    
    state = AAGetState(controlHWnd, timeout)
    If Instr(state,"checked") > 0 Then
        WFOptionState = CHECKED
    Else
        WFOptionState = UNCHECKED
    End If

End Function

结论

对 Windows 窗体来说,在对话框中自动标识 Windows 控件的传统方法不能很好地生效。但是,控件的 Name 属性(通过 WM_GETCONTROLNAME 消息访问)给出了几乎总是唯一的与区域无关的永久标识符。文中的代码片断显示了如何调整 Visual Test 以便利用此消息,但您可以对其他 Windows UI 自动化框架进行类似的调整操作来测试 Windows 窗体。

如有任何关于自动化 Windows 窗体的问题,请参阅 http://www.windowsforms.net/(英文)中的社区论坛。

posted on   荣锋亮  阅读(532)  评论(0编辑  收藏  举报

(评论功能已被禁用)
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示