dotnet/wpf · error · InvalidOperationException

SR.HasLogicalParent

Error message

SR.HasLogicalParent

What it means

ChangeLogicalParent (internal logical-tree plumbing used by FrameworkElement.AddLogicalChild/parent assignment) enforces that a child must be detached from its current logical parent before being attached to a new one. If _parent is non-null and differs from newParent, WPF throws InvalidOperationException (HasLogicalParent) to prevent 'child stealing' and corrupt trees.

Solutions

  1. Remove the child from its current parent first (oldParent.RemoveLogicalChild(child) or remove from the panel's Children) before adding it to the new parent.
  2. Use XamlReader/clone or construct a new element instance if the element must appear in two places.
  3. For ContentControls, reassign the old holder's Content to null before setting it on the new holder.

Example fix

// before
panelB.Children.Add(sharedButton); // InvalidOperationException: has logical parent panelA

// after
panelA.Children.Remove(sharedButton);
panelB.Children.Add(sharedButton);
Defensive patterns

Strategy: validation

Validate before calling

var logicalParent = LogicalTreeHelper.GetParent(child);
if (logicalParent != null && logicalParent != newParent)
    throw new InvalidOperationException("Child already has a logical parent; detach first.");

Type guard

bool IsOrphan(FrameworkElement child) => LogicalTreeHelper.GetParent(child) == null;

Try / catch

try
{
    newParent.AddLogicalChild(child);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("parent"))
{
    Log.Warn("Child already attached to another logical parent.", ex);
}

Prevention

When it happens

Trigger: Adding an element that already has a logical parent to a second parent: calling newParent.AddLogicalChild(child) where child.Parent (or logical parent) is another element; reparenting without removing from the old parent first; sharing one child instance between two containers.

Common situations: Reusing a single UI element instance in multiple Panels/Tabs; moving controls between containers without detaching; content assigned to two ContentControls simultaneously.

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/3947078da9ec318b. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Generated/FrameworkElement.cs:300

            ///////////////////

            //
            // -- 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.
            //
            this.VerifyAccess();
            newParent?.VerifyAccess();

            // Logical Parent must first be dropped before you are attached to a newParent
            // This mitigates illegal tree state caused by logical child stealing as illustrated in bug 970706
            if (_parent != null && newParent != null && _parent != newParent)
            {
                throw new System.InvalidOperationException(SR.HasLogicalParent);
            }

            // Trivial check to avoid loops
            if (newParent == this)
            {
                throw new System.InvalidOperationException(SR.CannotBeSelfParent);
            }

            // invalid during a VisualTreeChanged event
            VisualDiagnostics.VerifyVisualTreeChange(this);

            // Logical Parent implies no InheritanceContext
            if (newParent != null)
            {
                ClearInheritanceContext();
            }

            IsParentAnFE = newParent is FrameworkElement;

View on GitHub (pinned to 81131a70a4)