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.
Solution recommandée pour Windows Forms
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.
|
|
|
|
|
|
|
|
|
|
|
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
- programmation Windows Forms par Chris Sells