diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 242df8e357..c9ffa4fcc1 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -224,6 +224,7 @@ namespace Avalonia.Controls if (e.OldValue is ContextMenu oldMenu) { control.ContextRequested -= ControlContextRequested; + control.DetachedFromVisualTree -= ControlDetachedFromVisualTree; oldMenu._attachedControls?.Remove(control); ((ISetLogicalParent?)oldMenu._popup)?.SetParent(null); } @@ -233,6 +234,7 @@ namespace Avalonia.Controls newMenu._attachedControls ??= new List(); newMenu._attachedControls.Add(control); control.ContextRequested += ControlContextRequested; + control.DetachedFromVisualTree += ControlDetachedFromVisualTree; } } @@ -410,9 +412,9 @@ namespace Avalonia.Controls private static void ControlContextRequested(object sender, ContextRequestedEventArgs e) { - var control = (Control)sender; - if (!e.Handled + if (sender is Control control && control.ContextMenu is ContextMenu contextMenu + && !e.Handled && !contextMenu.CancelOpening()) { contextMenu.Open(control, e.Source as Control ?? control); @@ -420,6 +422,15 @@ namespace Avalonia.Controls } } + private static void ControlDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) + { + if (sender is Control control + && control.ContextMenu is ContextMenu contextMenu) + { + contextMenu.Close(); + } + } + private bool CancelClosing() { var eventArgs = new CancelEventArgs(); diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 9490f27f47..9b43bddbb2 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -6,6 +6,8 @@ using Avalonia.Markup.Xaml; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.UnitTests; +using Avalonia.VisualTree; + using Moq; using Xunit; @@ -128,12 +130,10 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - var renderer = new Mock(); - var platform = AvaloniaLocator.Current.GetService(); - var windowImpl = Mock.Get(platform.CreateWindow()); - windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); + popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Hide()).Verifiable(); - var window = new Window(windowImpl.Object); + var window = PreparedWindow(); window.Width = 100; window.Height = 100; @@ -160,12 +160,15 @@ namespace Avalonia.Controls.UnitTests button.ContextMenu = c; c.Open(button); - var e = CreatePointerPressedEventArgs(window, new Point(90, 90)); var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window); - overlay.RaiseEvent(e); + _mouse.Down(overlay, MouseButton.Left, new Point(90, 90)); + _mouse.Up(button, MouseButton.Left, new Point(90, 90)); Assert.Equal(1, tracker); Assert.True(c.IsOpen); + + popupImpl.Verify(x => x.Hide(), Times.Never); + popupImpl.Verify(x => x.Show(true), Times.Exactly(1)); } } @@ -174,12 +177,10 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - var renderer = new Mock(); - var platform = AvaloniaLocator.Current.GetService(); - var windowImpl = Mock.Get(platform.CreateWindow()); - windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); + popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Hide()).Verifiable(); - var window = new Window(windowImpl.Object); + var window = PreparedWindow(); window.Width = 100; window.Height = 100; @@ -199,11 +200,13 @@ namespace Avalonia.Controls.UnitTests c.PlacementMode = PlacementMode.Bottom; c.Open(button); - var e = CreatePointerPressedEventArgs(window, new Point(90, 90)); var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window); - overlay.RaiseEvent(e); + _mouse.Down(overlay, MouseButton.Left, new Point(90, 90)); + _mouse.Up(button, MouseButton.Left, new Point(90, 90)); Assert.False(c.IsOpen); + popupImpl.Verify(x => x.Hide(), Times.Exactly(1)); + popupImpl.Verify(x => x.Show(true), Times.Exactly(1)); } } @@ -221,15 +224,17 @@ namespace Avalonia.Controls.UnitTests ContextMenu = sut }; - var window = new Window {Content = target}; + var window = PreparedWindow(target); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); + var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window); _mouse.Click(target, MouseButton.Right); Assert.True(sut.IsOpen); - _mouse.Click(target); + _mouse.Down(overlay); + _mouse.Up(target); Assert.False(sut.IsOpen); popupImpl.Verify(x => x.Show(true), Times.Once); @@ -251,15 +256,16 @@ namespace Avalonia.Controls.UnitTests ContextMenu = sut }; - var window = new Window {Content = target}; + var window = PreparedWindow(target); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); + var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window); _mouse.Click(target, MouseButton.Right); - Assert.True(sut.IsOpen); - _mouse.Click(target, MouseButton.Right); + _mouse.Down(overlay, MouseButton.Right); + _mouse.Up(target, MouseButton.Right); Assert.True(sut.IsOpen); popupImpl.Verify(x => x.Hide(), Times.Once); @@ -293,12 +299,10 @@ namespace Avalonia.Controls.UnitTests Assert.True(sut.IsOpen); - _mouse.Click(target2, MouseButton.Left); - - Assert.False(sut.IsOpen); - sp.Children.Remove(target1); - + + Assert.False(sut.IsOpen); + _mouse.Click(target2, MouseButton.Right); Assert.True(sut.IsOpen); @@ -439,17 +443,20 @@ namespace Avalonia.Controls.UnitTests { ContextMenu = sut }; - - var window = new Window {Content = target}; - window.ApplyTemplate(); + + var window = PreparedWindow(target); + var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window); sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; }; + window.Show(); + _mouse.Click(target, MouseButton.Right); Assert.True(sut.IsOpen); - _mouse.Click(target, MouseButton.Right); + _mouse.Down(overlay, MouseButton.Right); + _mouse.Up(target, MouseButton.Right); Assert.True(eventCalled); Assert.True(sut.IsOpen); @@ -459,6 +466,18 @@ namespace Avalonia.Controls.UnitTests } } + private Window PreparedWindow(object content = null) + { + var renderer = new Mock(); + var platform = AvaloniaLocator.Current.GetService(); + var windowImpl = Mock.Get(platform.CreateWindow()); + windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); + + var w = new Window(windowImpl.Object) { Content = content }; + w.ApplyTemplate(); + return w; + } + private IDisposable Application() { var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100)); @@ -480,18 +499,5 @@ namespace Avalonia.Controls.UnitTests return UnitTestApplication.Start(services); } - - private PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p) - { - var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); - return new PointerPressedEventArgs( - source, - pointer, - source, - p, - 0, - new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), - KeyModifiers.None); - } } }