Obtaining A List all Window Handles, Class names and Titles
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:
- FindWindowEx - retrieves window handles
- GetClassName - obtains the class of the window given a handle
- GetWindowText - obtains the text in the window title bar or text of the control if available given a window handle
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 Longand 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