|
作者:Brian McMaster Windows Forms Microsoft Corporation
2004 年 3 月
摘要: 本文旨在教導您如何使用 Name 屬性來唯一識別 Microsoft Windows Form 控制項,並示範如何升級 Visual Test,以處理 Windows Form。文中涵蓋了您可移植並套用的原始程式碼,以便在現有的自動化架構進行類似的升級。除此,本文還列出一些 Windows Form 本身不支援的 Microsoft Win32 API。本文內容並不能做為自動化 Windows Form 的單一完整解決方案,在 Windows Form 控制項裡若有不可行的 Visual Test 方法,亦無法用為替代方法,而在自動化 Windows Form 控制項且 Visual Test 不具相等的控制項時,也無法提供與 Visual Test 類似的介面。
(請注意:範例程式碼中的註解均為英文,此文章中所顯示的中文化註解,僅供參考)
(列印共 23 頁)
適用於:
Microsoft Visual Studio® Windows Forms Visual Test Active Accessibility
目錄
簡介 永續性識別問題 問題的傳統解決方案 控制項識別碼 標題和類別名稱 AccName 和 AccRole Windows 階層順序 建議的 Windows Form 解決方案 WM_GETCONTROLNAME 訊息的正式規格 利用 Visual Test 6.5 自動化 Windows Form WFSupport.inc 原始程式碼 結論 參考書籍
簡介
自 Windows Form 首度問世起,開發人員在自動化架構上就面臨了獨特的挑戰。現有的自動化架構大多需要修改才能自動化 Windows Form。 Windows 類別名稱的動態本質,加上缺乏一些通用 Win32®API 的支援,可能會導致自動化架構出現難題,而在 Visual Test 中更是如此。為了讓事情單純化,Windows Form 公開了控制項的 Name 屬性給外部處理序。這可方便您識別測試中的控制項。在本文中,您會學習到如何將識別控制項的能力應用到 Visual Test 上,以加強 Windows Form 的自動化測試功能。
永續性識別問題
在撰寫 Windows UI 的自動化測試時,能夠唯一識別表單上控制項以便在稍後找出該些控制項於測試 Runtime 中加以操作,這點相當的重要。識別項必須能夠持續在進行測試的應用程式的多個執行個體間。理論上,識別項不應該包括地區設定相依的字串,而且也不應該相依在整個產品週期內會經常變動的項目,例如視窗階層排序。
問題的傳統解決方案
控制項識別碼
UI 的識別項過去一直都是控制項識別碼。這是與地區設定無關的解決方案,會保存在表單的多個執行個體間,而且 90% 的情況下都是唯一的。然而,在 Windows Form 中,控制項識別碼是該特定控制項 HWND 的鏡映影像。因此,控制像識別碼在您每次啟動表單時都不一樣,所以您不能將此用作為您的 UI 識別項。
標題和類別名稱
當控制項識別碼不能用時,Windows UI 接下來最具持續性的識別項是視窗標題和類別名稱組合。假如您能在測試的所有不同地區設定上調整要尋找的標題的話,此解決方案在大部份控制項識別碼無法使用的情況下都適用。這可能另外需要一點功夫,再次強調這並不是完整的解決方案;有些控制項的標題會變,或甚至完全沒有標題。如果對話方塊上有多個控制項具有相同的標題和類別名稱組,則需要您取得特定 Caption+ClassName 組合 N 個執行個體。而 Windows Form 讓視窗類別名稱的行尾部份成為動態則使得此問題更加複雜。根據您用來自動化 UI 的架構而定,可能會無法依控制項的視窗類別名稱來搜尋該控制項,因為它是動態產生的,例如,WindowsForms10.BUTTON.app3a。類別名稱的 "3a" 部份是產生的,而且很有可能在下一次啟動含有此按鈕的表單時會不一樣。因此除非您的架構有能力在類別名稱上對應子字串,否則這並不是可行的選項。
AccName 和 AccRole
第三個選項是 AccessibleName + AccessibleRole (MSAA 的概念)。與 Caption+ClassName 類似,AccName 通常是當地語系化的字串,而 AccRole 本身根本就不是唯一的。此外,搜尋 MSAA 階層也很慢,而且如果您需要呼叫 Windows API 來取得一些透過 MSAA 介面沒有提供的資訊,呼叫 WindowFromAccessibleObject 以轉換成 HWND 可說是相當冗長的過程。總而言之,假設您的架構會當地語系化 AccessibleNames,而您不介意在有多個元件具有相同名稱和角色的情況下搜尋 N 個 AccessibleName + Role 組來解決重複的問題,這對於 Windows Form 的 PersistentID 倒是個可行的解決方案。
Windows 階層順序
最後一個選項是在 Windows 樹狀目錄階層中使用子系順序。也就是說,如果您知道要點按的按鈕是父系主表單的第三個子系,可使用 GetWindow() API 適當的參數尋找該按鈕的 HWND 來取得第一個子系第二個同級成員。這在 Windows Form 中肯定可行。不過,它顯然也容易發生錯誤,因為開發人員很輕易就可以新增另一個控制項到表單上而搞亂順序,或甚至將按鈕移到 GroupBox 控制項或 Panel 在樹狀目錄階層中加入新層級。此外,Windows 也無法保證子系順序會在作業系統的各個不同版本間保持一致,所以可以想像得到一些 Microsoft 作業系統有可能在將來改變順序,使您的測試與該作業系統不相容。
建議的 Windows Form 解決方案
Windows Form 在內部提供了一種方法解決永續性識別項的問題。該解決方案是讓您將表單上該些控制項的 Name 屬性公開給跨處理序執行的自動化工具。您可以透過 SendMessage() API 呼叫傳遞 WM_GETCONTROLNAME 訊息來完成此動作。本文中有提供範例程式碼,但若要摘要該處理序,您必須註冊 WM_GETCONTROLNAME 訊息、瀏覽 Windows 樹狀目錄階層,並將訊息傳給每個遇到的 HWND。控制項的內部 Name 屬性會傳回到 SendMessage() 呼叫的 LPARAM 緩衝區。您接著可將此緩衝區與搜尋的控制項名稱相加比較。由於 Name 屬性是存在應用程式的程式碼中,所以不會經過當地語系化。另外,Visual Studio 設計階段環境也會保持 Name 屬性與程式碼中實際變動的識別項 (例如 Button1、TreeView2 等等) 互相同步,來強制此屬性的唯一性。因此,一般說來,如果此屬性不唯一,程式碼也無法編譯。
注意 此規則有一些例外,例如在 Runtime 動態產生的控制項。
如果應用程式的開發人員不是使用 Visual Studio,或是在表單上動態產生控制項並將之新增到控制項集合中的話,您的控制項很有可能還沒有設定 Name 屬性。這將導致 SendMessage 呼叫在 LPARAM 中傳回空字串。您在這種情況下的選擇是強迫開發人員將這些控制項上的 Name 屬性設定為唯一的字串,或使用本文稍早提到的其中一種傳統永續性識別機制。
WM_GETCONTROLNAME 訊息的正式規格
應用程式會傳送 WM_GETCONTROLNAME 訊息將對應於 Windows Form 控制項的名稱複製到呼叫者提供的緩衝區內。
語法
若要傳送此訊息,請呼叫 SendMessage 函式,如下表所示。
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; |
); |
|
參數
- WParam:指定要複製的 TCHAR 字元最大值,包括結束的 Null 字元。
- LParam:指向要接收控制項名稱的緩衝區。
傳回值
傳回值是複製的 TCHAR 字元數,不包括結束的 Null 字元。
利用 Visual Test 6.5 自動化 Windows Form
如稍早所述,藉由 Visual Test 的新執行個體來自動化 Windows Form 有點艱難。此外,在本文出版之時,Visual Test 並不支援原始 AMD64 平台。不過,練習採用 Visual Test 處理 Windows Form,有助於您對如何升級任何現有的工具來處理自動化 Windows Form 多一點認識。
您可以使用 WM_GETCONTROLNAME 訊息來搜尋控制項。以下範例說明如何取得 Windows Form 控制項名稱,提供特定的 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
重要 您必須開發的實際程式碼比範例中所顯示的還要複雜得多,因為 Windows 並不能讓您使用 SendMessage 來封送處理處理序間的字串。WFSupport.inc 中的實際程式碼使用的是共用記憶體。但一旦您有此函式之後,撰寫遞迴常式來瀏覽 Windows 樹狀目錄階層及尋找您要的控制項就簡單許多。為求完整起見,在此包含了函式的遞迴部份,但您必須開發的實際程式碼會將此函式包裝在逾時迴圈中以支援多次搜尋,進而縮短測試當中的計時問題。 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
您接著可以使用此常式來尋找任何具有開始 HWND 和控制項名稱的 Windows Form 控制項 HWND,如下列範例所示: Dim controlHandle As Long
controlHandle = FindWindowsFormsControlRecursive( myStartWnd, "Button1")
下一個大問題是 Windows Form 控制項其視窗類別名稱的動態本質。控制項的 Visual Test API 會進行驗證以確保您傳給它們的 HWND 實際上是參考到該型別已知的控制項。例如,如果您是在參考 SysTreeView32 的 HWND 上呼叫 WButtonClick(),就沒有什麼作用。您首先必須使用 WButtonSetClass() 將類別名稱註冊為有效的按鈕類型。
如果 Windows Form 按鈕控制項有靜態控制名稱,您可以單純呼叫 WButtonSetClass("WindowsForms10.BUTTON"),而所有的 WButton* API 都能正常運作。不過,因為按鈕控制項有動態的類別名稱,加上 WButtonSetClass() 並不支援前置詞對應,您就必須想辦法得知該類別在測試 Runtime 時確切的名稱。您可以透過一個稱為 FindControlAndClassName() 的方法來包裝 FindWindowsFormsControlRecursive 以完成此動作,該方法會使用參照參數同時傳回按鈕的 HWND 和類別名稱。當您取得類別名稱時,只要將它傳到 WButtonSetClass(),而 Visual Test 就可以馬上與 Windows Form 按鈕互動。以下範例展示 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
警告 在前一個範例中加入逾時支援,是為了讓常式不致於在如果控制項需要花幾秒鐘時間出現的話馬上就失敗。
有些標準 Windows Form 控制項並不支援所有的 Win32 API。這會造成 Visual Test 的問題,因為 Win32 API 是 Visual Test 程式庫背後主要的驅動程式。例如,BM_GETSTATE 就是個不支援的 Win32 API,它是用來取得按鈕、核取方塊、選項按鈕等等的狀態。在這種情況下,WFSupport.inc 中有提供其他方法用來取代 Visual Test 等同項。這些方法絕大多數都是使用 MSAA 來展開必要的資訊。以下範例說明了替代 Visual Test 的 WCheckState() 的來源。 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
如需一份已知不可行的 Visual Test 函式清單,請參閱 WFSupport.inc 原始程式碼底下的替代函式部份。
WFSupport.inc 原始程式碼
以下範例包含了協助 Visual Test 自動化 Windows Form 的支援常式。您可以將常式包含在任何專案中,並呼叫各個方法來搜尋和操作 Windows Form 控制項。 '=======================================================
' 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
結論
透過自動化來識別對話方塊上的 Windows 控制項的傳統方法並不適用於 Windows Form。不過,在控制項上透過 WM_GETCONTROLNAME 訊息存取的 Name 屬性,提供您一個與地區設定無關的永續性識別項,幾乎始終是唯一的。本文中的程式碼片段展示了如何調整 Visual Test 來利用此訊息,不過您可能可以透過類似的動作來調整任何其他 Windows UI 自動化架構以測試 Windows Form。
如果您對自動化 Windows Form 有任何疑問,請參考位於 http://www.windowsforms.net/ 的社群討論。
參考書籍
|