Sunday, 14 April 2019

VBA - Windows API - Find all windows by class and then filter by process id

I needed some code to find all the top windows of Excel, that is of class XLMAIN and then filter these by process id. This is because elsewhere I am writing some C# code to acquire an instance of Excel via its window handles but all I have been given is a process id. Because I like to test logic in VBA I have written a VBA version. I am happy to share.

To begin, I use FindWindowEx to find all the windows I am interested in. All Excel instances have a main window of a class XLMAIN, and all of these have a parent window of the desktop. So supply zero for the first argument to signify the desktop and supply XLMAIN as the third argument, the fourth argument ignored. The second argument is used but varies, it allows us to loop through multiple results, we supply the previous result to get the next result. So I wrote a function to collect these to a collection, the function is called AllWindowsByClass(). The function is paramterised so that it can find windows of other class types.

Once in possession of a collection of window handles, I want to filter by a process id that I have been given. GetWindowThreadProcessId is the windows API that is best for this. In the function FilterWindowHandlesByProcessId() I loop through the a collection of handles and filter them to a new collection.

Enjoy!


Option Explicit
Option Private Module

Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWndParent As Long, _
ByVal hWndChildAfter As Long, ByVal lpszClass As String, ByVal lpszWindow As String) As Long

Private Declare Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As Long, lpdwProcessId As Long) As Long

Private Function AllWindowsByClass(ByVal sClass As String) As VBA.Collection
    Dim colRet As VBA.Collection
    Set colRet = New VBA.Collection
    
    Dim hWndParent As Long
    
    hWndParent = FindWindowEx(0, 0, sClass, vbNullString)
    While hWndParent <> 0
        colRet.Add hWndParent
        hWndParent = FindWindowEx(0, hWndParent, sClass, vbNullString)
    
    Wend
    
    Set AllWindowsByClass = colRet
End Function


Private Function FilterWindowHandlesByProcessId(ByVal colWindowsHandles As VBA.Collection, ByVal lFilterProcessId As Long) As VBA.Collection
    Dim colRet As VBA.Collection
    Set colRet = New VBA.Collection

    Dim lLoop As Long
    For lLoop = 1 To colWindowsHandles.Count
        Dim lWinHandle As Long
        lWinHandle = colWindowsHandles.Item(lLoop)
    
        Dim lProcessId As Long
        GetWindowThreadProcessId lWinHandle, lProcessId
        
        If lProcessId = lFilterProcessId Then
            colRet.Add lWinHandle
        End If
    Next

    Set FilterWindowHandlesByProcessId = colRet
End Function



Here are some test functions but they have hard coded values that were valid for me and I determined whilst looking at Spy++ and other Windows diagnostic tools. Nevertheless they demonstrate how to call the above functions.



'************************************************************************************************
'* TEST FUNCTIONS
'************************************************************************************************

Private Sub TestAllWindowsByClass()
    Dim col As VBA.Collection
    Set col = AllWindowsByClass("XLMAIN")
    Debug.Assert col.Count = 2  'may differ for you!
End Sub

Private Sub TestFilterWindowHandlesByProcessId()
    
    Dim colWinHandles As VBA.Collection
    Set colWinHandles = AllWindowsByClass("XLMAIN")

    Debug.Assert colWinHandles.Count = 2

    Dim lTestProcessId As Long
    lTestProcessId = 24272 ' a process currently running on my PC, probably will differ for you!

    Dim colFiltered As VBA.Collection
    Set colFiltered = FilterWindowHandlesByProcessId(colWinHandles, lTestProcessId)

    Debug.Assert colFiltered.Count = 1
End Sub

No comments:

Post a Comment