Based on the suggestions from both @IInspectable and @Charles, UI Automation
is DEFINITELY the way to go. I've always had a bit of a problem trudging through the MSDN Articles for each method because often times they use C++
or C#
code (I guess someday I'll need to really learn these languages, but I digress).
For those who are intimidated by the UI Automation
Approach, I'm leaving my other answer because it would be useful if you can master it. . . but it is literally reinventing UI Automation
to try and DO IT!
Using the MSDN Documentation mostly confused me until I learned the structures of the Elements/Properties, etc. (Further research also shows I could combine properties using AndCondition
to build conjunctive properties. This is well and great, however, I have to credit This YouTube Video which takes a UI Automation
approach to reading text for getting me over the hump. Sure it's a totally different approach/use case, BUT the structures are the same, and the author of the Video (Tech Savvy
) uses VB.Net
language which is what I really needed.
Below is the entire subroutine I built - from scratch yet! I'm sure I can improve it, but for a first attempt at working with UI Automation
I was able to accomplish the ENTIRE process needed, NOT just only a smidgen of some of the work, and it is MUCH. . . . MUCH easier long-run, so THANK YOU ALL for the suggestions to move to UI Automation
- this is ABSOLUTELY the best move! I felt like I gained a 'Level Up' in my coding. <3
Note: When dealing with legacy objects from VB6 (Thunder Frames, etc), there is thankfully a LocalizedControlType
Property which can be used to find your items as you walk the subtree
of your initial program!
Note2: You'll notice I'm using a Click_Object routine for my calls. . . This is NOT using the Invoke
method because I had trouble with Invoke
calls hanging for up to 10 Seconds or more for a simple button click. I know I can call these using a separate thread dispatcher
, but I ultimately went with Microsft.TestAPI's Mouse Class
and the Click/MoveTo
Methods therein as suggested in This SO Article.
Public Shared Sub FindHMUtil(ByVal ComNum As String)
Dim mW As MainWindow = CType(Application.Current.MainWindow, MainWindow)
'If utility is open - need to kill it and restart.
If Process.GetProcessesByName("hmutility").Count > 0 Then
Commands(0, 0) = "/C TaskKill /FI ""imagename eq hmutility.exe"" /F"
Commands(1, 0) = "Successfully able to kill Firmware Utility."
Commands(2, 0) = "Unable TypeOf kill Firmware Utility."
Commands(3, 0) = "Restarting TPG Printer Firmware Utility"
Commands(4, 0) = "Confirming TPG Printer Firmware"
Commands(5, 0) = "False"
MyProcessControl.ProcessCommandLine("cmd.exe", Commands, mW.PrinterCheckText2)
End If
'Restart Utility
Commands(0, 0) = "/C Start ""HmUtil"" ""C:\Program Files (x86)\hmUtility\hmUtility.exe"""
Commands(1, 0) = "Successfully Able to Start Firmware Utility"
Commands(2, 0) = "Unable to Start Firmware Utility"
Commands(3, 0) = "Restarting TPG Printer Firmware Utility"
Commands(4, 0) = "Confirming TPG Printer Firmware"
Commands(5, 0) = "False"
MyProcessControl.ProcessCommandLine("cmd.exe", Commands, mW.PrinterCheckText2)
Thread.Sleep(350)
Dim enumerator As New WindowsEnumerator
'get handle for initial window
'Note - FindWindow API will not find ThunderForm Windows with WindowTitle if they are part of a Thunder Wrapper!!!!
'Leaving this as a construct for myself
Dim hmImg As IntPtr = enumerator.FindWindowInt(Nothing, "Image")
If hmImg = IntPtr.Zero Or hmImg = vbEmpty Or hmImg = 0 Or hmImg = vbNull Then
'keeping as a reminder - sometimes the findwindow API will not find the ThunderForm
hmImg = enumerator.FindWindowInt(ThunderForm, Nothing)
If hmImg = IntPtr.Zero Or hmImg = vbEmpty Or hmImg = 0 Or hmImg = vbNull Then
hmImg = enumerator.FindWindowExInt(Nothing, Nothing, ThunderForm, Nothing)
Debug.Print("Still found nothing mate!")
End If
End If
'get initial window element
Dim hmWind As AutomationElement = AutomationElement.FromHandle(hmImg)
'Get first set of Elements needed from initial window. NOTE: Not all are used immediately, but keeping them read prevents need to find them a 2nd time.
Dim hmWindExecute As AutomationElement = hmWind.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Execute"))
Dim hmWindResetPrntr As AutomationElement = hmWindExecute.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Reset Printer"))
Dim hmWindSetup As AutomationElement = hmWindExecute.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Setup"))
Dim hmWindSend As AutomationElement = hmWindExecute.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Send"))
Dim hmWindClearStat As AutomationElement = hmWindExecute.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Clear Status"))
Dim hmWindFLashHndl As IntPtr = enumerator.FindWindowExInt(Nothing, Nothing, ThunderFrame, "Flash File Selection")
Dim hmWindPane As AutomationElement = hmWind.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "pane"))
Dim hmWindPaneBtns As AutomationElementCollection = hmWindPane.FindAll(TreeScope.Subtree, New PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "button"))
Click_Object(hmWindPaneBtns(4), hmWindPaneBtns(4).GetClickablePoint)
Dim hmWindFlashFileFrame As AutomationElement = hmWind.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Flash File Selection"))
Dim hmWindFlashFileBox As AutomationElement = hmWindFlashFileFrame.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "edit"))
Click_Object(hmWindSetup, hmWindSetup.GetClickablePoint)
'Get handle for new setup window
Dim hmSetup As IntPtr = enumerator.FindWindowExInt(Nothing, Nothing, ThunderForm, "Setup")
If hmSetup = IntPtr.Zero Or hmSetup = 0 Or hmSetup = vbEmpty Or hmSetup = vbNull Then
hmSetup = enumerator.FindWindowExInt(Nothing, Nothing, ThunderForm, Nothing)
End If
'Get new window with listbox of ComPorts and get other controls inside new window
Dim hmSetupWind As AutomationElement = AutomationElement.FromHandle(hmSetup)
Dim hMSetupRemove As AutomationElement = hmSetupWind.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Remove"))
Dim hmSetupOk As AutomationElement = hmSetupWind.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "OK"))
Dim hmSetupON As AutomationElement = hmSetupWind.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "On"))
Dim hmSetupClear As AutomationElement = hmSetupWind.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Clear"))
Dim hmSetupStatList As AutomationElement
Dim hmListHndl As IntPtr = enumerator.FindWindowExInt(Nothing, Nothing, ThunderList, Nothing)
Dim hmListBox As AutomationElement
If hmListHndl = IntPtr.Zero Or hmListHndl = 0 Or hmListHndl = vbEmpty Or hmListHndl = vbNull Then
hmListBox = hmSetupWind.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "list"))
Else
hmListBox = AutomationElement.FromHandle(hmListHndl)
End If
'Get all ListBoxes of the new window
Thread.Sleep(250)
Dim hmSetupParent As TreeWalker
'iterating through all listboxes - if not original list box then
Dim checkItm As AutomationElement
Dim checkItmCache As New CacheRequest
checkItmCache.Add(AutomationElement.LocalizedControlTypeProperty)
Click_Object(hMSetupRemove, hMSetupRemove.GetClickablePoint)
Dim hmListItems As AutomationElementCollection = hmListBox.FindAll(TreeScope.Subtree, New PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "list item"))
For Each hmListItem As AutomationElement In hmListItems
If hmListItem.Current.Name = ComNum Then
'DoubleClick list item for ComPort of Printer
hmListItem.SetFocus()
DoubleClick_Object(hmListItem, hmListItem.GetClickablePoint)
'Get info for new Window
Dim hmPortSetupWindHndl As IntPtr = enumerator.FindWindowExInt(Nothing, Nothing, ThunderForm, Nothing)
Dim hmPortsetupOK As AutomationElement = AutomationElement.FromHandle(hmPortSetupWindHndl).FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Ok"))
'Click OK on new window with Port Settings - Defaults are fine.
Click_Object(hmPortsetupOK, hmPortsetupOK.GetClickablePoint)
'Turn port ON
Click_Object(hmSetupON, hmSetupON.GetClickablePoint)
'Get items from Status Boxt
Dim hmSetupStatFind As TreeWalker
Click_Object(hmSetupOk, hmSetupOk.GetClickablePoint)
Exit For
End If
Next hmListItem
Click_Object(hmWindFlashFileBox, hmWindFlashFileBox.GetClickablePoint)
My.Computer.Clipboard.SetText(MyProcessControl.myPath & "TPG\189-798F304A.BIN")
My.Computer.Keyboard.SendKeys("^V", True)
Click_Object(hmWindSend, hmWindSend.GetClickablePoint)
Dim hmStatus As AutomationElement = hmWind.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, "Status"))
Dim SectorStrng As String = "Derps"
Do Until SectorStrng.Contains(ComNum & " open")
Dim hmStatLines As AutomationElementCollection = hmStatus.FindAll(TreeScope.Subtree, New PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "list item"))
For a = 0 To hmStatLines.Count - 1
If hmStatLines(a).Current.Name.Contains(ComNum & " open") Then
SectorStrng = hmStatLines(a).Current.Name
End If
Next
Dim e As EventArgs
MyProcessControl.ForceUIToUpdate()
MyProcessControl.OnTimedEvent(mW, e)
Loop
If Process.GetProcessesByName("hmutility").Count > 0 Then
Commands(0, 0) = "/C TaskKill /FI ""imagename eq hmutility.exe"" /F"
Commands(1, 0) = "Successfully able to kill Firmware Utility."
Commands(2, 0) = "Unable TypeOf kill Firmware Utility."
Commands(3, 0) = "Restarting TPG Printer Firmware Utility"
Commands(4, 0) = "Confirming TPG Printer Firmware"
Commands(5, 0) = "False"
MyProcessControl.ProcessCommandLine("cmd.exe", Commands, mW.PrinterCheckText2)
End If
End Sub
Public Shared Sub DoubleClick_Object(givenElement As AutomationElement, mousePoint As Point)
givenElement.SetFocus()
'Adding a 1/4 second sleep ensures object has proper focus before clicking - sometimes the routines moved too fast and threw an exception.
Thread.Sleep(250)
Input.Mouse.MoveTo(New System.Drawing.Point(mousePoint.X, mousePoint.Y))
Input.Mouse.DoubleClick(Input.MouseButton.Left)
End Sub
Public Shared Sub Click_Object(givenElement As AutomationElement, mousePoint As Point)
givenElement.SetFocus()
Thread.Sleep(250)
Input.Mouse.MoveTo(New System.Drawing.Point(mousePoint.X, mousePoint.Y))
Input.Mouse.Click(Input.MouseButton.Left)
End Sub