Programming

Microsoft Access VBA UIAutomation InvokePattern Issue

Fix UIAutomation InvokePattern.Invoke not firing Microsoft Access VBA windowless buttons on secondary monitors. Includes DPI scaling, diagnostics and COM.

1 answer 1 view

UIAutomation InvokePattern.Invoke() does not trigger Microsoft Access VBA windowless button action on secondary monitor

Background: Using C# (.NET Framework 4.8) with System.Windows.Automation to invoke a non-standard, windowless button (AutomationElement.Current.NativeWindowHandle == 0) in Microsoft Access VBA.

Problem: InvokePattern.Invoke() executes without errors and UIA diagnostics are identical, but the button action only works when the Access window is on the primary monitor. On secondary monitors, Invoke() returns normally but no action occurs.

Environment:

  • .NET Framework 4.8
  • References: UIAutomationClient, UIAutomationTypes
  • Target: Microsoft Access VBA (windowless controls)
  • OS: Windows 10/11 (multi-monitor setup, possibly per-monitor DPI)

Minimal code:

csharp
public void ClickButton(AutomationElement button)
{
 object patternObj;

 if (button.TryGetCurrentPattern(InvokePattern.Pattern, out patternObj))
 {
 InvokePattern invokePattern = patternObj as InvokePattern;

 if (invokePattern != null)
 {
 invokePattern.Invoke();
 Application.DoEvents();
 Thread.Sleep(200);
 }
 }
}

Diagnostics (identical in both cases):

=== Diagnostics start ===
IsOffscreen: False
IsEnabled: True
HasKeyboardFocus: False
BoundingRectangle: 0;23;109;24
NativeWindowHandle: 0x0
Supported patterns: InvokePatternIdentifiers.Pattern
TryGetClickablePoint: True -> 54,5;35
Top-level window hwnd: 0x2046A
Foreground window hwnd: 0x2046A
InvokePattern invoked — check whether the button was activated.
=== Diagnostics end ===

Attempts tried:

  • Verified IsOffscreen, BoundingRectangle, TryGetClickablePoint (identical)
  • SetForegroundWindow(topHwnd) and button.SetFocus()
  • AutomationElement.FromPoint(clickablePoint) and invoke
  • AttachThreadInput + SetForegroundWindow before invoke
  • LegacyIAccessiblePattern.DoDefaultAction (not available)
  • Fallback: SendInput mouse simulation (works reliably)

Observations:

  • Invoke() succeeds without exceptions
  • Windowless control (NativeWindowHandle == 0), no SendMessage(BM_CLICK)
  • UIA exposes control, but Access provider ignores invoke on secondary monitor

Questions:

  1. Has InvokePattern.Invoke() returned successfully but failed on secondary monitors in multi-monitor UIA setups with Access VBA?
  2. Known issues with UIA/Microsoft Access providers for windowless controls or multi-monitor/per-monitor DPI?
  3. Recommended diagnostics: ETW traces, UIA Verifier, MSAA inspection, additional properties?
  4. Reliable alternatives to trigger Access VBA windowless buttons without mouse simulation (e.g., without SendInput)?

Yes — this matches a known provider/coordinate-mapping problem: with microsoft access vba windowless controls (NativeWindowHandle == 0) the Access UIA provider can accept invokepattern.invoke (no exception) but silently ignore the action when the form is on a secondary/per‑monitor‑scaled display. Make your UIA client per‑monitor DPI aware (SetProcessDpiAwareness), verify what the provider actually receives with Inspect/ETW, and — when you can — call the underlying Access routine via COM interop (Application.Run / DoCmd.RunMacro) instead of relying on UIAutomation. If you can’t change the DB/provider, SendInput (mouse simulation) is the most reliable practical fallback.


Contents


Problem summary and minimal repro

You already have a solid minimal repro: a C# UIA client calling InvokePattern.Invoke() against an Access form button whose AutomationElement.Current.NativeWindowHandle == 0, and the call returns successfully but only fires the button action when the Access window sits on the primary monitor. On a secondary (or differently scaled) monitor Invoke returns but nothing happens; however SendInput/mouse simulation does trigger the action.

That symptom is exactly what others have observed: UIAutomation shows the pattern available and clickable point present, but the Access provider fails to route the invoke to the actual control when the control is hosted windowlessly on a non‑primary monitor (likely a coordinate/DPI or provider bug). Community reproductions and diagnosis are described in the devblog and StackOverflow threads linked below, and Microsoft docs explain that windowless ActiveX providers and Invoke behavior are provider‑dependent (Microsoft windowless ActiveX guidance; InvokePattern.Invoke notes).


Why windowless controls behave differently (NativeWindowHandle == 0)

Windowless ActiveX controls don’t have their own HWND. Because of that the hosting container (Access in this case) must provide UIA information and input routing via provider interfaces such as IRawElementProviderSimple, IRawElementProviderFragment, IRawElementProviderFragmentRoot and related services. The Microsoft guidance explains those responsibilities and the special handling windowless controls require: the host must map coordinates, implement navigation, and forward input/events for the control to behave like a normal HWND control to automation clients (Make a Windowless ActiveX Control Accessible).

Two important consequences:

  • InvokePattern.Invoke is provider‑dependent. The UIA client calls into UIAutomationCore, which routes the request to the provider; whether the control actually activates is up to that provider implementation (InvokePattern.Invoke documentation).
  • If the provider uses screen coordinates or an HWND mapping that assumes the primary monitor or system DPI, the mapping can be wrong on a secondary monitor (different monitor origin or different DPI). The provider may drop or ignore the invoke because it can’t find the target input surface.

So this isn’t a bug in your Invoke call per se — it’s a mismatch between what the Access provider expects and what your client/process believe are the correct coordinates or routing mechanism.


UIAutomation, multi‑monitor and per‑monitor DPI issues

Multi‑monitor setups with per‑monitor DPI make UI automation tricky. Windows and DWM apply scaling on a per‑monitor basis, and automation clients must handle that mapping: Microsoft’s DPI guidance and the UIA/scale docs explain that coordinate spaces and DPI contexts can change while the process runs (High DPI desktop app guidance; UIA and screen scaling).

A practical Microsoft Q&A recommendation is to make the client process per‑monitor DPI aware (for example, call SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE) early) and to use physical cursor coordinates when you need to reconcile UIA results with real pixels: “make the client application dpi-aware firstly (Try SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)). And then GetPhysicalCursorPos because the UI Automation API does not use logical coordinates.” (Microsoft Q&A post).

What to try:

  • Make your UIA client process per‑monitor DPI aware at startup (before any UI is created).
  • Compare AutomationElement.TryGetClickablePoint (logical/DIP coordinates) with the monitor’s physical pixel coordinates. Convert between them using the monitor DPI (GetDpiForMonitor) or get the physical cursor coordinate (GetPhysicalCursorPos) as an authoritative check.
  • If converting makes the clickable point line up with the control visually but Invoke still does nothing, the provider likely ignores invokes routed from a different coordinate space or is simply buggy on secondary monitors.

Code hint — set per-monitor DPI awareness (call once at startup):

csharp
// Call early (program start), before creating windows
[DllImport("shcore.dll")]
private static extern int SetProcessDpiAwareness(ProcessDpiAwareness value);

private enum ProcessDpiAwareness
{
 ProcessDPIUnaware = 0,
 ProcessSystemDPIAware = 1,
 ProcessPerMonitorDPIAware = 2
}

// Usage:
SetProcessDpiAwareness(ProcessDpiAwareness.ProcessPerMonitorDPIAware);

After that, either convert Automation clickable points to physical pixels with GetDpiForMonitor or use GetPhysicalCursorPos to validate coordinate mapping (see Microsoft Q&A).


Diagnostics to run (Inspect, ETW, MSAA, events)

You want facts. Run these tests in a repeatable loop and capture results for primary vs secondary monitor runs.

  1. Inspect / Accessibility Insights
  • Use Inspect.exe or Accessibility Insights to capture: SupportedPatterns, BoundingRectangle, NativeWindowHandle, TryGetClickablePoint, control type, and event subscriptions while the Access window is on primary vs secondary monitor. Compare outputs line by line. The Microsoft windowless ActiveX doc explains what a correct provider should expose (windowless ActiveX guidance).
  1. Subscribe to Invoke events from your client
  • Add an automation event handler and log whether the provider raises the InvokedEvent when you call InvokePattern.Invoke:
csharp
AutomationEventHandler handler = (sender, e) =>
{
 Console.WriteLine("Invoke event received at " + DateTime.Now);
};

Automation.AddAutomationEventHandler(
 InvokePattern.InvokedEvent,
 buttonElement,
 TreeScope.Element,
 handler);

// Call invokePattern.Invoke(); then wait and see if handler runs
  1. ETW / provider tracing
  • Capture UIA provider ETW traces to see if the Invoke call reaches the Access provider, and whether the provider attempts to route input. If you can collect ETW traces before/after the Invoke, include them when reporting. (Community reports suggest provider dispatch is where things are dropped; ETW can show whether the provider receives the call.)
  1. Compare clickable/pixel coordinates
  • Query TryGetClickablePoint on both monitors and record the point. Then get the monitor DPI (GetDpiForMonitor) or call GetPhysicalCursorPos to compare physical pixels. If the logical clickable point maps to a different physical pixel on the secondary monitor (or off by one monitor origin), that’s a clue.
  1. Test Legacy/IAccessible
  • Try AccessibleObjectFromPoint or LegacyIAccessiblePattern.DoDefaultAction again while logging whether any legacy object exists. If legacy IAccessible is present, accDoDefaultAction can sometimes succeed where Invoke does not.
  1. Minimal repro artifacts
  • Save: (a) Inspect output for both monitors, (b) small Access DB that reproduces the problem, © the small C# client that calls Invoke, (d) ETW traces and screen DPI settings. These artifacts are what Microsoft engineering will want if you file a bug.

Community reproductions: see the dev blog diagnosis and several StackOverflow reports that match the symptom (Invoke returns; action not fired on secondary monitor) — helpful corroboration when triaging (devhut reproduction; StackOverflow example).


Alternatives and reliable workarounds (without SendInput)

You asked for alternatives to mouse simulation. Here are the practical options, ordered by reliability and invasiveness.

  1. COM automation: call the Access action directly (recommended)
  • If you can modify or rely on the Access DB, expose the action as a public sub/function or a macro and call it via Access automation from your process. This avoids UIA and window/coordinate issues entirely.

Example (C# using COM interop / GetActiveObject):

csharp
using System.Runtime.InteropServices;

dynamic accessApp = null;
try
{
 accessApp = Marshal.GetActiveObject("Access.Application"); // requires Access running in same session
}
catch (COMException)
{
 // Optionally start Access, then open DB
 // var t = Type.GetTypeFromProgID("Access.Application");
 // accessApp = Activator.CreateInstance(t);
}

if (accessApp != null)
{
 // If the button calls a macro:
 accessApp.DoCmd.RunMacro("MacroName");

 // Or call a public procedure:
 accessApp.Run("PublicProcedureName", /* args if any */);
}

Pros: deterministic, bypasses UIA and DPI problems. Cons: requires Access to expose the action as callable (or you must modify DB to add a helper procedure or macro). Also needs same user session and appropriate COM permissions.

  1. Legacy MSAA / IAccessible accDoDefaultAction
  • If the control exposes a legacy IAccessible object and accDoDefaultAction works on primary monitor, it may still work on secondary. You already tried LegacyIAccessiblePattern and found it unavailable — so this option may not be present in your case.
  1. Invoke a higher‑level function on the form (if present)
  • If the form has a public method you can call (via COM), invoke that. That is just a variation of COM automation.
  1. Provider fix or container change
  • If you own the hosting code (not Access), implement the full IRawElementProvider set and correct coordinate mapping per Microsoft docs. For Access itself you can’t change that; instead, file a bug with Microsoft.
  1. UIA parent/container invoke (rare)
  • Sometimes invoking a parent element or calling an exposed container action will route to the child. This is hit‑or‑miss and depends on the provider. Try walking up the automation tree and checking for alternative patterns.
  1. If none of the above are possible: SendInput (mouse simulation)
  • You already have this and it works reliably. It’s the practical fallback when provider or DB changes are impossible.

A short, pragmatic plan you can implement today:

  1. Make the client process per‑monitor DPI aware at startup (see code above). Test again.
  2. Run Inspect/Accessibility Insights and subscribe to Invoke events to check provider behavior.
  3. If Invoke still fails on secondary monitor, do the robust thing: call the Access action directly via COM (Application.Run or DoCmd.RunMacro). Modify the Access DB to expose a small helper method if necessary.
  4. If you cannot change the DB and the provider doesn’t expose legacy actions, use SendInput as the last resort.

Automation event subscription example (to confirm provider behavior):

csharp
AutomationEventHandler handler = (sender, e) =>
{
 Console.WriteLine("Invoked event received: " + DateTime.Now);
};

Automation.AddAutomationEventHandler(
 InvokePattern.InvokedEvent,
 buttonElement,
 TreeScope.Element,
 handler);

// Then call:
invokePattern.Invoke();
// Wait for the handler to print; if it never prints, the provider didn't raise InvokedEvent.

COM interop example was shown earlier. If you go the COM route, add error handling and ensure Access is running in the same interactive session.

If you still want to convert clickable point to physical pixels (diagnostic only), get the monitor handle for the point and call GetDpiForMonitor (Shcore) — this gives dpiX, dpiY so you can compute scaleFactor = dpiX / 96. Multiply the logical clickable point by scaleFactor to get physical pixels. Comparing that value to GetPhysicalCursorPos helps validate coordinate mapping.


Troubleshooting checklist and when to file a bug

Quick checklist to triage and gather a good bug report:

  • [ ] Reproduce consistently: run test with Access on primary monitor (works) and secondary monitor (fails).
  • [ ] Save Inspect.exe output (SupportedPatterns, BoundingRectangle, NativeWindowHandle, TryGetClickablePoint) for both runs.
  • [ ] Capture automation event logs (subscribe to InvokedEvent) showing whether provider raised events.
  • [ ] Capture ETW/UIA provider traces that cover the Invoke call.
  • [ ] Record OS version, Access version, per‑monitor DPI settings (scale % on both monitors), and a small Access DB that repros the button.
  • [ ] Try setting your client to ProcessPerMonitorDpiAware and repeat; record differences.
  • [ ] Try calling the action via COM interop (Application.Run / DoCmd.RunMacro) and note success/failure.
  • [ ] If the provider appears to ignore Invoke on secondary monitor (no event, no action), file a bug on Microsoft Q&A / Feedback Hub and attach the above artifacts. Reference the windowless ActiveX guidance and that Invoke is provider‑dependent (Microsoft docs on windowless ActiveX; InvokePattern.Invoke).

When to file: after you’ve collected Inspect output and ETW traces that show Invoke was called by the client and not acted on by the provider. The more artifacts you include, the faster engineering can triage.


Sources

  1. https://learn.microsoft.com/en-us/windows/win32/winauto/use-ui-automation-to-make-an-windowless-activex-control-accessible
  2. https://www.devhut.net/vba-ribbon-automation-via-uiautomation/
  3. https://learn.microsoft.com/en-us/dotnet/api/system.windows.automation.invokepattern.invoke?view=windowsdesktop-8.0
  4. https://learn.microsoft.com/en-us/dotnet/framework/ui-automation/ui-automation-and-screen-scaling
  5. https://learn.microsoft.com/en-us/answers/questions/642922/get-the-elementfrompoint-(iuiautomation)-from-the
  6. https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
  7. https://stackoverflow.com/questions/73828572/button-press-not-recognized-vba-ui-automation-library
  8. https://stackoverflow.com/questions/25561199/microsoft-ui-automation-invoke-pattern-exception
  9. https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClient/System/Windows/Automation/InvokePattern.cs
  10. https://github.com/TestStack/White/issues/426
  11. https://github.com/FlaUI/FlaUI/issues/306

Conclusion

The behavior you see is consistent with known UIAutomation provider limitations for microsoft access vba windowless controls and with coordinate/DPI mismatches on secondary monitors. First try making your UIA client per‑monitor DPI aware and run the Inspect/ETW diagnostics; then prefer calling the Access action directly via COM interop (Application.Run / DoCmd.RunMacro) when possible. If neither is feasible, continue using SendInput as a pragmatic fallback and file a detailed bug with Microsoft including Inspect output and ETW traces so the Access provider can be fixed.

Authors
Verified by moderation
Moderation
Microsoft Access VBA UIAutomation InvokePattern Issue