{"record":{"id":"37a93fbad45abc06","repo":"dotnet/wpf","slug":"sr-illegaltreechangedetectedpostaction","errorCode":null,"errorMessage":"SR.IllegalTreeChangeDetectedPostAction","messagePattern":"SR\\.IllegalTreeChangeDetectedPostAction","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/PtsHost/StructuralCache.cs","lineNumber":113,"sourceCode":"        /// <summary>\n        /// Sets current page context to be used in the cache's queries\n        /// </summary>\n        /// <param name=\"currentPage\">Document page to become current in the context</param>\n        /// <returns>Reference to object compatible with IDisposable to re-initialize page context</returns>\n        internal IDisposable SetDocumentVisualValidationContext(FlowDocumentPage currentPage)\n        {\n            return (new DocumentVisualValidationContext(this, currentPage) as IDisposable);\n        }\n\n        /// <summary>\n        /// Detects if illegal tree change operation has been performed, but hidden by external\n        /// code through try-catch statement.\n        /// </summary>\n        internal void DetectInvalidOperation()\n        {\n            if (_illegalTreeChangeDetected)\n            {\n                throw new InvalidOperationException(SR.IllegalTreeChangeDetectedPostAction);\n            }\n        }\n\n        /// <summary>\n        /// Notes the fact that world has changed while in measure / arrange.\n        /// </summary>\n        internal void OnInvalidOperationDetected()\n        {\n            if (_currentPage != null)\n            {\n                _illegalTreeChangeDetected = true;\n            }\n        }\n\n        /// <summary>\n        /// Invalidate format caches accumulated in the NameTable.\n        /// </summary>\n        internal void InvalidateFormatCache(bool destroyStructure)","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/PtsHost/StructuralCache.cs#L95-L131","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nvoid OnLayoutUpdated(object s, EventArgs e) { doc.Blocks.Add(p); }\n// after\nvoid OnLayoutUpdated(object s, EventArgs e) {\n    Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => doc.Blocks.Add(p)));\n}","handlingStrategy":"validation","validationCode":"// Guard: ensure no tree mutation is queued while layout is in progress\nbool layoutInProgress = element.IsMeasureValid == false || element.IsArrangeValid == false;\nif (layoutInProgress)\n    Dispatcher.BeginInvoke(DispatcherPriority.Background, () => doc.Blocks.Add(newBlock));\nelse\n    doc.Blocks.Add(newBlock);","typeGuard":null,"tryCatchPattern":"try\n{\n    paginator.ComputePageCount();\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"tree change\"))\n{\n    // re-run after layout settles\n    Dispatcher.BeginInvoke(DispatcherPriority.Background, () => paginator.ComputePageCount());\n}","preventionTips":["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"],"tags":["wpf","flowdocument","illegal-tree-change","layout"],"backgroundTag":"invalid-state-transition","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}