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,621
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:
I came across the solution whilst playing around with pointers, though I don't understand why.

It works if you declare the first parameter of WindowFromAccessibleObject as "ByVal pacc As Object" instead of "ByVal pacc As IAccessible".

Here is the code, including different declarations of WindowFromAccessibleObject if the code is running as 64-bit or as 32-bit.

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
    [URL=https://www.mrexcel.com/forum/usertag.php?do=list&action=hash&hash=If]#If[/URL]  Win64 Then
        Public Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc" _
            (ByVal pacc As IAccessible, phwnd As LongPtr) As Long
    [URL=https://www.mrexcel.com/forum/usertag.php?do=list&action=hash&hash=Else]#Else[/URL] 
        Public Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc" _
            (ByVal pacc As Object, phwnd As LongPtr) As Long
    [URL=https://www.mrexcel.com/forum/usertag.php?do=list&action=hash&hash=End]#End[/URL]  If
    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 - now they are the same value

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)
    
    Unload ufm1
     
End Sub
 
Upvote 0

Excel Facts

Format cells as date
Select range and press Ctrl+Shift+3 to format cells as date. (Shift 3 is the # sign which sort of looks like a small calendar).
I had already done that before posting this API approach by replacing the pacc arg type with Object and IUknown and they both worked as expected.

I am not sure why it didn't work for you using IAccessible as per the documentation of the WindowFromAccessibleObject API but I'll try asking some COM experts in other forums.
 
Upvote 0

Forum statistics

Threads
1,215,140
Messages
6,123,269
Members
449,093
Latest member
Vincent Khandagale

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