自动化Windows 窗体

 

布赖恩·麦克马斯特
Windows 窗体
Microsoft Corporation

2004 年 3 月

总结:本文档介绍如何使用 Name 属性唯一标识 Microsoft Windows 窗体 控件。 你将了解如何升级视觉测试以处理Windows 窗体。 该文档包含源代码,你可以移植这些代码并应用这些代码,以便对现有自动化框架进行类似的升级。 本文档还列出了一些Windows 窗体本身不支持的 Microsoft Win32 API。 本文档未提供用于自动执行Windows 窗体的完整解决方案、不可用于Windows 窗体控件的每个视觉测试方法的替换方法,或提供类似于用于自动化Windows 窗体控件的接口,而 Visual Test 没有等效项。 ) (23 个打印页

适用于:

   Microsoft Visual Studio®
   Windows 窗体
   视觉测试
   Active Accessibility

目录

简介
持久性标识问题
问题的传统解决方案
   控制 ID
   标题和类名
   AccName 和 AccRole
   Windows 层次结构顺序
适用于Windows 窗体的建议解决方案
WM_GETCONTROLNAME消息的正式规范
使用 Visual Test 6.5 自动执行Windows 窗体
WFSupport.inc 源代码
结论
书籍

简介

自 Windows 窗体 首次发布以来,开发人员在自动化框架方面面临着独特的挑战。 现有的自动化框架很少可以在不进行修改的情况下自动Windows 窗体。 Windows 类名的动态特性,加上缺少对一些常见 Win32® API 的支持,可能会给自动化框架(尤其是视觉测试)带来困难。 为了简化操作,Windows 窗体向外部进程公开控件的 Name 属性。 这使你能够方便地识别测试中的控件。 本文档介绍如何将识别控件的功能应用于可视化测试,以增强Windows 窗体的自动测试功能。

持久性标识问题

为 Windows UI 编写自动测试时,必须能够唯一地标识窗体上的控件,以便以后可以在测试用例运行时找到这些控件来操作它们。 标识符必须在受测应用程序的多个实例中持久保存。 理想情况下,标识符不应包含与区域设置相关的字符串,并且不应依赖于在整个产品生命周期中经常更改的内容,例如窗口层次结构排序。

问题的传统解决方案

控制 ID

传统上,UI 的标识符是控件 ID。 这是一个独立于区域设置的解决方案,在表单的多个实例中持久保存,在 90% 的时间内是唯一的。 但是,在 Windows 窗体 中,控件 ID 是该特定控件的 HWND 镜像图像。 因此,每次启动窗体时控件 ID 都不同,因此不能将其用作 UI 标识符。

标题和类名

当控件 ID 失败时,Windows UI 的下一个最佳持久标识符是窗口描述文字和类名的组合。 此解决方案在控件 ID 失败的大多数情况下效果良好,前提是你可以在要测试的所有不同区域设置上调整要查找的描述文字。 这可以添加一些额外的工作,再次,它不是一个完整的解决方案:某些控件具有更改的标题,或者根本没有字幕。 这要求获取特定 Caption+ClassName 组合的“第 N 个”实例,前提是对话框中有多个具有相同描述文字和类名对的控件。 Windows 窗体使窗口类名的尾随部分成为动态部分,使此问题复杂化。 根据你用于自动化 UI 的框架,这会使无法按窗口类名称搜索控件,因为它是动态生成的,例如 WindowsForms10.BUTTON.app3a。 将生成类名的“3a”部分,并且很可能在下次启动带有此按钮的窗体时有所不同。 因此,除非框架能够对类名执行子字符串匹配,否则这不是一个选项。

AccName 和 AccRole

第三个选项是 AccessibleName + AccessibleRole (MSAA) 的概念。 与 Caption+ClassName 类似, AccName 通常是本地化字符串, AccRole 本身远非唯一。 此外,搜索 MSAA 层次结构的速度很慢,如果你曾经需要调用 Windows API 来获取某些未通过 MSAA 接口提供的信息,则调用 WindowFromAccessibleObject 转换为 HWND 很繁琐。 总之,如果框架将本地化 AccessibleNames,并且你不介意在有多个具有相同名称和角色的元素的情况下通过搜索“第 N 个”AccessibleName + 角色对来解析重复项,则这是适用于 Windows 窗体 的 PersistentID 的可行解决方案。

Windows 层次结构顺序

最后一种选择是在 Windows 树层次结构中使用子排序。 也就是说,如果你知道要单击的按钮是父main窗体的第三个子级,则可以通过使用 GetWindow () API 的适当参数来查找该按钮的 HWND 来获取第一个子级的第二个同级。 这当然适用于Windows 窗体。 但是,这显然容易出错,因为开发人员可以轻松地将另一个控件添加到窗体中以取消排序,甚至通过将按钮移动到 GroupBox 控件或 面板来向树层次结构添加新级别。 此外,Windows 不保证子排序在不同版本的操作系统之间保持一致,因此可以想象,某些 Microsoft 操作系统将来可能会改变顺序,使测试与操作系统不兼容。

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

注意 此规则有例外情况,例如在运行时动态生成的控件。

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

WM_GETCONTROLNAME消息的正式规范

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

语法

若要发送此消息,请调用 SendMessage 函数,如下表所示。

 lResult = SendMessage( 
    // returns LRESULT in lResult 
   (HWND) hWndControl, 
    // handle to destination control 
   (UINT) WM_GETCONTROLNAME, 
    // message ID 
   (WPARAM) wParam, 
    // = (WPARAM) () wParam;
   (LPARAM) lParam 
    // = (LPARAM) () lParam;
); 
 

参数

  • WParam:指定要复制的最大 TCHAR 字符数,包括终止 null 字符。
  • LParam:指向要接收控件名称的缓冲区的指针。

返回值

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

使用 Visual Test 6.5 自动执行Windows 窗体

如前所述,很难使用新的视觉测试实例自动Windows 窗体。 此外,在本文档发布时,视觉测试不支持本机 AMD64 平台。 但是,通过完成调整视觉测试以使用Windows 窗体的练习,你可以对如何升级任何现有工具以处理自动化Windows 窗体有所了解。

可以使用 WM_GETCONTROLNAME 消息搜索控件。 以下示例演示如何获取给定特定 HWND 的Windows 窗体控件名称。

Function GetWindowsFormsID(wnd As Long) As String 
    ' Define the buffer that will eventually contain the desired
    ' component's name.
    Dim bytearray as String * 65535
    Dim msg as Long
    Dim size As Long 
    ' The amount of memory to be allocated.
    Dim retLength As Long
    Dim retVal as String

    size = 65536 'Len(bytearray)

    msg = RegisterWindowMessage("WM_GETCONTROLNAME")
    ' Send message to the control's HWND for getting the specified
    ' control name.
    retLength = SendMessage(wnd, msg, size, bytearray)

    ' The string comes back as Unicode. Convert to MultiByte and store in
    ' 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

    ' Start with the first child.
    childWnd = GetWindow(startWnd, GW_CHILD)
    While childWnd <> 0 And retVal = 0
        ' Compare the WindowsFormsID and see if this is the control.
        If GetWindowsFormsID(childWnd) <> controlName Then
            tmpWnd = childWnd
            ' Do depth-first recursion on the children.
            retVal = FindWindowsFormsControlRecursive(tmpWnd, controlName)
            childWnd = GetWindow(childWnd, GW_HWNDNEXT)
        Else
            ' Found it.
            retVal = childWnd
            Exit While
        End if

    Wend
    
    FindWindowsFormsControlRecursive = retVal
End Function

然后,可以使用此例程在给定起始 HWND 和控件名称的情况下查找任何Windows 窗体控件的 HWND,如以下示例所示。

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

下一个大问题是Windows 窗体控件的窗口类名的动态性质。 控件的视觉测试 API 执行验证,以确保传递它们的 HWND 实际引用该类型的已知控件。 例如,如果在引用 SysTreeView32 的 HWND 上调用 WButtonClick () ,它将不起作用。 首先必须使用 WButtonSetClass () 将类名注册为按钮的有效类型。

如果Windows 窗体按钮控件具有静态控件名称,则只需调用 WButtonSetClass (“WindowsForms10.BUTTON”) ,所有 WButton* API 都可以正常工作。 但是,由于按钮控件具有动态类名,并且 WButtonSetClass () 不支持前缀匹配,因此必须在测试运行时弄清楚类名。 为此,可以使用名为 FindControlAndClassName () 的方法包装 FindWindowsFormsControlRecursive 调用,该方法使用引用参数返回 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。 这会导致视觉测试出现问题,因为 Win32 API 是可视化测试库背后的主要驱动程序。 例如,BM_GETSTATE是一个不受支持的 Win32 API,用于获取按钮、检查框、单选按钮等的状态。 在这种情况下, WFSupport.inc 中提供了其他方法来替换视觉测试等效项。 其中大多数方法使用 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

有关无效的已知视觉测试函数的列表,请参阅 WFSupport.inc 源代码底部部分中的替换函数。

WFSupport.inc 源代码

以下示例包含支持例程,以帮助 Visual Test 自动执行Windows 窗体。 可以在任何项目中包括这些例程,并调用 方法来搜索和操作Windows 窗体控件。

'=======================================================
' File name: 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 Shared memory constant======================
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

    ' Start with the first child.
    childWnd = GetWindow(startWnd, GW_CHILD)
    While childWnd <> 0 And retVal = 0
        ' Compare the WindowsFormsID and see if this is the control
        ' we are after.
        If GetWindowsFormsID(childWnd) <> controlName Then
            tmpWnd = childWnd
            ' Do depth-first recursion on the children.
            retVal = FindWindowsFormsControlRecursive(tmpWnd, controlName)
            childWnd = GetWindow(childWnd, GW_HWNDNEXT)
        Else
            ' Found it.
            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 
        ' Why does  Visual Test not have a
        ' GetDefaultWaitTimeout method?
        originalTimeout = SetDefaultWaitTimeout(5)
        timeout = originalTimeout
        ' Reset the timeout.
        SetDefaultWaitTimeout(originalTimeout)
    End If
    
    While retVal = 0 And timeout > 0
        retVal = FindWindowsFormsControlRecursive(startWnd, controlName)
        
        ' If we did not find it, sleep and try again.
        If retVal = 0 Then
            Sleep 1
            timeout = timeout - 1
        End if
    Wend

    If retVal = 0 Then
        ' Need to search one more time (this covers the case where
        ' timeout is 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>
''' Determine if we are on a Win9x machine or not
''' </summary>
'''    <returns>True if this is a flavor of Win9x</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>
''' This method extracts the Windows Forms Name property from the given HWND.
''' </summary>
'''    <param name="wnd">target window</param>
'''    <returns>The name of control as a string.</returns>
'''-----------------------------------------------------------------------------
Function GetWindowsFormsID(wnd As Long) As String
    Dim PID As Long 'pid of the process that contains the control
    Dim msg as Long

    ' Define the buffer that will eventually contain the desired
    ' component's name.
    Dim bytearray as String * 65535

    ' Allocate space in the target process for the buffer as shared
    ' memory.
    Dim bufferMem As Long 
    ' Base address of the allocated region for the buffer.
    Dim size As Long 
    ' The amount of memory to be allocated.
    Dim written As Long 
    ' Number of bytes written to memory.
    Dim retLength As Long
    Dim retVal As Long
    Dim errNum As Integer
    Dim errDescription As String


    size = 65536 'Len(bytearray)

    ' Creating and reading from a shared memory region is done
    ' differently in Win9x than in newer 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

            ' Send message to the control's HWND for getting the
         ' Specified control name.
            retLength = SendMessage(wnd, msg, size, bufferMem)

            
            ' Now read the component's name from the shared memory location.
            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$
            ' Free the memory that was allocated.
            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)

            ' Send message to the treeview control's HWND for 
            ' getting the specified control's name.
            retLength = SendMessage(wnd, msg, size, bufferMem) 

            ' Read the control's name from the specific shared memory
            ' for the buffer.
            CopyMemoryA(bytearray, bufferMem, 1024)

        Error_Handler_9x:
            errNum = Err
            errDescription = Error$

            ' Unmap and close the file.
            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

    ' Get the string value for the Control name.
    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>
''' These are the functions you use to find the HWnds of Windows Forms controls.
''' </summary>
'''    <param name="startWnd">window handle of where you want to start your search
'''            NOTE: this window is not included in the search, only the descendants </param>
'''    <param name="controlName">This is the WindowsFormsID of the control.
'''                Use the Windows Forms Spy tool to get the ID.  Note that this is also
'''                the "Name" property of the Windows Forms control in code.</param>
'''    <returns>The window handle of the control</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>
''' Windows Forms replacement for WCheckState function
''' </summary>
'''    <param name="controlHwnd">The HWnd of this control in traditional  
       Visual Test representation (e.g. "=1234")</param>
'''    <returns>CHECKED, UNCHECKED, or GRAYED</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>
''' Windows Forms replacement for WOptionState function
''' </summary>
'''    <param name="controlHwnd">The HWnd of this control in traditional 
       Visual Test representation (e.g. "=1234")</param>
'''    <returns>CHECKED or UNCHECKED</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 消息访问)为你提供一个与区域设置无关的持久性标识符,该标识符几乎总是唯一的。 本文中的代码片段演示如何调整视觉测试以利用此消息,但你可以执行类似操作来调整任何其他 Windows UI 自动化框架来测试Windows 窗体。

如果对自动执行Windows 窗体有任何疑问或疑问,请参阅 中的http://www.windowsforms.net/社区论坛。

书籍