[VBA × Windows API] How to Use the FindWindowEx Function

FindWindowEx Function

The FindWindowEx function retrieves a handle to a child window inside a given parent window, by matching its class name and/or window name. The Windows API also offers a similarly named FindWindow, but the two differ subtly in both usage and purpose, so be careful not to mix them up.

When you hear “window," you probably think of a top-level dialog-like thing such as a UserForm. But in Windows, the buttons, text boxes, and other controls living inside a window are also windows in their own right. (Think of them as the API-level equivalent of a CommandButton or TextBox on a UserForm.)

For example, the dialog you see when you open the Project Properties in the VBE is structured like this. You can probably guess which window corresponds to which element on screen.

This tree structure was captured with Microsoft Spy++, which displays every window’s information in the order [window] [window handle] [window name] [class name].

FindWindowEx is the function that takes the [window name] and [class name] shown in a tree like this and returns the matching [window handle]. In the image above, for instance, you can pass the window name "OK" and the class name "Button" to obtain the handle 000A0F8A — which points to the OK button.

Once you have a handle to a child window, you can drive it programmatically from your code. If the child window is a button, you can click it; if it’s a checkbox, you can toggle it on and off; if it’s a text box, you can type values into it — and so on.
(To actually perform these operations you use functions such as SendMessage and PostMessage.)

Because FindWindow can only retrieve top-level windows (the kind most people picture when they hear “window"), you need FindWindowEx whenever you want to reach a window-inside-a-window — that is, a button, edit box, or similar child control.

 

Usage

Before calling FindWindowEx, you must declare it at the top of your module.
Without the declaration the function cannot be used and will raise an error, so don’t forget it.

The declaration differs depending on whether your Office/VBA host is 32-bit or 64-bit. Place one of the following near the top of your module (just after Option Explicit) to make the function available inside that module.

icon-code 64-bit

Declare PtrSafe Function FindWindowEx Lib “user32" Alias “FindWindowExA" _
(ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr

icon-code 32-bit

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

If you are not sure which one to use, paste the following block at the top of your module. It automatically selects the correct declaration at compile time.
In the VBE, the unused branch may be shown in red, but this does not affect execution.

#If VBA7 Then
    Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
#Else
    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
#End If

You can also prefix the declaration with Private or Public to control its scope.

Private Declare PtrSafe Function … : callable only within the current module
Public Declare PtrSafe Function … : callable from outside the module

 
 

Syntax

The syntax of the FindWindowEx function is as follows.

icon-code FindWindowEx function

hWnd = FindWindowEx(hWnd1, hWnd2, lpsz1, lpsz2)

Arguments

hWnd1 (64-bit: LongPtr / 32-bit: Long)

Handle to the parent window whose children you want to search.
If you pass NULL (0), the desktop window is used as the parent.
Because the desktop window is the ultimate ancestor of every window, passing NULL here effectively turns the call into a top-level search — the same behavior as FindWindow.
 

hWnd2 (64-bit: LongPtr / 32-bit: Long)

Handle at which to start the search among the children of hWnd1.
The search proceeds through the children in Z-order, but it begins at the window immediately after the handle you pass here.
If you pass NULL (0), the search starts at the first child of hWnd1.
Note: this handle must be a direct child of hWnd1.

Child-window Z-order matches the top-to-bottom order you see in the Spy++ tree view.
For example, in the image below, passing 000811A4 as hWnd1 and 0 (NULL) here makes the search walk from the first child 00071268 down through 000E1046 — all 11 child windows. If instead of NULL you pass 05BF0804, the search is narrowed to just the 5 windows from 01200632 to 000E1046.

 
lpsz1
(String)

The class name of the window whose handle you want.
A window class name is an identifier Windows uses to tell different kinds of windows apart. You can look class names up easily with Microsoft’s official tools Microsoft Spy++ or Inspect.exe.

If you pass vbNullString (NULL) for this argument, every child window whose name matches lpsz2 becomes a candidate — regardless of class. If lpsz2 alone is specific enough to uniquely identify the window, passing vbNullString for this argument is perfectly fine.
 

lpsz2 (String)

The window name (caption) of the window whose handle you want.
As with the class name, you can inspect window names using Microsoft Spy++ or Inspect.exe. Unlike class names, window names can change at runtime depending on state, so be aware of that.

If you pass vbNullString (NULL), every child window matching lpsz1 becomes a candidate. When lpsz1 alone uniquely identifies the target, passing vbNullString here is also fine.
   

Return value

hWnd (64-bit: LongPtr / 32-bit: Long)

On success, the return value is a handle to the first child window that matches the criteria you passed in. If several windows match, the one closest to the front (in Z-order) is returned.

On failure, the return value is NULL (0).
As with the arguments, the return type depends on whether you are running 32-bit or 64-bit VBA — be mindful of this when declaring variables that receive the result.
 

icon-edit Which window is returned first? ―Although it’s not visually obvious, child windows also have a Z-order. That ordering is maintained internally and can be inspected with tools such as Microsoft Spy++ or Inspect.exe.
 
FindWindowEx walks this ordering from front to back. For example, if you pass "Button" as lpsz1, you get the handle of the frontmost button. When several child windows share the same window name and class name, you can use the hWnd2 argument — combined with this Z-order knowledge — to pinpoint exactly the one you want.

 

Sample Code

The sample below uses FindWindowEx together with FindWindow and SendMessage to send a string to Notepad. Open Notepad first, then run this macro, and the text “Sent from VBA" will be typed into it. If multiple Notepad windows are open, the string is sent to whichever one FindWindow returns first (essentially the frontmost Notepad window).

Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr

Const WM_IME_CHAR As Long = &H286

Sub main()

    Dim hWnd        As LongPtr
    Dim hWndEdit    As LongPtr
    Dim sText       As String
    Dim sChar       As String
    Dim i           As Long

    ' Get the Notepad window handle
    hWnd = FindWindow("Notepad", vbNullString)
    If hWnd = 0 Then
        Call MsgBox("Please launch Notepad first.", vbExclamation)
        Exit Sub
    End If
    
    ' Get the handle of the text input area inside Notepad
    hWndEdit = FindWindowEx(hWnd, 0, "Edit", vbNullString)

    ' Message to send (line breaks are allowed)
    sText = "Sent from VBA" & vbLf
    
    ' Send the string one character at a time
    For i = 1 To Len(sText)
    
        ' Convert the character for sending
        sChar = Asc(Mid(sText, i, 1))
        
        ' Send the character
        Call SendMessage(hWndEdit, WM_IME_CHAR, sChar, 0)
        
    Next i

End Sub

 

icon-share-square VBA × Windows API Index

For other Windows API functions, please also refer to the index page below.

icon-share-square Reference

Microsoft official: FindWindowExW function (winuser.h) — Win32 apps

Excel,VBA,Windows API