A much simpler alternative to the FindWindow API for retrieving the HWND of UserForms

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,577
Office Version
  1. 2016
Platform
  1. Windows
Hi all,

Just wanted to share this little known API function for those who need to retrieve the Window handle (hwnd) of a userform.

As you all know, the standard approach is to use the FindWindow API but this has two awkward issues :

Code:
[COLOR=#000000]Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long[/COLOR]

1- The first argument of the FindWindow API expects the UserForm Window Class name . This adds complexity to the code because there are two different Class names (ThunderXFrame vs ThunderDFrame) depending on the excel application version ...So an extra check is needed.

2 - The FindWindow function expects the Form's Caption to be passed in its second argument . This too, may cause a problem if there happens to be more than one instance of a userform loaded @ the same time with the same Caption.

Fortunately, there is this handy API that I came accross recently called IUnknown_GetWindow which dispenses us from the two problems mentioned above and is very simple . This API is exported by its ordinal number by the shlwapi library.

The API has two parameters : An IN parameter that receives a pointer to userform object and an OUT parameter that returns a pointer to the hwnd.

https://msdn.microsoft.com/en-us/library/windows/desktop/bb773814(v=vs.85).aspx

Usage Example:

Code:
Option Explicit

#If Win64 Then
    Private Declare PtrSafe Function IUnknown_GetWindow Lib _
    "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hwnd As LongPtr) As Long
#Else
    Private Declare Function IUnknown_GetWindow Lib _
    "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hwnd As Long) As Long
#End If
 
Public Function GetUserformHwnd(ByVal ufmTarget As MSForms.UserForm) As Long
     IUnknown_GetWindow ufmTarget, VarPtr(GetUserformHwnd)
End Function


Sub Test()
     
    Dim ufm1 As UserForm1
    Dim ufm2 As UserForm1
    Dim lngUserform As Long
     
    Set ufm1 = New UserForm1
    Set ufm2 = New UserForm1
     
    For lngUserform = 0 To UserForms.Count - 1
        Debug.Print GetUserformHwnd(UserForms(lngUserform))
    Next lngUserform
     
End Sub

One would expect to use this neat API to get the much needed hwnd of a workbook window .. Unfortunately, this is not true. This API function seems to only work with a limted number of COM Interfaces .
 
Last edited:

Excel Facts

Back into an answer in Excel
Use Data, What-If Analysis, Goal Seek to find the correct input cell value to reach a desired result
Yet another easy alternative :

I never remembered that the UserForm Class implemented the IAccessible Interface:

Code:
Private Sub UserForm_Initialize()
    Debug.Print TypeOf Me Is IAccessible [B][COLOR=#008000]'<==Returns TRUE[/COLOR][/B]
End Sub

Based on the above fact, one could conviniently use the WindowFromAccessibleObject API to retrieve the hwnd of the userform as shown below :

Code:
Option Explicit

#If VBA7 Then
    Private Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As LongPtr) As Long
#Else
    Private Declare Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As Long) As Long
#End If

Private Sub UserForm_Activate()

    #If VBA7 Then
        Dim hwnd As LongPtr
    #Else
        Dim hwnd As Long
    #End If

    Call WindowFromAccessibleObject(Me, hwnd)
 
    MsgBox hwnd
 
End Sub


Or from a generic function in a Standard Module like in the previous post:

Code:
Option Explicit

#If VBA7 Then
    Private Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As LongPtr) As Long
#Else
    Private Declare Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As Long) As Long
#End If


#If VBA7 Then
    Function GetUserformHwnd(ByVal ufmTarget As MSForms.UserForm) As LongPtr
#Else
    Function GetUserformHwnd(ByVal ufmTarget As MSForms.UserForm) As Long
#End If
        
    WindowFromAccessibleObject ufmTarget, GetUserformHwnd

End Function


Sub Test()
     
    Dim ufm1 As UserForm1
    Dim ufm2 As UserForm1
    Dim lngUserform As Long
     
    Set ufm1 = New UserForm1
    Set ufm2 = New UserForm1
     
    For lngUserform = 0 To UserForms.Count - 1
        Debug.Print GetUserformHwnd(UserForms(lngUserform))
    Next lngUserform
     
End Sub
 
Last edited:
Upvote 0
Hi Jaafar,

I want to change existing code which uses FindWindow to retrieve the hwnd of a userform to use your WindowFromAccessibleObject method instead, but the handles returned are different.

Here is a test procedure showing the problem:

Code:
Option Explicit

Public Const ConstWindowsClass As String = "ThunderDFrame"

[URL=https://www.mrexcel.com/forum/usertag.php?do=list&action=hash&hash=If]#If[/URL]  VBA7 Then
    Public Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc" _
        (ByVal pacc As IAccessible, phwnd As LongPtr) As Long
    Public Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
        (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
[URL=https://www.mrexcel.com/forum/usertag.php?do=list&action=hash&hash=Else]#Else[/URL] 
    Public Declare Function WindowFromAccessibleObject Lib "oleacc" _
        (ByVal pacc As IAccessible, phwnd As Long) As Long
    Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
        (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
[URL=https://www.mrexcel.com/forum/usertag.php?do=list&action=hash&hash=End]#End[/URL]  If


[URL=https://www.mrexcel.com/forum/usertag.php?do=list&action=hash&hash=If]#If[/URL]  VBA7 Then
    Private Function GetUserformHwnd(ByVal ufmTarget As MSForms.UserForm) As LongPtr
[URL=https://www.mrexcel.com/forum/usertag.php?do=list&action=hash&hash=Else]#Else[/URL] 
    Private Function GetUserformHwnd(ByVal ufmTarget As MSForms.UserForm) As Long
[URL=https://www.mrexcel.com/forum/usertag.php?do=list&action=hash&hash=End]#End[/URL]  If
        
    WindowFromAccessibleObject ufmTarget, GetUserformHwnd
    
End Function


'Comparing handle returned by WindowFromAccessibleObject and FindWindow

Sub Test2()
     
    Dim ufm1 As UserForm1
    
    Set ufm1 = New UserForm1
    ufm1.Show vbModeless
    
    Debug.Print "FindWindow ="; FindWindow(ConstWindowsClass, ufm1.Caption)    
    Debug.Print "WindowFromAccessibleObject ="; GetUserformHwnd(ufm1)
     
End Sub
What am I missing?
 
Upvote 0
Hi John,

Strange! The two methods return exactly the same handle for me. I have just tested your code and it works just as expected.

Are you testing on a 32bit or 64bit system ?
 
Upvote 0
Here I use Windows 10 64-bit and Excel 2010 64-bit.

I hope someone here can try this API in order to confirm whether it always works as expected or not ...Having said that, I don't see how WindowFromAccessibleObject won't return the correct hwnd of the UserForm IAccessible Interface
 
Upvote 0
I ran the test on Windows XP and Excel 2007 (a 32-bit system) and both functions returned the same window handle.
 
Upvote 0
Hi Jaafar and John:

I tested John's code at post # 5 against WIN7 64-bit running Office/Excel installed in 32-bit; returned same handle values for me.

Mark
 
Last edited:
Upvote 0

Forum statistics

Threads
1,213,546
Messages
6,114,253
Members
448,556
Latest member
peterhess2002

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top