dotnet/wpf · error · InvalidOperationException
SR.IllegalTreeChangeDetectedPostAction
Error message
SR.IllegalTreeChangeDetectedPostAction
What it means
StructuralCache.DetectInvalidOperation throws InvalidOperationException when an illegal modification of the document tree was detected while the layout engine was measuring or arranging content. WPF's text layout caches structural information about the FlowDocument; mutating the tree mid-layout corrupts that cache, so the flag set by NoteIllegallTreeChangeDetected is surfaced on the next pass. This is a guard against undefined behavior, not a recoverable state.
Solutions
- Defer tree modifications with Dispatcher.BeginInvoke at a lower priority so they run after the layout pass completes
- Move content mutations out of layout-synchronous events (LayoutUpdated, SizeChanged handlers)
- Use DocumentPage/ICustomDocumentPaginator hooks only for read-only inspection; batch edits between layout passes
- Re-create the FlowDocumentPage/reader after edits instead of reusing a partially laid-out document
Example fix
// before
void OnLayoutUpdated(object s, EventArgs e) { doc.Blocks.Add(p); }
// after
void OnLayoutUpdated(object s, EventArgs e) {
Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => doc.Blocks.Add(p)));
} Defensive patterns
Strategy: validation
Validate before calling
// Guard: ensure no tree mutation is queued while layout is in progress
bool layoutInProgress = element.IsMeasureValid == false || element.IsArrangeValid == false;
if (layoutInProgress)
Dispatcher.BeginInvoke(DispatcherPriority.Background, () => doc.Blocks.Add(newBlock));
else
doc.Blocks.Add(newBlock); Try / catch
try
{
paginator.ComputePageCount();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("tree change"))
{
// re-run after layout settles
Dispatcher.BeginInvoke(DispatcherPriority.Background, () => paginator.ComputePageCount());
} Prevention
- Defer all document edits out of layout events with Dispatcher.BeginInvoke
- Do not modify FlowDocument content inside MeasureOverride/ArrangeOverride/PrintDocument
- Batch document edits between pagination passes
When it happens
Trigger: Modifying the element tree (adding/removing Blocks, Run text, TableCell content) inside a measure/arrange pass of a FlowDocument-based control, or from a handler that runs synchronously during layout (e.g. LayoutUpdated, OnMeasure override), and then calling DetectInvalidOperation (via pagination or collection operations).
Common situations: Data binding or event handlers updating document content during pagination/printing; running document edits inside MeasureOverride/ArrangeOverride; DocumentPaginator or print pipeline triggering layout while UI code mutates the FlowDocument.
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
- SR.FlowDocumentInvalidContnetChange
- SR.Format(SR.PTSError, fserr)
- 0x80040206
- ArgumentNullException: child
- SR.ArrangeReentrancyInvalid
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/37a93fbad45abc06.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/PtsHost/StructuralCache.cs:113
/// <summary>
/// Sets current page context to be used in the cache's queries
/// </summary>
/// <param name="currentPage">Document page to become current in the context</param>
/// <returns>Reference to object compatible with IDisposable to re-initialize page context</returns>
internal IDisposable SetDocumentVisualValidationContext(FlowDocumentPage currentPage)
{
return (new DocumentVisualValidationContext(this, currentPage) as IDisposable);
}
/// <summary>
/// Detects if illegal tree change operation has been performed, but hidden by external
/// code through try-catch statement.
/// </summary>
internal void DetectInvalidOperation()
{
if (_illegalTreeChangeDetected)
{
throw new InvalidOperationException(SR.IllegalTreeChangeDetectedPostAction);
}
}
/// <summary>
/// Notes the fact that world has changed while in measure / arrange.
/// </summary>
internal void OnInvalidOperationDetected()
{
if (_currentPage != null)
{
_illegalTreeChangeDetected = true;
}
}
/// <summary>
/// Invalidate format caches accumulated in the NameTable.
/// </summary>
internal void InvalidateFormatCache(bool destroyStructure)View on GitHub (pinned to 81131a70a4)