dotnet/wpf · error · InvalidOperationException
SR.CannotModifyVisualChildrenDuringTreeWalk
Error message
SR.CannotModifyVisualChildrenDuringTreeWalk
What it means
Thrown by VisualCollection.ConnectChild when children are added while the owner visual is iterating its children collection during a property-invalidation tree walk (IsVisualChildrenIterationInProgress). Mutating the collection during that walk would corrupt the in-progress iteration, so WPF fails fast with InvalidOperationException.
Solutions
- Defer the mutation: Dispatcher.BeginInvoke(DispatcherPriority.Background, () => collection.Add(child)) so it runs after the walk completes.
- Buffer pending additions in a list during the callback and apply them after (e.g. at Loaded or next layout pass).
- Restructure so children are added before triggering the property change that starts the walk.
Example fix
// before
protected override void OnRenderSizeChanged(SizeChangedInfo info)
{
Children.Add(BuildChild()); // may throw during a tree walk
}
// after
Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => Children.Add(BuildChild()))); Defensive patterns
Strategy: try-catch
Validate before calling
if (((Visual)VisualTreeHelper.GetParent(child) ?? owner).CheckAccess()
&& owner.IsVisualChildrenIterationInProgress /* requires internal access */) { /* defer */ } Try / catch
try { collection.Insert(0, child); }
catch (InvalidOperationException) {
Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => collection.Insert(0, child)));
} Prevention
- Never mutate visual children from property-changed or invalidation callbacks.
- Use Dispatcher.BeginInvoke to defer Add/Insert past the tree walk.
- Buffer pending additions and apply them after the current layout pass.
- Keep child-list changes on the UI thread at stable points (Loaded/Unloaded).
When it happens
Trigger: Calling Add or Insert (which call ConnectChild) from within a callback that runs during visual-tree property invalidation — e.g. inside OnVisualChildrenChanged during an active walk, or property-changed handlers that synchronously add children while WPF is enumerating the same collection.
Common situations: Custom panels adding children in response to property invalidations; code hooks (e.g. AutomationPeer, layout notifications) that mutate children re-entrantly during tree walks.
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.Automation_RecursivePublicCall
- SR.CannotModifyLogicalChildrenDuringTreeWalk
- SR.CannotModifyLogicalChildrenDuringTreeWalk
- SR.DrawingGroup_AlreadyOpen
- SR.TextContainerChangingReentrancyInvalid
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/096bbda23c481ea6.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/VisualCollection.cs:375
private void ConnectChild(int index, Visual value)
{
//
// -- Approved By The Core Team --
//
// Do not allow foreign threads to change the tree.
// (This is a noop if this object is not assigned to a Dispatcher.)
//
// We also need to ensure that the tree is homogenous with respect
// to the dispatchers that the elements belong to.
//
_owner.VerifyAccess();
value.VerifyAccess();
// It is invalid to modify the children collection that we
// might be iterating during a property invalidation tree walk.
if (_owner.IsVisualChildrenIterationInProgress)
{
throw new InvalidOperationException(SR.CannotModifyVisualChildrenDuringTreeWalk);
}
Debug.Assert(value != null);
Debug.Assert(_items[index] == null);
Debug.Assert(value._parent == null);
Debug.Assert(!value.IsRootElement);
value._parentIndex = index;
_items[index] = value;
IncrementVersion();
// Notify the Visual tree about the children changes.
_owner.InternalAddVisualChild(value);
}
/// <summary>
/// Disconnects a child.
/// </summary>View on GitHub (pinned to 81131a70a4)