GetInnerAccessHwnd(): Retrieve the Window Handle to the Microsoft Access Canvas

I've referenced this function in several past articles, but never took the time to document it on its own.

As this history suggests, the GetInnerAccessHwnd() function is not particularly useful on its own.  All it does is return a seemingly random number.  That seemingly random number is actually a pointer to a window handle that represents the Access application's so-called "canvas."  

Armed with this information, we can do all sorts of interesting things, including:

The Access Application "Canvas"

The canvas is the area where the forms and reports are "drawn".  In the screenshots below, the canvas is the area that I've highlighted in teal:

Now let's dive into how this function works.

The Approach:

The GetInnerAccessHwnd function is a VBA procedure that simplifies the process of retrieving the inner window handle (HWND) of the Microsoft Access application.

The function relies primarily on the FindWindowEx API call.  If for some reason that approach does not work, the GetParent API call is used to get the parent window of the currently active Access object (form, report, or datasheet).  In fact, the first three years I used this function, I did not use the FindWindowEx API call at all; the function relied entirely on the GetParent API call.  One problem with that approach is that sometimes there is no active form, report, or datasheet (as in the second screenshot above).

I've never bothered to look into it too deeply (because it just works), but I suspect that the function almost never falls back on the old GetParent API code.  

As for the optional ChildHWnd argument, I believe that is also largely a historical artifact and can likely be ignored in most, if not all, circumstances.

The Algorithm:

Here's how the GetInnerAccessHwnd function operates, broken down into layman's terms:

  1. Attempt to find the inner window handle directly by calling FindWindowEx, targeting the main Access application window.
  2. If successful, return this handle immediately – job done.
  3. If not, proceed with error handling enabled to avoid any runtime errors during the process.
  4. Check if an optional child window handle was provided. If not, try to obtain the handle from the active form, report, or datasheet.
  5. With the child window handle in hand (or after attempting to acquire one), call GetParent to climb up the window hierarchy and fetch the inner window handle.
  6. Return the obtained handle for use in further API calls.

The Function:

Here is the GetInnerAccessHwnd() function on its own without its required dependencies:

Function GetInnerAccessHwnd(Optional ByVal ChildHWnd As LongPtr = 0) As LongPtr
    
    GetInnerAccessHwnd = apiFindWindowEx(hWndAccessApp, ByVal 0&, "MDIClient", vbNullString)
    If GetInnerAccessHwnd <> 0 Then Exit Function

    On Error Resume Next
    If ChildHWnd = 0 Then ChildHWnd = Screen.ActiveForm.hWnd
    If ChildHWnd = 0 Then ChildHWnd = Screen.ActiveReport.hWnd
    If ChildHWnd = 0 Then ChildHWnd = Screen.ActiveDatasheet.hWnd
    GetInnerAccessHwnd = apiGetParent(ChildHWnd)
End Function

The Full Code:

The code below includes the required API function declarations along with the function header and relevant notes. The code can be copied and pasted into a blank standard module for a fully-working solution that can be easily integrated into your projects.

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

Private Declare PtrSafe Function apiGetParent _
    Lib "user32" Alias "GetParent" (ByVal hWnd As LongPtr) As LongPtr

'---------------------------------------------------------------------------------------
' Procedure : GetInnerAccessHwnd
' Date      : 11/26/2014 - 12/12/2023
' Author    : Mike Wolfe
' Source    : https://nolongerset.com/getinneraccesshwnd/
' Purpose   : Gets the window handle of the Access "canvas".
' Notes     - We don't use hWndAccessApp here because that's the window handle for the
'               full application; what we really want is the handle for the "inner container";
'               i.e., the Access window minus the docked menus, docked toolbars, and status bar
'---------------------------------------------------------------------------------------
'
Function GetInnerAccessHwnd(Optional ByVal ChildHWnd As LongPtr = 0) As LongPtr
    
    GetInnerAccessHwnd = apiFindWindowEx(hWndAccessApp, ByVal 0&, "MDIClient", vbNullString)
    If GetInnerAccessHwnd <> 0 Then Exit Function

    On Error Resume Next
    If ChildHWnd = 0 Then ChildHWnd = Screen.ActiveForm.hWnd
    If ChildHWnd = 0 Then ChildHWnd = Screen.ActiveReport.hWnd
    If ChildHWnd = 0 Then ChildHWnd = Screen.ActiveDatasheet.hWnd
    GetInnerAccessHwnd = apiGetParent(ChildHWnd)
End Function

Sample Usage

For sample usage, check out all the articles that include calls to the GetInnerAccessHwnd() function, including the following:

KeepFormOnCanvas(): Get Back Here, Form!
What happens if the user resizes their Access window so that our form can’t open in its entirety? KeepFormOnCanvas() to the rescue!
Fun with Form Windows
Using my FillAccessWindow function to resize Form and Report objects relative to the amount of available space on the Access canvas.
CascadeForm(): Prevent Multi-Instance Forms From Hiding Behind Each Other
The CascadeForm() function introduces the cascading-window effect from the Windows operating system to Microsoft Access.
PreviewReport Function
This custom function is the simplest and safest way to preview reports in any Microsoft Access application.
Avoid the Cascading Maximize
Prevent a full screen report from forcing the calling form to be maximized with this clever workaround.

Further Reading

While writing this article, I noticed that the Access.Application.hWndAccessApp method returned a Long data type, even when running in a 64-bit VBA environment.  

I found this to be unexpected and curious, so I explored that topic on its own in full-article length.  You can find that discussion here, along with a solution to the mystery (courtesy of former Access MVP Ben Clothier and Shane Groff of the Microsoft Access engineering team).

The Curious Case of the Return Type of the hWndAccessApp Method in 64-bit VBA
Is everything I thought I knew about 64-bit vs. 32-bit VBA wrong? Or is there a problem with the Access object model? Or is it something else?

Acknowledgements
  • Portions of this article's body generated with the help of ChatGPT
  • Cover image generated by DALL-E-3