dotnet/wpf · error · InvalidOperationException

SR.Automation_RecursivePublicCall

Error message

SR.Automation_RecursivePublicCall

What it means

AutomationPeer.GetBoundingRectangle() is a public UIA entry point that must never re-enter while another public AutomationPeer call is in progress on the same peer. The peer sets _publicCallInProgress for the duration of the Core override; if GetBoundingRectangle is called again (directly or via an override calling back into public APIs), the guard throws InvalidOperationException with SR.Automation_RecursivePublicCall.

Solutions

  1. Inside overrides, call GetBoundingRectangleCore()/protected virtuals instead of the public GetBoundingRectangle().
  2. Defer work triggered by automation events to the dispatcher queue (Dispatcher.BeginInvoke) so it runs after the in-progress call completes.
  3. Cache the rect or compute it via UIElement APIs (TransformToVisual/PointToScreen) directly rather than re-entering the peer.
  4. Guard custom code with a re-entrancy flag or check _publicCallInProgress before calling public APIs.

Example fix

// before
protected override Rect GetBoundingRectangleCore()
{
    Rect r = GetBoundingRectangle(); // re-enters public API -> throw
    return r;
}
// after
protected override Rect GetBoundingRectangleCore()
{
    return PointToScreen(new Point(0, 0)) != default ? ComputeRectFromOwner() : new Rect();
}
Defensive patterns

Strategy: try-catch

Validate before calling

// C# — check for re-entrancy before calling
bool isPeerBusy(AutomationPeer peer) =>
    (bool?)typeof(AutomationPeer)
        .GetField("_publicCallInProgress", BindingFlags.NonPublic | BindingFlags.Instance)
        ?.GetValue(peer) == true;
if (isPeerBusy(peer)) { /* defer call via Dispatcher.BeginInvoke */ }

Type guard

static bool CanQueryPeerSafely(AutomationPeer peer) =>
    peer != null && !(bool?)typeof(AutomationPeer)
        .GetField("_publicCallInProgress", BindingFlags.NonPublic | BindingFlags.Instance)
        ?.GetValue(peer) == true;

Try / catch

try
{
    var rect = peer.GetBoundingRectangle();
}
catch (InvalidOperationException) when (ex.Message.Contains("recursive") || ex.InnerException == null)
{
    // deferred re-query: run again after the current call completes
    Dispatcher.CurrentDispatcher.BeginInvoke(() => QueryBoundingRectangleLater(peer));
}

Prevention

When it happens

Trigger: Calling GetBoundingRectangle() from inside an override of GetBoundingRectangleCore() (or another Core override like IsOffscreenCore/AncestorsInvalid work) on the same peer, e.g. via UIA client callbacks re-entering during the call.

Common situations: Custom peer overrides that call public peer APIs instead of the Core methods; automation properties (FocusChanged/LayoutUpdated handlers) re-querying the same element synchronously during a UIA property fetch; re-entrancy from AncestorsInvalid invalidation callbacks.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/fee52fb750d45826. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Automation/Peers/AutomationPeer.cs:742

        protected virtual AutomationHeadingLevel GetHeadingLevelCore()
        {
            return AutomationHeadingLevel.None;
        }

        //
        // INTERNAL STUFF - NOT OVERRIDABLE
        //
        internal virtual Rect GetVisibleBoundingRectCore()
        {
            // Too late to add abstract methods, since this class has already shipped(using default definition)!
            return GetBoundingRectangle();
        }

        ///
        public Rect GetBoundingRectangle()
        {
            if (_publicCallInProgress)
                throw new InvalidOperationException(SR.Automation_RecursivePublicCall);

            try
            {
                _publicCallInProgress = true;
                _boundingRectangle = GetBoundingRectangleCore();
            }
            finally
            {
                _publicCallInProgress = false;
            }
            return _boundingRectangle;
        }

        ///
        public bool IsOffscreen()
        {
            if (_publicCallInProgress)
                throw new InvalidOperationException(SR.Automation_RecursivePublicCall);

View on GitHub (pinned to 81131a70a4)