VBA – Bring a Window to the Front Without Knowing Its Hwnd or Title

I’ve made a couple previous post relating to bringing windows/applications to the foreground:

I was recently thinking about it, especially the case where we can’t directly retrieve the Hwnd value from the instance and I use the Title to find it and I had an idea and thought I’d share it with you all.

The Solution

I wondered if there couldn’t be a simple way to identify the Hwnd by building a list of Hwnd values prior to initiating an application and immediately after, and then comparing the two lists to identify the newly create Hwnd.

In theory, this made sense.  In practice, I found out that Microsoft application create multiple windows, not just one.  So was there a way to isolate the one I was truly after.  In the end, I believe I have to solution that works.  Thus far I have tested in Office 2013, 2019 and 2019.

To make it work I need 2 functions: 1 to generate lists of Handles, 1 to compare the 2 lists and find the newly created Hwnd.

Generate the Current List of Handles

Public Sub UIA_GetHandles(ByVal oDict As Object)
On Error GoTo Error_Handler
    Dim oUIAutomation         As UIAutomationClient.CUIAutomation
    Dim oUIADesktop           As UIAutomationClient.IUIAutomationElement
    Dim oUIAElements          As UIAutomationClient.IUIAutomationElementArray
    Dim i                     As Long

    Set oUIAutomation = New UIAutomationClient.CUIAutomation
    Set oUIADesktop = oUIAutomation.GetRootElement
    Set oUIAElements = oUIADesktop.FindAll(TreeScope_Children, oUIAutomation.CreateTrueCondition)
    For i = 0 To oUIAElements.Length - 1
        With oUIAElements.GetElement(i)
            If .CurrentClassName <> "MSO_BORDEREFFECT_WINDOW_CLASS" Then
                oDict.Add .GetCurrentPropertyValue(UIA_NativeWindowHandlePropertyId), .CurrentName
            End If
        End With
    Next i

Error_Handler_Exit:
    On Error Resume Next
    Set oUIAElements = Nothing
    Set oUIADesktop = Nothing
    Set oUIAutomation = Nothing
    Exit Sub

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Source: UIA_GetHandles" & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Description: " & Err.Description & _
           Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

Notice I exclude the handles with the class ‘MSO_BORDEREFFECT_WINDOW_CLASS’, these are created at the same time, but are not the handle we are after. By excluding these, we seem to be left with a single Handle.

Compare the 2 lists

#If VBA7 Then
    Public Function GetNewHandle() As LongPtr
#Else
    Public Function GetNewHandle() As Long
#End If
    On Error GoTo Error_Handler
    Dim dictkey As Variant

    Set oDictFinal = CreateObject("Scripting.Dictionary")
    Call UIA_GetHandles(oDictFinal)
    DoEvents

    'Compare the 2
    For Each dictkey In oDictFinal.Keys
        If Not oDictInitial.Exists(dictkey) Then
            GetNewHandle = dictkey
        End If
    Next dictkey

Error_Handler_Exit:
    On Error Resume Next
    Set oDictFinal = Nothing
    Set oDictInitial = Nothing
    Exit Function

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Source: GetNewHandle" & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Description: " & Err.Description & _
           Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Function

Usage Example

Module level variable:

Public oDictInitial           As Object   'Scripting.Dictionary
Public oDictFinal             As Object     'Scripting.Dictionary

Actual procedure:

Public Sub LaunchExcel()
'#Const EarlyBind = True 'Use Early Binding, Req. Reference Library
    #Const EarlyBind = False    'Use Late Binding
    #If EarlyBind = True Then
        'Early Binding Declarations
        Dim oExcel            As Excel.Application
        Dim oExcelWrkBk       As Excel.WorkBook
        Dim oExcelWrSht       As Excel.WorkSheet
    #Else
        'Late Binding Declaration/Constants
        Dim oExcel            As Object
        Dim oExcelWrkBk       As Object
        Dim oExcelWrSht       As Object
    #End If
    Dim bExcelOpened          As Boolean
    Dim lRetVal               As Long
    #If VBA7 Then
        Dim lExcelHwnd        As LongPtr
    #Else
        Dim lExcelHwnd        As Long
    #End If

    Set oDictInitial = CreateObject("Scripting.Dictionary")
    Call UIA_GetHandles(oDictInitial) 'Generate our list of initial Handles

    'Start Excel
    On Error Resume Next
    Set oExcel = GetObject(, "Excel.Application")    'Bind to existing instance of Excel
    If Err.Number <> 0 Then    'Could not get instance of Excel, so create a new one
        Err.Clear
        On Error GoTo Error_Handler
        Set oExcel = CreateObject("Excel.Application")
        bExcelOpened = False
    Else    'Excel was already running
        bExcelOpened = True
    End If
    On Error GoTo Error_Handler

    oExcel.ScreenUpdating = True
    oExcel.Visible = True   'Keep Excel hidden until we are done with our manipulation
    Set oExcelWrkBk = oExcel.Workbooks.Add()    'Start a new workbook
    Set oExcelWrSht = oExcelWrkBk.Sheets(1)

    'Bring to the Front By Finding the Newly Created Handle
    lExcelHwnd = GetNewHandle() 'Identify the Newly Created Handle
    If lExcelHwnd <> 0 Then
        lRetVal = SetForegroundWindow(lExcelHwnd)
        If lRetVal = 0 Then
            'Did not get the focus for some reason
        End If
    End If

Error_Handler_Exit:
    On Error Resume Next
    Set oExcelWrSht = Nothing
    Set oExcelWrkBk = Nothing
    oExcel.ScreenUpdating = True
    Set oExcel = Nothing
    Exit Sub

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: LaunchExcel" & vbCrLf & _
           "Error Description: " & Err.Description & _
           Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

So, it is pretty straightforward at the end of the day, and now we don’t even need to get the title to perform comparisons. Just one more possible approach that can be used.