Automazione Windows Forms

 

Brian McMaster
Windows Forms
Microsoft Corporation

Marzo 2004

Riepilogo: In questo documento si apprenderà come identificare in modo univoco i controlli di Microsoft Windows Forms usando la proprietà Name. Verrà illustrato come aggiornare Visual Test per gestire Windows Forms. Il documento contiene il codice sorgente che è possibile convertire e applicare per eseguire aggiornamenti simili ai framework di automazione esistenti. Questo documento elenca anche alcune delle API Di Microsoft Win32 che Windows Forms non supportano intrinsecamente. Questo documento non fornisce una soluzione completa per l'automazione Windows Forms, i metodi sostitutivi per ogni metodo Di test visivo che non funziona sui controlli Windows Forms o un'interfaccia simile a Visual Test per l'automazione dei controlli Windows Forms per cui Visual Test non ha alcun equivalente. (23 pagine stampate)

Si applica a:

   Microsoft Visual Studio®
   Windows Forms
   Test visivo
   Active Accessibility

Contenuto

Introduzione
Problema di identificazione persistente
Soluzioni tradizionali al problema
   ID controllo
   Didascalia e nome classe
   AccName e AccRole
   Ordine gerarchia di Windows
Soluzione consigliata per Windows Forms
Specifica formale per WM_GETCONTROLNAME messaggio
Automazione di Windows Forms con Visual Test 6.5
Codice sorgente WFSupport.inc
Conclusione
Libri

Introduzione

Dal momento che la versione iniziale di Windows Forms, gli sviluppatori hanno affrontato sfide uniche con i framework di automazione. Pochi framework di automazione esistenti possono automatizzare Windows Forms senza modifiche. La natura dinamica dei nomi delle classi di Windows, associata alla mancanza di supporto per alcune API Win32® comuni può causare difficoltà per i framework di automazione, in particolare Visual Test. Per semplificare le operazioni, Windows Forms espone la proprietà Name dei controlli ai processi esterni. Ciò consente di identificare facilmente i controlli nei test. In questo documento si apprenderà come applicare la possibilità di identificare i controlli a Visual Test per migliorare le funzionalità di test automatizzate di Windows Forms.

Problema di identificazione persistente

Quando si scrivono test automatizzati per l'interfaccia utente di Windows, è importante identificare in modo univoco i controlli in un modulo in modo che sia possibile trovare in un secondo momento tali controlli per modificarli in fase di runtime del test case. L'identificatore deve essere persistente in più istanze dell'applicazione in fase di test. Idealmente, l'identificatore non deve includere una stringa dipendente dalle impostazioni locali e non deve dipendere da qualcosa che può cambiare frequentemente durante il ciclo di vita del prodotto, ad esempio l'ordinamento della gerarchia delle finestre.

Soluzioni tradizionali al problema

ID controllo

Tradizionalmente, l'identificatore per l'interfaccia utente era l'ID di controllo. Si tratta di una soluzione indipendente dalle impostazioni locali, che si è mantenuta in più istanze del modulo ed è stata univoca oltre il 90% del tempo. Tuttavia, in Windows Forms, l'ID del controllo è un'immagine mirror dell'HWND per quel particolare controllo. Pertanto, l'ID controllo è diverso ogni volta che si avvia il modulo, quindi non è possibile usarlo come identificatore dell'interfaccia utente.

Didascalia e nome classe

Quando l'ID di controllo non è riuscito, l'identificatore permanente successivo per l'interfaccia utente di Windows era la combinazione di didascalia e nome della classe. Questa soluzione ha funzionato bene nella maggior parte dei casi in cui l'ID di controllo non è riuscito, purché sia possibile modificare la didascalia che si stava cercando in tutte le diverse impostazioni locali in cui si è verificato il test. Ciò può aggiungere un lavoro aggiuntivo, e di nuovo, non era una soluzione completa; alcuni controlli hanno didascalie che cambiano o non hanno didascalie. Ciò richiede di ottenere l'istanza "Nth" di una determinata combinazione Caption+ClassName se sono presenti più controlli nella finestra di dialogo con coppie di nomi di classe e didascalia identiche. Windows Forms complica questo problema rendendo dinamica la parte finale del nome della classe di finestra. A seconda del framework usato per automatizzare l'interfaccia utente, questa operazione può rendere impossibile la ricerca di un controllo tramite il nome della classe di finestra, perché viene generata dinamicamente, ad esempio WindowsForms10.BUTTON.app3a. La parte "3a" del nome della classe viene generata e probabilmente sarà diversa la prossima volta che si avvia il modulo con questo pulsante. Quindi, a meno che il framework non abbia la possibilità di eseguire la corrispondenza della sottostringa sul nome della classe, questa non è un'opzione.

AccName e AccRole

Una terza opzione era AccessibleName + AccessibleRole (concetti di MSAA). Analogamente a Caption+ClassName, AccName era in genere una stringa localizzata e AccRole era lontano da univoco da solo. Inoltre, la ricerca della gerarchia MSAA era lenta e era noioso chiamare WindowFromAccessObject per convertire in un HWND se è mai necessario chiamare un'API Di Windows per ottenere alcune informazioni che non sono state fornite tramite l'interfaccia MSAA. In tutto, se il framework localizza i Nomi accessibili e non è consigliabile risolvere i duplicati cercando la coppia "Nth" AccessibleName + Role in una situazione in cui sono presenti più elementi con lo stesso nome e ruolo, si tratta di una soluzione valida per un PersistentID per Windows Forms.

Ordine gerarchia di Windows

Un'opzione finale consiste nell'usare l'ordinamento figlio nella gerarchia dell'albero di Windows. In questo caso, se si sapeva che il pulsante che si desidera fare clic era il terzo figlio del modulo principale padre, è possibile ottenere il secondo elemento figlio del primo figlio usando i parametri appropriati per l'API GetWindow() per trovare L'HWND del pulsante. Questo funziona certamente in Windows Forms. Tuttavia, è ovviamente soggetto a errori perché uno sviluppatore può aggiungere facilmente un altro controllo al modulo per generare l'ordinamento o anche aggiungere un nuovo livello alla gerarchia dell'albero spostando il pulsante in un controllo GroupBox o Pannello. Inoltre, Windows non garantisce che l'ordinamento figlio sarà coerente tra diverse versioni del sistema operativo, quindi è possibile che alcuni sistemi operativi Microsoft possano modificare l'ordine in futuro, rendendo i test incompatibili con il sistema operativo.

Windows Forms internamente consente di risolvere il problema dell'identificatore persistente. La soluzione consiste nell'esporre la proprietà Name dei controlli in un modulo agli strumenti automatizzati in esecuzione. È possibile eseguire questa operazione tramite una chiamata API SendMessage() standard passando il messaggio di WM_GETCONTROLNAME . Il codice di esempio viene fornito in questo documento, ma per riepilogare il processo, è necessario registrare il messaggio di WM_GETCONTROLNAME , esplorare la gerarchia dell'albero di Windows e inviare il messaggio a ogni HWND visualizzato. La proprietà Internal Name del controllo verrà restituita a un buffer nella chiamata LPARAM della chiamata SendMessage(). È quindi possibile confrontare questo buffer con il nome del controllo che si sta cercando. Poiché la proprietà Name viene archiviata nel codice dell'applicazione, non viene localizzata. Inoltre, l'ambiente di progettazione di Visual Studio applica l'univocità di questa proprietà mantenendo sincronizzata la proprietà Name con l'identificatore di variabile effettivo nel codice, ad esempio Button1, TreeView2 e così via. Pertanto, in generale, se questa proprietà non era univoca, il codice non verrà compilato.

Nota Esistono eccezioni a questa regola, ad esempio i controlli generati dinamicamente in fase di esecuzione.

Se gli sviluppatori dell'applicazione non usano Visual Studio o generano dinamicamente i controlli nel modulo e li aggiungono all'insieme di controlli, è possibile che i controlli non abbiano il relativo set di proprietà Name . Verrà generata la chiamata SendMessage che restituisce una stringa vuota nell'oggetto LPARAM. Le opzioni in questo caso devono forzare gli sviluppatori a impostare la proprietà Name su questi controlli su una stringa univoca o per usare uno dei meccanismi di identificazione persistente tradizionali menzionati in precedenza in questo documento.

Specifica formale per WM_GETCONTROLNAME messaggio

Un'applicazione invia un messaggio WM_GETCONTROLNAME per copiare il nome corrispondente a un controllo Windows Forms in un buffer fornito dal chiamante.

Sintassi

Per inviare questo messaggio, chiamare la funzione SendMessage come illustrato nella tabella seguente.

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

Parameters

  • WParam: specifica il numero massimo di caratteri TCHAR da copiare, incluso il carattere Null terminante.
  • LParam: puntatore al buffer che deve ricevere il nome del controllo.

Valore restituito

Il valore restituito è il numero di caratteri TCHAR copiati, non incluso il carattere null terminante.

Automazione di Windows Forms con Visual Test 6.5

Come indicato in precedenza, è difficile automatizzare Windows Forms con una nuova istanza di Visual Test. Inoltre, al momento della pubblicazione di questo documento, Visual Test non supporta la piattaforma AMD64 nativa. Tuttavia, eseguendo l'esercizio dell'adattamento di Visual Test per lavorare con Windows Forms, è possibile acquisire alcune informazioni su come aggiornare gli strumenti esistenti per gestire l'automazione delle Windows Forms.

È possibile usare il messaggio WM_GETCONTROLNAME per cercare i controlli. Nell'esempio seguente viene illustrato come ottenere il nome del controllo Windows Forms, dato un determinato HWND.

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 ** Il codice effettivo che è necessario sviluppare è più complicato di quello visualizzato nell'esempio perché Windows non consente di eseguire il marshalling delle stringhe tra i processi usando SendMessage. Il codice effettivo in WFSupport.inc usa la memoria condivisa. Ma una volta che si dispone di questa funzione, scrivere una routine ricorsiva per esplorare la gerarchia dell'albero di Windows e trovare il controllo desiderato è meno complicato. La parte ricorsiva della funzione è inclusa qui per completezza, ma il codice effettivo che è necessario sviluppare esegue il wrapping di questa funzione in un ciclo di timeout per supportare la ricerca più volte per ridurre i problemi di intervallo nei test.

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

È quindi possibile utilizzare questa routine per trovare il valore HWND di qualsiasi controllo Windows Forms dato un HWND iniziale e un nome di controllo, ad esempio illustrato nell'esempio seguente.

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

Il prossimo problema importante è la natura dinamica dei nomi delle classi di finestre per i controlli Windows Forms. Le API Di test visivo per i controlli eseguono la convalida per garantire che l'HWND passato faccia effettivamente riferimento a un controllo noto di quel tipo. Ad esempio, se si chiama WButtonClick() in un HWND che fa riferimento a sysTreeView32, non funzionerà. È prima necessario registrare il nome della classe come tipo di pulsante valido usando WButtonSetClass().

Se i controlli dei pulsanti Windows Forms hanno nomi di controlli statici, è possibile chiamare semplicemente WButtonSetClass("WindowsForms10.BUTTON") e tutte le API WButton* funzioneranno correttamente. Tuttavia, poiché i controlli pulsante hanno nomi di classe dinamici e poiché WButtonSetClass() non supporta la corrispondenza dei prefissi, è necessario determinare il nome della classe esattamente in fase di esecuzione del test. A tale scopo, è possibile eseguire il wrapping della chiamata FindWindowsFormsControlRecursive con un metodo denominato FindControlAndClassName() che restituisce sia HWND che il nome della classe del pulsante usando i parametri di riferimento. Quando si recupera il nome della classe, è sufficiente passarlo a WButtonSetClass() e Visual Test è pronto per interagire con il pulsante Windows Forms. L'esempio seguente mostra l'aspetto di 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

Attenzione Nell'esempio precedente è stato aggiunto il supporto del timeout in modo che la routine non riesca immediatamente se il controllo richiede alcuni secondi.

Alcuni dei controlli Windows Forms standard non supportano tutte le API Win32. Ciò causa problemi per il test visivo perché l'API Win32 è i driver principali dietro le librerie di test visivi. Ad esempio, BM_GETSTATE è un'API Win32 non supportata usata per ottenere lo stato dei pulsanti, delle caselle di controllo, dei pulsanti di opzione e così via. In questi casi, in WFSupport.inc vengono forniti metodi aggiuntivi per sostituire gli equivalenti di Visual Test. La maggior parte di questi metodi usa MSAA per estrarre le informazioni necessarie. Nell'esempio seguente viene illustrata l'origine per una sostituzione di WCheckState()di 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

Per un elenco delle funzioni di test visivo note che non funzionano, vedere le funzioni di sostituzione nella sezione inferiore del codice sorgente di WFSupport.inc.

Codice sorgente di WFSupport.inc

L'esempio seguente contiene le routine di supporto per consentire a Visual Test di automatizzare Windows Forms. È possibile includere queste routine in qualsiasi progetto e chiamare i metodi per cercare e modificare i controlli 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

Conclusione

I metodi tradizionali per identificare i controlli Windows in una finestra di dialogo tramite l'automazione non funzionano correttamente per Windows Forms. Tuttavia, la proprietà Name nei controlli a cui si accede tramite il messaggio WM_GETCONTROLNAME fornisce un identificatore permanente indipendente dalle impostazioni locali quasi sempre univoco. Il frammento di codice in questo articolo illustra come adattare Visual Test per usare questo messaggio, ma probabilmente è possibile eseguire operazioni simili ad adattare qualsiasi altro framework di automazione interfaccia utente di Windows per testare Windows Forms.

Per domande o dubbi sull'automazione delle Windows Forms, vedere i forum della community all'indirizzo http://www.windowsforms.net/.

Libri