dotnet/wpf · error · InvalidOperationException

SR.ArrangeReentrancyInvalid

Error message

SR.ArrangeReentrancyInvalid

What it means

TextBlock throws this InvalidOperationException when a public or text-view method is re-entered while the Arrange (layout) pass is in progress. Arrangement positions the measured lines; reentrant calls could observe or mutate half-arranged state, so VerifyReentrancy() rejects them.

Solutions

  1. Defer the offending call with Dispatcher.BeginInvoke(DispatcherPriority.Background) so it runs after arrange completes.
  2. In SizeChanged/LayoutUpdated handlers, do not write Text/Inlines directly; guard with a re-entrancy flag of your own and schedule the update.
  3. For derived controls, keep ArrangeOverride read-only on the TextBlock's content and perform mutations from user-input or data-change handlers instead.
  4. If the query is from automation, return cached metrics and refresh on a later dispatcher priority rather than re-entering immediately.

Example fix

// before: rewriting text on SizeChanged
void OnTextBlockSizeChanged(object s, SizeChangedEventArgs e)
{
    textBlock.Text = TrimToFit(textBlock.Text); // throws ArrangeReentrancyInvalid during arrange
}

// after: defer until layout is idle
void OnTextBlockSizeChanged(object s, SizeChangedEventArgs e)
{
    Dispatcher.BeginInvoke(DispatcherPriority.Background,
        new Action(() => textBlock.Text = TrimToFit(textBlock.Text)));
}
Defensive patterns

Strategy: try-catch

Validate before calling

bool insideArrange = !textBlock.IsArrangeValid; // if false, an arrange pass is likely running
if (insideArrange) Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(ApplyChange));

Type guard

static bool ArrangeSafe(TextBlock tb) => tb.IsArrangeValid;

Try / catch

try
{
    UpdateTextBlock();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("arrange"))
{
    Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(UpdateTextBlock));
}

Prevention

When it happens

Trigger: Calling public TextBlock members (text mutation via Text/Inlines, TextRange operations, textview queries like GetLineText) from ArrangeOverride, from a Arrange-time callback, or from code triggered synchronously during the arrange pass (e.g. SizeChanged/Render handlers that feed back into the TextBlock).

Common situations: SizeChanged or LayoutUpdated handlers that change Text or Inlines, causing another layout; custom panels arranging a TextBlock and also mutating its content; UIA clients resolving range/line APIs during arrange.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/TextBlock.cs:3564

        // ------------------------------------------------------------------
        private bool CheckFlags(Flags flags)
        {
            return ((_flags & flags) == flags);
        }

        // ------------------------------------------------------------------
        // Ensures none of our public (or textview) methods can be called during measure/arrange/content change.
        // ------------------------------------------------------------------
        private void VerifyReentrancy()
        {
            if(CheckFlags(Flags.MeasureInProgress))
            {
                throw new InvalidOperationException(SR.MeasureReentrancyInvalid);
            }

            if(CheckFlags(Flags.ArrangeInProgress))
            {
                throw new InvalidOperationException(SR.ArrangeReentrancyInvalid);
            }

            if(CheckFlags(Flags.ContentChangeInProgress))
            {
                throw new InvalidOperationException(SR.TextContainerChangingReentrancyInvalid);
            }
        }

        /// <summary>
        /// Returns index of the line that starts at the given dcp. Returns -1 if
        /// no line or the line metrics collection starts at the given dcp
        /// </summary>
        /// <param name="dcpLine">
        /// Start dcp of required line
        /// </param>
        private int GetLineIndexFromDcp(int dcpLine)
        {
            Invariant.Assert(dcpLine >= 0);

View on GitHub (pinned to 81131a70a4)