diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 5e1bcde2f67..9e61871f793 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -704,6 +704,12 @@ private static void ValidateLogicalChild(ILogical c) private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) { + if (this.GetLogicalParent() == null && !(this is IStyleRoot)) + { + throw new InvalidOperationException( + $"AttachedToLogicalTreeCore called for '{GetType().Name}' but control has no logical parent."); + } + // This method can be called when a control is already attached to the logical tree // in the following scenario: // - ListBox gets assigned Items containing ListBoxItem diff --git a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs index c40e6c735e0..8e08c5ce360 100644 --- a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs +++ b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs @@ -76,6 +76,29 @@ public void InheritanceParent_Should_Be_Cleared_When_Removed_From_Parent_When_Ha Assert.Null(target.InheritanceParent); } + [Fact] + public void Adding_Element_With_Null_Parent_To_Logical_Tree_Should_Throw() + { + var target = new Border(); + var visualParent = new Panel(); + var logicalParent = new Panel(); + var root = new TestRoot(); + + // Set the logical parent... + ((ISetLogicalParent)target).SetParent(logicalParent); + + // ...so that when it's added to `visualParent`, the parent won't be set again. + visualParent.Children.Add(target); + + // Clear the logical parent. It's now a logical child of `visualParent` but doesn't have + // a logical parent itself. + ((ISetLogicalParent)target).SetParent(null); + + // In this case, attaching the control to a logical tree should throw. + logicalParent.Children.Add(visualParent); + Assert.Throws(() => root.Child = logicalParent); + } + [Fact] public void AttachedToLogicalParent_Should_Be_Called_When_Added_To_Tree() {