diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index a676892384..1e5e80d144 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -128,6 +128,7 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty TopmostProperty = AvaloniaProperty.Register(nameof(Topmost)); + private bool _isOpenRequested = false; private bool _isOpen; private bool _ignoreIsOpenChanged; private PopupOpenState? _openState; @@ -361,17 +362,19 @@ namespace Avalonia.Controls.Primitives if (placementTarget == null) { - throw new InvalidOperationException("Popup has no logical parent and PlacementTarget is null"); + _isOpenRequested = true; + return; } var topLevel = placementTarget.VisualRoot as TopLevel; if (topLevel == null) { - throw new InvalidOperationException( - "Attempted to open a popup not attached to a TopLevel"); + _isOpenRequested = true; + return; } + _isOpenRequested = false; var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver); var handlerCleanup = new CompositeDisposable(5); @@ -492,6 +495,17 @@ namespace Avalonia.Controls.Primitives return new Size(); } + + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + if (_isOpenRequested) + { + Open(); + } + } + /// protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { @@ -552,6 +566,7 @@ namespace Avalonia.Controls.Primitives private void CloseCore() { + _isOpenRequested = false; if (_openState is null) { using (BeginIgnoringIsOpen()) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 53a8db2176..f032186bcd 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -21,7 +21,50 @@ namespace Avalonia.Controls.UnitTests.Primitives public class PopupTests { protected bool UsePopupHost; - + + [Fact] + public void Popup_Open_Without_Target_Should_Attach_Itself_Later() + { + using (CreateServices()) + { + int openedEvent = 0; + var target = new Popup(); + target.Opened += (s, a) => openedEvent++; + target.IsOpen = true; + + var window = PreparedWindow(target); + window.Show(); + Assert.Equal(1, openedEvent); + } + } + + [Fact] + public void Popup_Without_TopLevel_Shouldnt_Call_Open() + { + int openedEvent = 0; + var target = new Popup(); + target.Opened += (s, a) => openedEvent++; + target.IsOpen = true; + + Assert.Equal(0, openedEvent); + } + + [Fact] + public void Opening_Popup_Shouldnt_Throw_When_Not_In_Visual_Tree() + { + var target = new Popup(); + target.IsOpen = true; + } + + [Fact] + public void Opening_Popup_Shouldnt_Throw_When_In_Tree_Without_TopLevel() + { + Control c = new Control(); + var target = new Popup(); + ((ISetLogicalParent)target).SetParent(c); + target.IsOpen = true; + } + [Fact] public void Setting_Child_Should_Set_Child_Controls_LogicalParent() {