Automatizando Windows Forms

 

Brian McMaster
Windows Forms
Microsoft Corporation

Março de 2004

Resumo: Neste documento, você aprenderá a identificar exclusivamente os controles do Microsoft Windows Forms usando a propriedade Name. Você será mostrado como atualizar o Teste Visual para lidar com Windows Forms. O documento contém o código-fonte que você pode portar e aplicar para fazer atualizações semelhantes às estruturas de automação existentes. Este documento também lista algumas das APIs do Microsoft Win32 que Windows Forms não dão suporte intrinsecamente. Este documento não fornece uma solução completa para automatizar Windows Forms, métodos de substituição para cada método de Teste Visual que não funciona em controles Windows Forms ou uma interface semelhante ao Teste Visual para automatizar controles de Windows Forms para os quais o Teste Visual não tem equivalente. (23 páginas impressas)

Aplica-se a:

   Microsoft Visual Studio®
   Windows Forms
   Teste Visual
   Acessibilidade Ativa

Sumário

Introdução
Problema de identificação persistente
Soluções tradicionais para o problema
   ID do controle
   Legenda e nome da classe
   AccName e AccRole
   Ordem da Hierarquia do Windows
Solução recomendada para Windows Forms
Especificação formal para mensagem de WM_GETCONTROLNAME
Automatizando Windows Forms com o Visual Test 6.5
Código-fonte WFSupport.inc
Conclusão
Manuais

Introdução

Desde o lançamento inicial do Windows Forms, os desenvolvedores enfrentaram desafios exclusivos com suas estruturas de automação. Poucas estruturas de automação existentes podem automatizar Windows Forms sem modificação. A natureza dinâmica dos nomes de classe do Windows, juntamente com a falta de suporte para algumas APIs Comuns do Win32®, pode causar dificuldades para estruturas de automação, em particular o Teste Visual. Para facilitar as coisas, Windows Forms expõe a propriedade Name de controles a processos externos. Isso permite que você identifique convenientemente os controles em seus testes. Neste documento, você aprenderá a aplicar sua capacidade de identificar controles ao Teste Visual para aprimorar os recursos de teste automatizados de Windows Forms.

Problema de identificação persistente

Ao escrever testes automatizados para a interface do usuário do Windows, é importante que você possa identificar exclusivamente os controles em um formulário para que você possa encontrar posteriormente esses controles para manipulá-los no runtime de caso de teste. O identificador deve ser persistente em várias instâncias do aplicativo em teste. O ideal é que o identificador não inclua uma cadeia de caracteres dependente de localidade e não deve depender de algo que possa mudar com frequência durante todo o ciclo de vida do produto, como a ordenação da hierarquia de janelas.

Soluções tradicionais para o problema

ID do controle

Tradicionalmente, o identificador da interface do usuário era a ID de controle. Essa foi uma solução independente de localidade, que persistiu em várias instâncias do formulário e foi exclusiva mais de 90% do tempo. No entanto, em Windows Forms, a ID de controle é uma imagem espelho do HWND para esse controle específico. Portanto, a ID de controle é diferente sempre que você inicia o formulário, portanto, você não pode usá-la como seu identificador de interface do usuário.

Legenda e nome da classe

Quando a ID de controle falhou, o próximo melhor identificador persistente para a interface do usuário do Windows foi a combinação de legenda de janela e nome da classe. Essa solução funcionou bem na maioria dos casos em que a ID de controle falhou, desde que você pudesse ajustar o legenda estava procurando em todas as diferentes localidades em que estava testando. Isso pode adicionar algum trabalho adicional e, novamente, não foi uma solução completa; alguns controles têm legendas que mudam ou não têm legendas. Isso exigia que você obtémsse a instância "Nth" de uma combinação de Caption+ClassName específica se houvesse vários controles na caixa de diálogo com pares de legenda e nome de classe idênticos. Windows Forms complica esse problema fazendo com que a parte à direita do nome da classe de janela seja dinâmica. Dependendo da estrutura que você está usando para automatizar sua interface do usuário, isso pode tornar impossível pesquisar um controle pelo nome da classe de janela, pois ele é gerado dinamicamente, por exemplo, WindowsForms10.BUTTON.app3a. A parte "3a" do nome da classe é gerada e provavelmente será diferente na próxima vez que você iniciar o formulário com esse botão. Portanto, a menos que sua estrutura tenha a capacidade de fazer a correspondência de subcadeia de caracteres no nome da classe, essa não é uma opção.

AccName e AccRole

Uma terceira opção foi AccessibleName + AccessibleRole (conceitos de MSAA). Semelhante a Caption+ClassName, o AccName era geralmente uma cadeia de caracteres localizada e AccRole estava longe de ser exclusivo por conta própria. Além disso, a pesquisa na hierarquia msaa era lenta e era entediante chamar WindowFromAccessibleObject para converter em um HWND se você precisasse chamar uma API do Windows para obter algumas informações que não foram fornecidas por meio da interface MSAA. Ao todo, desde que sua estrutura localize os AccessibleNames e você não se importe em resolver duplicatas pesquisando o par "Nth" AccessibleName + Role em uma situação em que há vários elementos com o mesmo nome e função, essa é uma solução viável para um PersistentID para Windows Forms.

Ordem da Hierarquia do Windows

Uma opção final era usar a ordenação filho na hierarquia de árvore do Windows. Ou seja, se você soubesse que o botão que queria clicar era o terceiro filho do formulário de main pai, poderia obter o segundo irmão do primeiro filho usando os parâmetros apropriados para a API GetWindow() para localizar o HWND do botão. Isso certamente funciona em Windows Forms. No entanto, é obviamente propenso a erros porque um desenvolvedor pode facilmente adicionar outro controle ao formulário para descartar a ordenação ou até mesmo adicionar um novo nível à hierarquia de árvore movendo o botão para um controle GroupBox ou Painel. Além disso, o Windows não garante que a ordenação filho será consistente em diferentes versões do sistema operacional, portanto, é concebível que algum sistema operacional da Microsoft possa alterar a ordem no futuro, tornando seus testes incompatíveis com o sistema operacional.

Windows Forms internamente fornece uma maneira de resolver o problema do identificador persistente. A solução é que você exponha a propriedade Name dos controles em um formulário para ferramentas automatizadas que estão ficando sem processo. Você pode fazer isso por meio de uma chamada à API SendMessage() padrão passando a mensagem WM_GETCONTROLNAME . O código de exemplo é fornecido neste documento, mas para resumir o processo, você deve registrar a mensagem WM_GETCONTROLNAME , procurar a hierarquia de árvore do Windows e enviar a mensagem para cada HWND encontrado. A propriedade Name interna do controle será retornada a um buffer no LPARAM da chamada SendMessage( ). Em seguida, você pode comparar esse buffer com o nome do controle que está procurando. Como a propriedade Name é armazenada no código do aplicativo, ela não é localizada. Além disso, o ambiente de tempo de design do Visual Studio impõe a exclusividade dessa propriedade mantendo a propriedade Name em sincronia com o identificador de variável real no código (por exemplo, Button1, TreeView2 e assim por diante). Portanto, em geral, se essa propriedade não fosse exclusiva, o código não seria compilado.

Nota Há exceções a essa regra, como controles gerados dinamicamente no runtime.

Se os desenvolvedores do aplicativo não estiverem usando o Visual Studio ou estiverem gerando dinamicamente os controles no formulário e adicionando-os à coleção de controles, é possível que seus controles não tenham a propriedade Name definida. Isso resultará na chamada SendMessage retornando uma cadeia de caracteres vazia no LPARAM. As opções nesse caso são forçar os desenvolvedores a definir a propriedade Name nesses controles como uma cadeia de caracteres exclusiva ou usar um dos mecanismos tradicionais de identificação persistente mencionados anteriormente neste documento.

Especificação formal para mensagem de WM_GETCONTROLNAME

Um aplicativo envia uma mensagem WM_GETCONTROLNAME para copiar o nome que corresponde a um controle Windows Forms em um buffer fornecido pelo chamador.

Sintaxe

Para enviar essa mensagem, chame a função SendMessage , conforme mostrado na tabela a seguir.

 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 o número máximo de caracteres TCHAR a serem copiados, incluindo o caractere nulo de terminação.
  • LParam: ponteiro para o buffer que deve receber o nome do controle.

Valor Retornado

O valor retornado é o número de caracteres TCHAR copiados, não incluindo o caractere nulo de terminação.

Automatizando Windows Forms com o Visual Test 6.5

Conforme indicado anteriormente, é difícil automatizar Windows Forms com uma nova instância do Teste Visual. Além disso, no momento da publicação deste documento, o Visual Test não dá suporte à plataforma AMD64 nativa. No entanto, ao passar pelo exercício de adaptação do Teste Visual para trabalhar com Windows Forms, você pode obter alguma compreensão de como atualizar as ferramentas existentes para lidar com a automatização de Windows Forms.

Você pode usar a mensagem WM_GETCONTROLNAME para pesquisar os controles. O exemplo a seguir mostra como obter o nome do controle Windows Forms, dado um HWND específico.

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 ** O código real que você deve desenvolver é mais complicado do que o mostrado no exemplo porque o Windows não permite que você faça marshaling de cadeias de caracteres entre processos usando SendMessage. O código real no WFSupport.inc usa memória compartilhada. Mas quando você tem essa função, escrever uma rotina recursiva para navegar na hierarquia de árvore do Windows e encontrar o controle desejado é menos complicado. A parte recursiva da função está incluída aqui para integridade, mas o código real que você deve desenvolver encapsula essa função em um loop de tempo limite para dar suporte à pesquisa várias vezes para reduzir problemas de tempo nos testes.

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

Em seguida, você pode usar essa rotina para localizar o HWND de qualquer controle Windows Forms dado um HWND inicial e um nome de controle, como mostrado no exemplo a seguir.

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

O próximo grande problema é a natureza dinâmica dos nomes de classe de janela para controles Windows Forms. As APIs de Teste Visual para controles executam a validação para garantir que o HWND que você as passa realmente se refere a um controle conhecido desse tipo. Por exemplo, se você chamar WButtonClick() em um HWND que se refere a um SysTreeView32, ele não funcionará. Primeiro, você precisa registrar o nome da classe como um tipo válido de botão usando WButtonSetClass().

Se Windows Forms controles de botão tivessem nomes de controle estáticos, bastaria chamar WButtonSetClass("WindowsForms10.BUTTON") e todas as API do WButton* funcionariam bem. No entanto, como os controles de botão têm nomes de classe dinâmicos e, como WButtonSetClass() não dá suporte à correspondência de prefixo, você deve descobrir qual é o nome da classe exatamente no runtime do teste. Você pode fazer isso encapsulando a chamada FindWindowsFormsControlRecursive com um método chamado FindControlAndClassName() que retorna o HWND e o nome da classe do botão usando parâmetros de referência. Quando você receber de volta o nome da classe, basta passá-lo para WButtonSetClass() e o Teste Visual está pronto para interagir com o botão Windows Forms. O exemplo a seguir mostra a aparência 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

Cuidado No exemplo anterior, o suporte ao tempo limite foi adicionado para que a rotina não falhe imediatamente se o controle levar alguns segundos para aparecer.

Alguns dos controles de Windows Forms padrão não dão suporte a todas as APIs do Win32. Isso causa problemas para o Teste Visual porque as API do Win32 são os principais drivers por trás das bibliotecas de Teste Visual. Por exemplo, BM_GETSTATE é uma API Win32 sem suporte usada para obter o estado dos botões, marcar caixas, botões de opção e assim por diante. Nesses casos, métodos adicionais são fornecidos no WFSupport.inc para substituir os equivalentes do Teste Visual. A maioria desses métodos usa MSAA para extrair as informações necessárias. O exemplo a seguir mostra a origem de uma substituição para WCheckState()do 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 obter uma lista das funções de Teste Visual conhecidas que não funcionam, consulte as funções de substituição na seção inferior do código-fonte WFSupport.inc.

Código-fonte WFSupport.inc

O exemplo a seguir contém as rotinas de suporte para ajudar o Visual Test a automatizar Windows Forms. Você pode incluir essas rotinas em qualquer projeto e chamar os métodos para pesquisar e 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

Conclusão

Os meios tradicionais de identificar controles do Windows em uma caixa de diálogo por meio da automação não funcionam bem para Windows Forms. No entanto, a propriedade Name nos controles, acessada por meio da mensagem WM_GETCONTROLNAME , fornece um identificador persistente independente de localidade que é quase sempre exclusivo. O snippet de código neste artigo mostra como adaptar o Teste Visual para usar essa mensagem, mas você provavelmente pode fazer algo semelhante para adaptar qualquer outra estrutura de automação da interface do usuário do Windows para testar Windows Forms.

Se você tiver dúvidas ou preocupações sobre como automatizar Windows Forms, consulte os fóruns da comunidade em http://www.windowsforms.net/.

Manuais