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
- Inside overrides, call GetBoundingRectangleCore()/protected virtuals instead of the public GetBoundingRectangle().
- Defer work triggered by automation events to the dispatcher queue (Dispatcher.BeginInvoke) so it runs after the in-progress call completes.
- Cache the rect or compute it via UIElement APIs (TransformToVisual/PointToScreen) directly rather than re-entering the peer.
- 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
- Never call public AutomationPeer wrappers (GetBoundingRectangle, IsEnabled, etc.) from within *Core overrides — call the Core/protected methods or read owner element state directly.
- Queue automation-event handler work with Dispatcher.BeginInvoke instead of re-querying the peer synchronously.
- Cache automation property values on the owner instead of re-querying the peer.
- Test custom peers under UIInspect/acccheck to force concurrent UIA property queries and catch re-entrancy early.
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
- Microsoft.Windows.Controls.SR.UIA_OperationCannotBePerformed
- Microsoft.Windows.Controls.SR.VirtualizedElement
- SR.ReentrantVisualTreeChangeError
- VirtualizedElement
- Cannot countersign an unsigned package.
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)