Automatisation des Windows Forms

 

Brian McMaster
Windows Forms
Microsoft Corporation

Mars 2004

Résumé: Dans ce document, vous allez apprendre à identifier de manière unique les contrôles Microsoft Windows Forms à l’aide de la propriété Name. Vous verrez comment mettre à niveau Visual Test pour gérer Windows Forms. Le document contient le code source que vous pouvez transférer et appliquer pour effectuer des mises à niveau similaires aux infrastructures d’automatisation existantes. Ce document répertorie également certaines des API Microsoft Win32 qui Windows Forms ne prennent pas intrinsèquement en charge. Ce document ne fournit pas une solution complète pour automatiser Windows Forms, des méthodes de remplacement pour chaque méthode De test visuel qui ne fonctionne pas sur des contrôles Windows Forms, ou une interface similaire à Visual Test pour automatiser Windows Forms contrôles pour lesquels Visual Test n’a pas d’équivalent. (23 pages imprimées)

S’applique à :

   Microsoft Visual Studio®
   Windows Forms
   Test visuel
   Active Accessibility

Contenu

Introduction
Problème d’identification persistant
Solutions traditionnelles au problème
   ID du contrôle
   Légende et nom de la classe
   AccName et AccRole
   Ordre hiérarchique Windows
Solution recommandée pour Windows Forms
Spécification formelle du message WM_GETCONTROLNAME
Automatisation des Windows Forms avec Visual Test 6.5
WFSupport.inc Source Code
Conclusion
Livres

Introduction

Depuis la version initiale de Windows Forms, les développeurs ont fait face à des défis uniques avec leurs infrastructures d’automatisation. Peu d’infrastructures d’automatisation existantes peuvent automatiser Windows Forms sans modification. La nature dynamique des noms de classes Windows, associée à l’absence de prise en charge de quelques API Win32® courantes, peut entraîner des difficultés pour les frameworks d’automatisation, en particulier Visual Test. Pour faciliter les choses, Windows Forms expose la propriété Name des contrôles à des processus externes. Cela vous permet d’identifier facilement les contrôles dans vos tests. Dans ce document, vous allez apprendre à appliquer votre capacité à identifier des contrôles à Visual Test pour améliorer les fonctionnalités de test automatisé de Windows Forms.

Problème d’identification persistant

Lorsque vous écrivez des tests automatisés pour l’interface utilisateur Windows, il est important que vous puissiez identifier de manière unique les contrôles d’un formulaire afin que vous puissiez ensuite trouver ces contrôles pour les manipuler au moment de l’exécution du cas de test. L’identificateur doit être persistant sur plusieurs instances de l’application testée. Dans l’idéal, l’identificateur ne doit pas inclure de chaîne dépendante des paramètres régionaux et ne doit pas dépendre de quelque chose qui peut fréquemment changer tout au long du cycle de vie du produit, comme l’ordre de la hiérarchie des fenêtres.

Solutions traditionnelles au problème

ID du contrôle

Traditionnellement, l’identificateur de l’interface utilisateur était l’ID de contrôle. Il s’agissait d’une solution indépendante des paramètres régionaux, qui persistait sur plusieurs instances du formulaire et était unique dans 90 % du temps. Toutefois, dans Windows Forms, l’ID de contrôle est une image miroir du HWND pour ce contrôle particulier. Ainsi, l’ID de contrôle étant différent chaque fois que vous lancez le formulaire, vous ne pouvez pas l’utiliser comme identificateur d’interface utilisateur.

Légende et nom de la classe

Lorsque l’ID de contrôle a échoué, le prochain meilleur identificateur persistant pour l’interface utilisateur Windows était la combinaison de légende de fenêtre et de nom de classe. Cette solution a bien fonctionné dans la plupart des cas où l’ID de contrôle a échoué, à condition que vous puissiez ajuster la légende que vous recherchiez sur tous les différents paramètres régionaux dans lesquels vous avez été testé. Cela peut ajouter du travail supplémentaire, et là encore, il ne s’agissait pas d’une solution complète; certains contrôles ont des légendes qui changent, ou n’ont aucune légende du tout. Cela vous a obligé à obtenir le « Nth » instance d’une combinaison Légende+ClassName particulière s’il y avait plusieurs contrôles dans la boîte de dialogue avec des paires légende et nom de classe identiques. Windows Forms complique ce problème en rendant dynamique la partie de fin du nom de la classe de fenêtre. Selon l’infrastructure que vous utilisez pour automatiser votre interface utilisateur, la recherche d’un contrôle par son nom de classe de fenêtre peut être impossible, car il est généré dynamiquement, par exemple WindowsForms10.BUTTON.app3a. La partie « 3a » du nom de la classe est générée et sera probablement différente la prochaine fois que vous lancerez le formulaire avec ce bouton. Par conséquent, à moins que votre infrastructure ait la possibilité d’effectuer une correspondance de sous-chaînes sur le nom de la classe, ce n’est pas une option.

AccName et AccRole

Une troisième option était AccessibleName + AccessibleRole (concepts de MSAA). À l’instar de Caption+ClassName, accName était généralement une chaîne localisée, et AccRole était loin d’être unique en soi. En outre, la recherche dans la hiérarchie MSAA était lente et il était fastidieux d’appeler WindowFromAccessibleObject pour effectuer une conversion en HWND si vous deviez appeler une API Windows pour obtenir des informations qui n’étaient pas fournies via l’interface MSAA. Dans l’ensemble, à condition que votre infrastructure localise les AccessibleNames, et que vous ne vous dérangez pas de résoudre les doublons en recherchant la paire « Nth » AccessibleName + Role dans une situation où il existe plusieurs éléments avec le même nom et le même rôle, il s’agit d’une solution viable pour un PersistentID pour Windows Forms.

Ordre hiérarchique Windows

Une dernière option consistait à utiliser l’ordre enfant dans la hiérarchie de l’arborescence Windows. Autrement dit, si vous saviez que le bouton sur lequel vous souhaitez cliquer était le troisième enfant du formulaire de main parent, vous pouvez obtenir le deuxième frère du premier enfant en utilisant les paramètres appropriés à l’API GetWindow() pour rechercher le HWND du bouton. Cela fonctionne certainement dans Windows Forms. Toutefois, il est évidemment sujet aux erreurs, car un développeur peut facilement ajouter un autre contrôle au formulaire pour annuler l’ordre, ou même ajouter un nouveau niveau à la hiérarchie de l’arborescence en déplaçant le bouton dans un contrôle GroupBox ou un panneau. De plus, Windows ne garantit pas que les commandes enfants seront cohérentes entre les différentes versions du système d’exploitation. Il est donc concevable que certains systèmes d’exploitation Microsoft puissent modifier l’ordre à l’avenir, rendant vos tests incompatibles avec le système d’exploitation.

Windows Forms fournit en interne un moyen de résoudre le problème d’identificateur persistant. La solution consiste à exposer la propriété Name des contrôles d’un formulaire à des outils automatisés en cours d’exécution. Vous pouvez le faire par le biais d’un appel d’API SendMessage() standard en passant le message WM_GETCONTROLNAME . Un exemple de code est fourni dans ce document, mais pour résumer le processus, vous devez inscrire le message WM_GETCONTROLNAME , parcourir la hiérarchie de l’arborescence Windows et envoyer le message à chaque HWND que vous rencontrez. La propriété Name interne du contrôle est retournée à une mémoire tampon dans le LPARAM de l’appel SendMessage( ). Vous pouvez ensuite comparer cette mémoire tampon au nom du contrôle que vous recherchez. Étant donné que la propriété Name est stockée dans le code de votre application, elle n’est pas localisée. En outre, l’environnement de conception de Visual Studio applique l’unicité de cette propriété en conservant la propriété Name synchronisée avec l’identificateur de variable réel dans le code (par exemple Button1, TreeView2, etc.). Ainsi, en général, si cette propriété n’était pas unique, le code ne serait pas compilé.

Note Il existe des exceptions à cette règle, telles que les contrôles générés dynamiquement au moment de l’exécution.

Si les développeurs de votre application n’utilisent pas Visual Studio ou génèrent dynamiquement les contrôles du formulaire et les ajoutent à la collection de contrôles, il est possible que la propriété Name de vos contrôles ne soit pas définie. L’appel SendMessage renvoie alors une chaîne vide dans le LPARAM. Vos options dans ce cas sont de forcer les développeurs à définir la propriété Name sur ces contrôles sur une chaîne unique, ou à utiliser l’un des mécanismes d’identification persistante traditionnels mentionnés précédemment dans ce document.

Spécification formelle du message WM_GETCONTROLNAME

Une application envoie un message WM_GETCONTROLNAME pour copier le nom correspondant à un contrôle Windows Forms dans une mémoire tampon fournie par l’appelant.

Syntaxe

Pour envoyer ce message, appelez la fonction SendMessage comme indiqué dans le tableau suivant.

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

Paramètres

  • WParam : spécifie le nombre maximal de caractères TCHAR à copier, y compris le caractère null de fin.
  • LParam : pointeur vers la mémoire tampon qui doit recevoir le nom du contrôle.

Valeur de retour

La valeur de retour est le nombre de caractères TCHAR copiés, sans compter le caractère null de fin.

Automatisation des Windows Forms avec Visual Test 6.5

Comme indiqué précédemment, il est difficile d’automatiser Windows Forms avec une nouvelle instance de Visual Test. En outre, au moment de la publication de ce document, Visual Test ne prend pas en charge la plateforme AMD64 native. Toutefois, en effectuant l’exercice d’adaptation de Visual Test pour qu’il fonctionne avec Windows Forms, vous pouvez comprendre comment mettre à niveau tous les outils existants pour gérer l’automatisation des Windows Forms.

Vous pouvez utiliser le message WM_GETCONTROLNAME pour rechercher les contrôles. L’exemple suivant montre comment obtenir le nom du contrôle Windows Forms, en fonction d’un HWND particulier.

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

**Important ** Le code réel que vous devez développer est plus compliqué que ce qui est illustré dans l’exemple, car Windows ne vous permet pas de marshaler des chaînes entre les processus à l’aide de SendMessage. Le code réel dans WFSupport.inc utilise la mémoire partagée. Mais une fois que vous disposez de cette fonction, il est moins compliqué d’écrire une routine récursive pour parcourir la hiérarchie de l’arborescence Windows et trouver le contrôle souhaité. La partie récursive de la fonction est incluse ici pour plus d’exhaustivité, mais le code réel que vous devez développer encapsule cette fonction dans une boucle de délai d’expiration pour prendre en charge la recherche plusieurs fois afin de réduire les problèmes de minutage dans les tests.

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

Vous pouvez ensuite utiliser cette routine pour rechercher le HWND de n’importe quel contrôle Windows Forms en fonction d’un HWND de démarrage et d’un nom de contrôle, comme illustré dans l’exemple suivant.

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

Le problème majeur suivant est la nature dynamique des noms de classes de fenêtre pour les contrôles Windows Forms. Les API de test visuel pour les contrôles effectuent une validation pour s’assurer que le HWND que vous réussissez fait référence à un contrôle connu de ce type. Par exemple, si vous appelez WButtonClick() sur un HWND qui fait référence à un SysTreeView32, cela ne fonctionnera pas. Vous devez d’abord inscrire le nom de la classe en tant que type de bouton valide à l’aide de WButtonSetClass().

Si Windows Forms contrôles de bouton avaient des noms de contrôle statiques, vous pouvez simplement appeler WButtonSetClass(« WindowsForms10.BUTTON ») et toutes les API WButton* fonctionneraient correctement. Toutefois, étant donné que les contrôles de bouton ont des noms de classe dynamiques et que WButtonSetClass() ne prend pas en charge la correspondance de préfixes, vous devez déterminer quel est exactement le nom de la classe au moment de l’exécution de votre test. Pour ce faire, vous pouvez encapsuler l’appel FindWindowsFormsControlRecursive avec une méthode appelée FindControlAndClassName() qui retourne à la fois le HWND et le nom de classe du bouton à l’aide de paramètres de référence. Lorsque vous récupérez le nom de la classe, il vous suffit de le transmettre à WButtonSetClass() et Visual Test est prêt à interagir avec le bouton Windows Forms. L’exemple suivant montre à quoi ressemble 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

Attention Dans l’exemple précédent, la prise en charge du délai d’expiration a été ajoutée afin que la routine n’échoue pas immédiatement si le contrôle prend quelques secondes pour apparaître.

Certains des contrôles Windows Forms standard ne prennent pas en charge toutes les API Win32. Cela entraîne des problèmes pour Visual Test, car les API Win32 sont les principaux pilotes derrière les bibliothèques Visual Test. Par exemple, BM_GETSTATE est une API Win32 non prise en charge utilisée pour obtenir l’état des boutons, des cases d’case activée, des cases d’option, etc. Dans ce cas, des méthodes supplémentaires sont fournies dans WFSupport.inc pour remplacer les équivalents Visual Test. La plupart de ces méthodes utilisent MSAA pour extraire les informations nécessaires. L’exemple suivant montre la source d’un remplacement 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

Pour obtenir la liste des fonctions de test visuel connues qui ne fonctionnent pas, consultez les fonctions de remplacement dans la section inférieure du code source WFSupport.inc.

WFSupport.inc Source Code

L’exemple suivant contient les routines de prise en charge pour aider Visual Test à automatiser Windows Forms. Vous pouvez inclure ces routines dans n’importe quel projet et appeler les méthodes pour rechercher et manipuler Windows Forms contrôles.

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

Conclusion

Les moyens traditionnels d’identification des contrôles Windows sur une boîte de dialogue via l’automatisation ne fonctionnent pas bien pour Windows Forms. Toutefois, la propriété Name sur les contrôles, accessible via le message WM_GETCONTROLNAME , vous donne un identificateur persistant indépendant des paramètres régionaux qui est presque toujours unique. L’extrait de code de cet article montre comment adapter Visual Test pour utiliser ce message, mais vous pouvez probablement faire quelque chose de similaire pour adapter n’importe quel autre framework d’automatisation de l’interface utilisateur Windows pour tester Windows Forms.

Si vous avez des questions ou des préoccupations sur l’automatisation de Windows Forms, consultez les forums de la communauté à l’adresse http://www.windowsforms.net/.

Livres