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:
- Automatically moving a form to prevent it from getting cropped by the lower-right corner of a too-small application window
- Arranging forms in arbitrary layouts, such as a 2x2 matrix
- Avoiding the "cascading maximize" effect when previewing reports
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:
- Attempt to find the inner window handle directly by calling
FindWindowEx
, targeting the main Access application window. - If successful, return this handle immediately – job done.
- If not, proceed with error handling enabled to avoid any runtime errors during the process.
- Check if an optional child window handle was provided. If not, try to obtain the handle from the active form, report, or datasheet.
- 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. - 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:
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).
Acknowledgements
- Portions of this article's body generated with the help of ChatGPT
- Cover image generated by DALL-E-3