0

Just looking for a pointer as I'm struggling to find material online...

Is there a library or reference I can use in VB.NET to assign an arbitrary open window to an object via its handle, such that I can then view the elements & attributes associated with that window? I can identify the handle via Process.MainWindowHandle (i.e. System.Diagnostics.Process.GetProcesses) but how can I then use that handle to "hook" onto the window as an object?

(FYI The window is independent of my application, it's not a form or anything like that - it's just an open window)

All the material I find online is telling me how to get the handle but that's not the issue; I have the handle but I don't know how to use it to "hook" onto the window programmatically?

Would appreciate any pointers anyone could provide!

Thanks

Al

Ken White
  • 123,280
  • 14
  • 225
  • 444
Alan O'Brien
  • 151
  • 1
  • 13
  • Could you describe what you'd like to do once you hook onto the window? Possibly that'll give some more hints on how to help. – Andrew Mortimer Apr 06 '22 at 17:15
  • Sure - fairly simple really - it's a very basic "OK" dialog that I need to acknowledge. I'm trying to build a service that can detect if/when the window appears and automatically acknowledge it. I don't control it and I can't suppress it - I just want to clear it. At this stage, I have the PID for a given instance but there isn't enough information in the process object for me to accurately identify the specific window. So I'm trying to use the window handle to drill deeper and see what lies within (i.e. identify the method attached to "OK" button and trigger it) Does that make sense? – Alan O'Brien Apr 06 '22 at 17:19
  • https://stackoverflow.com/questions/23054337/how-can-i-programmatically-click-a-external-application-using-vb-net – Andrew Mortimer Apr 06 '22 at 17:22
  • Or maybe https://www.daniweb.com/programming/software-development/threads/333841/visual-basic-press-button-in-another-program – Andrew Mortimer Apr 06 '22 at 17:23
  • https://stackoverflow.com/questions/4899573/click-on-a-button-in-another-application-from-my-c-sharp-application – Andrew Mortimer Apr 06 '22 at 17:23
  • Awesome, thanks @AndrewMortimer , let me play around with these and revert.... – Alan O'Brien Apr 06 '22 at 17:24
  • Happy to help a fellow Dub! – Andrew Mortimer Apr 06 '22 at 17:31
  • What does *"hook" onto the window programmatically* mean, in your view? What do you want to actually do with this *foreign* Window? -- If you want to detect when a Window is opened anywhere in the System, UI Automation is the usual tool. Its `WindowPattern` can raise events when a Window is opened or closed and you can *interact* with it. Of course you can also close a Window. – Jimi Apr 07 '22 at 01:21
  • @Jimi pretty much what you've described - take the window into an object such that it can be inspected for attributes and, if possible, interacted with. Part of the problem I have is that the window has no caption, it's not a standard message box (it's some kind of MS Office 365 "Experience" dialog) and the parent process has no discernible attributes to distinguish it. So I'm trying to find a way of consistently identifying it (I don't want to be OK'ing random popups, just this very particular one) And of course it may or may not be present at all at any given time. I'll try UI Automation... – Alan O'Brien Apr 07 '22 at 07:41
  • 1
    `Inspect.exe` is part of the VS installation. It's usually located in `[Drive]:\Program Files (x86)\Windows Kits\10\bin\[Version ID]\x64\inspect.exe`. Use this tool to inspect the Dialog. You can probably use its `ClassName` to determine if it's the Window you're looking for when the [WindowPattern.WindowOpenedEvent](https://learn.microsoft.com/en-us/dotnet/api/system.windows.automation.windowpattern.windowopenedevent) notifies you that a Window has been shown. – Jimi Apr 07 '22 at 07:48
  • Okay `Inspect.exe` is awesome! Question is, though, what `AutomationElement` does one pass as argument when creating an event listener via `Automation.AddAutomationEventHandler` ? If the window isn't opened yet, I don't have an element to pass; if the window _has_ opened, the event has already taken place, and I've nothing to trap? Am I missing something obvious? – Alan O'Brien Apr 07 '22 at 13:03
  • You attach the `WindowOpenedEvent` to the `AutomationElement.RootElement` (represents the current Desktop). See here: [Run event when any Form loads](https://stackoverflow.com/a/55960110/7444103) (C#) and here: [Add an event to all Forms in a Project](https://stackoverflow.com/a/51505218/7444103) (VB.Net). Remember to unsubscribe to the event -- If the Window can be already opened, you can initially use the same Element (`RootElement`) to `FindFirst()` / `FindAll()` Windows that have specific properties (as the `ClassName` + others). Setup a multi `Condition` object to specify these details. – Jimi Apr 07 '22 at 13:52
  • Amazing! This is working perfectly now, thank you so much @Jimi! Great intro to UI Automation, I can see how this could be so useful for unit testing. Not sure if I should post the code as an answer just so I can mark the question as officially answered (I thought answering your own question was frowned upon?) but do want to mark it as answered somehow and give you the credit you deserve? What is the protocol? – Alan O'Brien Apr 07 '22 at 15:50
  • I sometimes use this *pattern* when I suggest a solution to a problem and the OP has never before used the related tools, so I act as a *guide*. Slapping a bunch of lines of code that barely have meaning to the asker into the answer box, can be more likely confusing than helpful -- Since you followed my suggestion and you came up with a working solution, then by all means post it as the answer. [Answering your own question](https://stackoverflow.com/help/search?q=answer+own+question) is not *frowned upon* at all -- If you have *Credits* to spare, send them to the Ukrainians :) – Jimi Apr 07 '22 at 20:29

1 Answers1

1

Here is the solution I arrived at, which is working exactly as intended. Cannot claim credit for it as @Jimi gave me all the necessary pointers for me to arrive at this. However, the snippet may be helpful for others trying to navigate UI Automation for the first time, like I was.

Private Sub StartListening()
    Try
        Dim eventHandler As AutomationEventHandler = AddressOf OnWindowOpenOrClose
        Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Subtree, eventHandler)

    Catch ex As Exception

    End Try
End Sub

Private Sub StopListening()
    Try
        Automation.RemoveAllEventHandlers()

    Catch ex As Exception

    End Try
End Sub

Private Sub OnWindowOpenOrClose(ByVal src As Object, ByVal e As AutomationEventArgs)

    Dim sourceElement As AutomationElement
    Dim okButton As AutomationElement
    Dim okButtonCondition As Condition
    Dim okButtonInvokePattern As InvokePattern

    Try
        sourceElement = DirectCast(src, AutomationElement)
        If sourceElement IsNot Nothing Then
            If sourceElement.Current.ControlType.Equals(ControlType.Window) AndAlso sourceElement.Current.ClassName = "NUIDialog" AndAlso sourceElement.Current.Name = "<Insert Dialog Name Here As Necessary>" Then
                If e.EventId Is WindowPattern.WindowOpenedEvent Then
                    okButtonCondition = New AndCondition(New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button),
                                                            New PropertyCondition(AutomationElement.NameProperty, "OK"))
                    okButton = sourceElement.FindFirst(TreeScope.Subtree, okButtonCondition)
                    If okButton IsNot Nothing Then
                        okButtonInvokePattern = DirectCast(okButton.GetCurrentPattern(InvokePattern.Pattern), InvokePattern)
                        okButtonInvokePattern.Invoke()
                    End If
                    Return
                End If
            End If
        End If

    Catch
        Return
    End Try

End Sub
Alan O'Brien
  • 151
  • 1
  • 13
  • 1
    This: `Dim eventHandler As AutomationEventHandler = AddressOf OnWindowOpenOrClose` needs to be promoted to a Field, because - when the app is published - the chance that the Event Handler object is garbage collected is relatively high. If some conditions are met (I cannot see how this code is used, in practice), it **will be** collected. -- You should use a distinct handler for the `Opened` and `Closed` events (if you add handler to the `WindowClosedEvent`, but I don't see it here). – Jimi Apr 09 '22 at 00:49
  • Thanks @Jimi, only seeing this now, didn't receive a notification for some reason - you've lost me somewhat there, how does one "promote" a handler to a "Field" exactly? – Alan O'Brien Apr 29 '22 at 14:15