Skip to: Site menu | Main content

Login

Name: 
Password:
Remember me?
Register

Obtaining A List all Window Handles, Class names and Titles

written by Mark Rowlinson - Last updated Oct 2004

Introduction

The most important feature when first looking into programming with the Win32 API is to being able to find the handles and class names of the windows. Many API functions require the window handle before anything can be acheived. The code in this article will enable you to obtain a list of all the window handles, class names and titles currently running in Excel. In effect it is doing one of the things done by a program such as Spy++.

API Declarations

There are 3 API functions used in this code:

The functions are declared as follows:

 
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _ 
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long 
 
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _ 
(ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long 
 
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _ 
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long 

Buffers

The latter 2 API functions require the use of buffers. It is very common for API functions to use buffers. In Visual Basic and VBA we are used to calling functions and using their return value. In API programming you often pass a variable to the function that is written to by the function. In the case of strings this means passing a buffer. You can create a buffer in several ways but the way I tend to use is to use the 'String$()' function to create a string of null characters as follows:

strText = String$(100, Chr$(0)) 

strText now contains a string of 100 null characters. We pass this string and its length that we know to be 100 to the functions. The function then returns the length of the string it has written to the buffer so we can determine the string which to output using the 'Left$()' function passing it our buffer and the API function's return value. Putting this together we have:

 
 'create the buffer
strText = String$(100, Chr$(0)) 
 'call the function
lngRet = GetClassName(hWnd, strText, 100) 
 'determine the string we wish to output
strOutput = Left$(strText, lngRet) 

Recursion

The code we are creating in this article uses a technique called recursion. A ecursive procedure is a proedure that calls itself. At first this may seem like a never ending loop is created and it is possible if you are not careful to create one! The key to it is to create a condition in the function that when satisfied no further calls are made. In the case of the code on this page the condition in a 'while loop'. The routine loops through calling itself until no further windows are found. ecursion is an area that can be quite difficult to get your head round but when you do you will find it a very useful technique

Finding The Windows

The first job of the code is to find the windows that are running. To do this we use the FindWindowEx function We pass it the parent window we want to search, the child window we want to search next in the z-order from and then the class name and window title of the window we want to find. In this case we want to find all windows so we use 'vbNullString' in place of the last 2 arguments. It is important that we use vbNullString and not "" as vbNullString is a special value. Alternatively we could declare the function:

ByVal lpsz1 As Long 

and pass the value 0& (The & indicates it is a long).

The parent argument we will make flexible and pass this as an agument to the Sub. The child argument should first be 0& but for each subsequent call should be the previous handle we found so that we loop through all the windows.

Putting something simple together

Ok, now we know how to use the functions we can put together our sub. We will just consider the case of obtaining a list of class names for now. The output of the sub will be produced on an excel sheet and will start in cell A1. We will then offset by 1 column as we enter a new level of child windows and 1 row for each window found. As the column offset is dependent on the level in the heirachy we are looking at we shall pass this as another argument to the sub. The row however needs to be incremented for every window found so we will declare that at module level. We also need to declare a variable in the sub to hold the class name and window handle of each window and also one to be the return value of our API call. We therefore have the following:

 
Dim x As Integer 
 
Private Sub GetWinInfo(hParent As Long, intOffset As Integer) 
    Dim hWnd As Long, lngRet As Long 
    Dim strText As String 
     

The next step is to find the first child of the hParent window. We can easily do this using:

hWnd = FindWindowEx(hParent, 0&, vbNullString, vbNullString) 

We want to loop through every child window we find i.e. while we find a window handle so we can set up a loop:

 
While hWnd <> 0 
     'notice that this time our call to FindWindowEx passes the previously found window
    hWnd = FindWindowEx(hParent, hWnd, vbNullString, vbNullString) 
Wend 
 

For each of these windows though we wnat to find the class name of the window and output it. We therefore need to use the GetClassName function to retrieve the class of the window and output it to the excel sheet as follows:

 
strText = String$(100, Chr$(0)) 
lngRet = GetClassName(hWnd, strText, 100) 
Range("a1").Offset(x, intOffset) = Left$(strText, lngRet) 

Finally we want to increment the row by 1 and also check for children of the window we have found. These child windows need to output with an offset of 1 more than the parent. We can therefore add the following:

 
GetWinInfo hWnd, intOffset + 1 
x=x+1 
 

Putting it All Together

We now have a working Sub as follows:

 
Dim x As Integer 
 
Private Sub GetWinInfo(hParent As Long, intOffset As Integer) 
    Dim hWnd As Long, lngRet As Long 
    Dim strText As String 
     
    While hWnd <> 0 
         'Output class name
        strText = String$(100, Chr$(0)) 
        lngRet = GetClassName(hWnd, strText, 100) 
        Range("a1").Offset(x, intOffset) = Left$(strText, lngRet) 
         'Check for children and increment row
        GetWinInfo hWnd, intOffset + 1 
        x=x+1 
         'Find next window
        hWnd = FindWindowEx(hParent, hWnd, vbNullString, vbNullString) 
    Wend 
End Sub 
 

This sub can be started from any window and will display all children of that window when passed the relevent window handle. Alternatively you can pass it 0& as the hParent argument and it will find all top level windows and all their children.

Summary

Hopefully the above text will adi your understanding of using API functions as well as showing a few useful techniques. As always if you have any commenst, suggestions or spot any errors please email suggestions@markrowlinson.co.uk. I have added to the code above to provide the added functionality of displaying any or all of the window handles, class names and window text and the full code follows below. All you need to do is call the GetWindows sub. To change the output change the third argument passed to GetWinInfo to be one of those given in the enumeration. Feel free to use and modify the code but I would appreciate it if credit is given where used.

 
Option Explicit 
 
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _ 
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long 
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _ 
(ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long 
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _ 
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long 
 
Private x As Integer 
 
Private Enum winOutputType 
    winHandle = 0 
    winClass = 1 
    winTitle = 2 
    winHandleClass = 3 
    winHandleTitle = 4 
    winHandleClassTitle = 5 
End Enum 
 
Public Sub GetWindows() 
    x = 0 
    GetWinInfo 0&, 0, winHandleClassTitle 
End Sub 
 
 
Private Sub GetWinInfo(hParent As Long, intOffset As Integer, OutputType As winOutputType) 
     'Sub to recursively obtain window handles, classes and text
     'given a parent window to search
     'Written by Mark Rowlinson
     'www.markrowlison.co.uk - The Programming Emporium
    Dim hWnd As Long, lngRet As Long, y As Integer 
    Dim strText As String 
    hWnd = FindWindowEx(hParent, 0&, vbNullString, vbNullString) 
    While hWnd <> 0 
        Select Case OutputType 
        Case winOutputType.winClass 
            strText = String$(100, Chr$(0)) 
            lngRet = GetClassName(hWnd, strText, 100) 
            Range("a1").Offset(x, intOffset) = Left$(strText, lngRet) 
        Case winOutputType.winHandle 
            Range("a1").Offset(x, intOffset) = hWnd 
        Case winOutputType.winTitle 
            strText = String$(100, Chr$(0)) 
            lngRet = GetWindowText(hWnd, strText, 100) 
            If lngRet > 0 Then 
                Range("a1").Offset(x, intOffset) = Left$(strText, lngRet) 
            Else 
                Range("a1").Offset(x, intOffset) ="N/A" 
            End If 
        Case winOutputType.winHandleClass 
            Range("a1").Offset(x, intOffset) = hWnd 
            strText = String$(100, Chr$(0)) 
            lngRet = GetClassName(hWnd, strText, 100) 
            Range("a1").Offset(x, intOffset + 1) = Left$(strText, lngRet) 
        Case winOutputType.winHandleTitle 
            Range("a1").Offset(x, intOffset) = hWnd 
            strText = String$(100, Chr$(0)) 
            lngRet = GetWindowText(hWnd, strText, 100) 
            If lngRet > 0 Then 
                Range("a1").Offset(x, intOffset + 1) = Left$(strText, lngRet) 
            Else 
                Range("a1").Offset(x, intOffset + 1) ="N/A" 
            End If 
        Case winOutputType.winHandleClassTitle 
            Range("a1").Offset(x, intOffset) = hWnd 
            strText = String$(100, Chr$(0)) 
            lngRet = GetClassName(hWnd, strText, 100) 
            Range("a1").Offset(x, intOffset + 1) = Left$(strText, lngRet) 
            strText = String$(100, Chr$(0)) 
            lngRet = GetWindowText(hWnd, strText, 100) 
            If lngRet > 0 Then 
                Range("a1").Offset(x, intOffset + 2) = Left$(strText, lngRet) 
            Else 
                Range("a1").Offset(x, intOffset + 2) ="N/A" 
            End If 
        End Select 
         'check for children
        y = x 
        Select Case OutputType 
        Case Is > 4 
            GetWinInfo hWnd, intOffset + 3, OutputType 
        Case Is > 2 
            GetWinInfo hWnd, intOffset + 2, OutputType 
        Case Else 
            GetWinInfo hWnd, intOffset + 1, OutputType 
        End Select 
         'increment by 1 row if no children found (added from above article to remove blank lines)
        If y = x Then 
            x = x + 1 
        End If 
         'now get next window
        hWnd = FindWindowEx(hParent, hWnd, vbNullString, vbNullString) 
    Wend 
     
End Sub