Automatización de Windows Forms

 

Brian McMaster
Windows Forms
Microsoft Corporation

Marzo de 2004

Resumen: En este documento aprenderá a identificar de forma única los controles de Microsoft Windows Forms mediante la propiedad Name. Se le mostrará cómo actualizar Visual Test para controlar Windows Forms. El documento contiene código fuente que puede portar y aplicar para realizar actualizaciones similares a los marcos de automatización existentes. En este documento también se enumeran algunas de las API de Microsoft Win32 que Windows Forms no admiten intrínsecamente. Este documento no proporciona una solución completa para automatizar Windows Forms, métodos de reemplazo para cada método de prueba visual que no funciona en controles Windows Forms o una interfaz similar a Visual Test para automatizar Windows Forms controles para los que Visual Test no tiene ningún equivalente. (23 páginas impresas)

Se aplica a:

   Microsoft Visual Studio®
   Windows Forms
   Prueba visual
   Active Accessibility

Contenido

Introducción
Problema de identificación persistente
Soluciones tradicionales al problema
   Id. de control
   Título y nombre de clase
   AccName y AccRole
   Orden de jerarquía de Windows
Solución recomendada para Windows Forms
Especificación formal del mensaje de WM_GETCONTROLNAME
Automatización de Windows Forms con Visual Test 6.5
Código fuente de WFSupport.inc
Conclusión
Libros

Introducción

Desde la versión inicial de Windows Forms, los desarrolladores han enfrentado desafíos únicos con sus marcos de automatización. Algunos marcos de automatización existentes pueden automatizar Windows Forms sin modificaciones. La naturaleza dinámica de los nombres de clase de Windows, junto con la falta de compatibilidad con algunas API comunes de Win32® puede causar dificultades para los marcos de automatización, en particular Visual Test. Para facilitar las cosas, Windows Forms expone la propiedad Name de los controles a procesos externos. Esto le permite identificar convenientemente los controles de las pruebas. En este documento, aprenderá a aplicar la capacidad de identificar controles en Visual Test para mejorar las características de pruebas automatizadas de Windows Forms.

Problema de identificación persistente

Al escribir pruebas automatizadas para la interfaz de usuario de Windows, es importante que pueda identificar controles de forma única en un formulario para que más adelante pueda encontrar esos controles para manipularlos en tiempo de ejecución de caso de prueba. El identificador debe ser persistente en varias instancias de la aplicación que se está probando. Idealmente, el identificador no debe incluir una cadena dependiente de la configuración regional y no debe depender de algo que pueda cambiar con frecuencia a lo largo del ciclo de vida del producto, como la ordenación de la jerarquía de ventanas.

Soluciones tradicionales al problema

Id. de control

Tradicionalmente, el identificador de la interfaz de usuario era el identificador de control. Se trataba de una solución independiente de la configuración regional, que se conservaba en varias instancias del formulario y era única durante el 90 por ciento del tiempo. Sin embargo, en Windows Forms, el identificador de control es una imagen reflejada del HWND para ese control determinado. Por lo tanto, el identificador de control es diferente cada vez que se inicia el formulario, por lo que no se puede usar como identificador de la interfaz de usuario.

Título y nombre de clase

Cuando se produjo un error en el identificador de control, el siguiente identificador persistente para la interfaz de usuario de Windows era la combinación de la ventana subtítulo y el nombre de clase. Esta solución funcionó bien en la mayoría de los casos en los que se produjo un error en el identificador de control, siempre que pudiera ajustar el subtítulo que buscaba en todas las distintas configuraciones regionales en las que estaba probando. Esto puede agregar algún trabajo adicional y, de nuevo, no era una solución completa; algunos controles tienen subtítulos que cambian o no tienen títulos en absoluto. Esto requiere que obtenga la instancia "Nth" de una combinación de Caption+ClassName determinada si había varios controles en el cuadro de diálogo con pares de nombre de clase y subtítulo idénticos. Windows Forms complica este problema haciendo que la parte final del nombre de la clase de ventana sea dinámica. En función del marco que use para automatizar la interfaz de usuario, esto puede hacer que la búsqueda de un control por su nombre de clase de ventana sea imposible, ya que se genera dinámicamente, por ejemplo, WindowsForms10.BUTTON.app3a. Se genera la parte "3a" del nombre de clase y probablemente será diferente la próxima vez que inicie el formulario con este botón. Por lo tanto, a menos que el marco tenga la capacidad de realizar la coincidencia de subcadenas en el nombre de clase, no es una opción.

AccName y AccRole

Una tercera opción era AccessibleName + AccessibleRole (conceptos de MSAA). De forma similar a Caption+ClassName, accName era normalmente una cadena localizada y AccRole estaba lejos de ser única por sí misma. Además, la búsqueda de la jerarquía de MSAA era lenta y era tediosa llamar a WindowFromAccessibleObject para convertir a un HWND si alguna vez necesitaba llamar a una API de Windows para obtener información que no se proporcionó a través de la interfaz de MSAA. En todo, siempre que el marco localice los Nombres accesibles y no le importa resolver duplicados buscando el par "Nth" AccessibleName + Role en una situación en la que hay varios elementos con el mismo nombre y rol, se trata de una solución viable para un PersistentID para Windows Forms.

Orden de jerarquía de Windows

Una opción final era usar el orden secundario en la jerarquía de árboles de Windows. Es decir, si sabía que el botón que quería hacer clic era el tercer elemento secundario del formulario principal primario, podría obtener el segundo elemento del mismo nivel del primer elemento secundario mediante los parámetros adecuados para la API GetWindow() para buscar el HWND del botón. Esto ciertamente funciona en Windows Forms. Sin embargo, obviamente es propenso a errores porque un desarrollador puede agregar fácilmente otro control al formulario para iniciar la ordenación, o incluso agregar un nuevo nivel a la jerarquía de árbol moviendo el botón a un control GroupBox o Panel. Además, Windows no garantiza que la ordenación secundaria sea coherente en diferentes versiones del sistema operativo, por lo que es posible que algún sistema operativo de Microsoft pueda modificar el orden en el futuro, lo que hace que las pruebas sean incompatibles con el sistema operativo.

Windows Forms internamente proporciona una manera de resolver el problema del identificador persistente. La solución es que exponga la propiedad Name de los controles de un formulario a herramientas automatizadas que se están quedando sin proceso. Puede hacerlo a través de una llamada API sendMessage() estándar que pasa el mensaje de WM_GETCONTROLNAME . El código de ejemplo se proporciona en este documento, pero para resumir el proceso, debe registrar el mensaje de WM_GETCONTROLNAME , examinar la jerarquía de árboles de Windows y enviar el mensaje a cada HWND que encuentre. La propiedad Name interna del control se devolverá a un búfer en el LPARAM de la llamada SendMessage(). A continuación, puede comparar este búfer con el nombre del control que está buscando. Puesto que la propiedad Name se almacena en el código de la aplicación, no se localiza. Además, el entorno en tiempo de diseño de Visual Studio aplica la unicidad de esta propiedad manteniendo sincronizada la propiedad Name con el identificador de variable real en el código (por ejemplo, Button1, TreeView2, etc.). Por lo tanto, en general, si esta propiedad no fuera única, el código no se compilaría.

Nota Hay excepciones a esta regla, como controles generados dinámicamente en tiempo de ejecución.

Si los desarrolladores de la aplicación no usan Visual Studio o generan dinámicamente los controles en el formulario y los agregan a la colección controls, es posible que los controles no tengan establecida su propiedad Name . Esto provocará que la llamada SendMessage devuelva una cadena vacía en el LPARAM. Las opciones de este caso son obligar a los desarrolladores a establecer la propiedad Name en estos controles en una cadena única o usar uno de los mecanismos de identificación persistentes tradicionales mencionados anteriormente en este documento.

Especificación formal del mensaje de WM_GETCONTROLNAME

Una aplicación envía un mensaje de WM_GETCONTROLNAME para copiar el nombre que corresponde a un control de Windows Forms en un búfer proporcionado por el autor de la llamada.

Sintaxis

Para enviar este mensaje, llame a la función SendMessage como se muestra en la tabla siguiente.

 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;
); 
 

Parámetros

  • WParam: especifica el número máximo de caracteres TCHAR que se van a copiar, incluido el carácter nulo de terminación.
  • LParam: puntero al búfer que va a recibir el nombre del control.

Valor devuelto

El valor devuelto es el número de caracteres TCHAR copiados, no incluido el carácter nulo de terminación.

Automatización de Windows Forms con Visual Test 6.5

Como se indicó anteriormente, es difícil automatizar Windows Forms con una nueva instancia de Visual Test. Además, en el momento de la publicación de este documento, Visual Test no admite la plataforma nativa AMD64. Sin embargo, al pasar por el ejercicio de adaptación de pruebas visuales para trabajar con Windows Forms, puede comprender cómo actualizar las herramientas existentes para controlar la automatización de Windows Forms.

Puede usar el mensaje WM_GETCONTROLNAME para buscar los controles. En el ejemplo siguiente se muestra cómo obtener el nombre del control Windows Forms, dado un HWND determinado.

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

**Importante ** El código real que debe desarrollar es más complicado que lo que se muestra en el ejemplo porque Windows no le permite serializar cadenas entre procesos mediante SendMessage. El código real de WFSupport.inc usa memoria compartida. Pero una vez que tenga esta función, escribir una rutina recursiva para examinar la jerarquía de árboles de Windows y encontrar el control que desee es menos complicado. La parte recursiva de la función se incluye aquí para mayor integridad, pero el código real que debe desarrollar ajusta esta función en un bucle de tiempo de espera para admitir la búsqueda varias veces para reducir los problemas de tiempo de ejecución en las pruebas.

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

A continuación, puede usar esta rutina para buscar el HWND de cualquier control de Windows Forms dado un HWND inicial y un nombre de control, como se muestra en el ejemplo siguiente.

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

El siguiente problema importante es la naturaleza dinámica de los nombres de clase de ventana para Windows Forms controles. Las API de prueba visual para los controles realizan la validación para asegurarse de que el HWND que los pasa realmente hacen referencia a un control conocido de ese tipo. Por ejemplo, si llama a WButtonClick() en un HWND que hace referencia a sysTreeView32, no funcionará. En primer lugar, debe registrar el nombre de clase como un tipo válido de botón mediante WButtonSetClass().

Si Windows Forms controles de botón tenían nombres de control estáticos, simplemente podría llamar a WButtonSetClass("WindowsForms10.BUTTON") y todas las API de WButton* funcionarían bien. Sin embargo, dado que los controles de botón tienen nombres de clase dinámicos y porque WButtonSetClass() no admite la coincidencia de prefijos, debe averiguar cuál es exactamente el nombre de clase en tiempo de ejecución de la prueba. Para ello, encapsula la llamada FindWindowsFormsControlRecursive con un método denominado FindControlAndClassName() que devuelve el HWND y el nombre de clase del botón mediante parámetros de referencia. Al recuperar el nombre de la clase, basta con pasarlo a WButtonSetClass() y Visual Test está listo para interactuar con el botón Windows Forms. En el ejemplo siguiente se muestra el aspecto de 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

Precaución En el ejemplo anterior, se agregó compatibilidad con el tiempo de espera, por lo que la rutina no producirá un error inmediatamente si el control tarda unos segundos en aparecer.

Algunos de los controles de Windows Forms estándar no admiten todas las API de Win32. Esto provoca problemas para Visual Test porque las API de Win32 son los controladores principales detrás de las bibliotecas de pruebas de Visual. Por ejemplo, BM_GETSTATE es una API win32 no compatible que se usa para obtener el estado de los botones, casillas, botones de radio, etc. En tales casos, se proporcionan métodos adicionales en WFSupport.inc para reemplazar los equivalentes de Visual Test. La mayoría de estos métodos usan MSAA para extraer la información necesaria. En el ejemplo siguiente se muestra el origen de un reemplazo de WCheckState() de Visual Test.

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

Para obtener una lista de las funciones conocidas de Visual Test que no funcionan, consulte las funciones de reemplazo en la sección inferior del código fuente de WFSupport.inc.

Código fuente de WFSupport.inc

En el ejemplo siguiente se incluyen las rutinas de soporte técnico para ayudar a visual Test a automatizar Windows Forms. Puede incluir estas rutinas en cualquier proyecto y llamar a los métodos para buscar y manipular controles Windows Forms.

'=======================================================
' 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

Conclusión

Los medios tradicionales para identificar controles de Windows en un cuadro de diálogo a través de la automatización no funcionan bien para Windows Forms. Sin embargo, la propiedad Name de los controles, a la que se accede a través del mensaje de WM_GETCONTROLNAME , proporciona un identificador persistente independiente de la configuración regional que casi siempre es único. El fragmento de código de este artículo muestra cómo adaptar Visual Test para usar este mensaje, pero probablemente puede hacer algo similar a adaptar cualquier otro marco de automatización de la interfaz de usuario de Windows para probar Windows Forms.

Si tiene alguna pregunta o duda sobre la automatización de Windows Forms, consulte los foros de la comunidad en http://www.windowsforms.net/.

Libros