diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index 78dc994df7..13f00bdc87 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -7,11 +7,20 @@ namespace Avalonia.Controls
using System;
using System.Reactive.Linq;
using System.Linq;
+ using System.ComponentModel;
+
public class ContextMenu : SelectingItemsControl
{
private bool _isOpen;
private Popup _popup;
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty IsOpenProperty =
+ AvaloniaProperty.RegisterDirect(nameof(IsOpen), o => o.IsOpen);
+
+
///
/// Initializes static members of the class.
///
@@ -22,6 +31,26 @@ namespace Avalonia.Controls
MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true);
}
+ ///
+ /// Gets a value indicating whether the popup is open
+ ///
+ public bool IsOpen => _isOpen;
+
+ ///
+ /// Occurs when the value of the
+ ///
+ /// property is changing from false to true.
+ ///
+ public event CancelEventHandler ContextMenuOpening;
+
+ ///
+ /// Occurs when the value of the
+ ///
+ /// property is changing from true to false.
+ ///
+ public event CancelEventHandler ContextMenuClosing;
+
+
///
/// Called when the property changes on a control.
///
@@ -59,12 +88,12 @@ namespace Avalonia.Controls
{
if (_popup != null && _popup.IsVisible)
{
- _popup.Close();
+ _popup.IsOpen = false;
}
SelectedIndex = -1;
- _isOpen = false;
+ SetAndRaise(IsOpenProperty, ref _isOpen, false);
}
///
@@ -89,11 +118,11 @@ namespace Avalonia.Controls
}
((ISetLogicalParent)_popup).SetParent(control);
- _popup.Child = control.ContextMenu;
+ _popup.Child = this;
- _popup.Open();
+ _popup.IsOpen = true;
- control.ContextMenu._isOpen = true;
+ SetAndRaise(IsOpenProperty, ref _isOpen, true);
}
}
@@ -118,21 +147,37 @@ namespace Avalonia.Controls
var control = (Control)sender;
var contextMenu = control.ContextMenu;
- if (e.MouseButton == MouseButton.Right)
+ if (control.ContextMenu._isOpen)
{
- if (control.ContextMenu._isOpen)
- {
- control.ContextMenu.Hide();
- }
+ if (contextMenu.CancelClosing())
+ return;
- contextMenu.Show(control);
+ control.ContextMenu.Hide();
e.Handled = true;
}
- else if (contextMenu._isOpen)
+
+ if (e.MouseButton == MouseButton.Right)
{
- control.ContextMenu.Hide();
+ if (contextMenu.CancelOpening())
+ return;
+
+ contextMenu.Show(control);
e.Handled = true;
}
}
+
+ private bool CancelClosing()
+ {
+ var eventArgs = new CancelEventArgs();
+ ContextMenuClosing?.Invoke(this, eventArgs);
+ return eventArgs.Cancel;
+ }
+
+ private bool CancelOpening()
+ {
+ var eventArgs = new CancelEventArgs();
+ ContextMenuOpening?.Invoke(this, eventArgs);
+ return eventArgs.Cancel;
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
new file mode 100644
index 0000000000..94d5caa720
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Windows.Input;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Markup.Data;
+using Avalonia.Platform;
+using Avalonia.UnitTests;
+using Moq;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests
+{
+ public class ContextMenuTests
+ {
+ private Mock popupImpl;
+
+ [Fact]
+ public void Clicking_On_Control_Toggles_ContextMenu()
+ {
+ using (Application())
+ {
+ popupImpl.Setup(x => x.Show()).Verifiable();
+ popupImpl.Setup(x => x.Hide()).Verifiable();
+
+ var sut = new ContextMenu();
+ var target = new Panel
+ {
+ ContextMenu = sut
+ };
+
+ new Window { Content = target };
+
+ target.RaiseEvent(new PointerReleasedEventArgs
+ {
+ RoutedEvent = InputElement.PointerReleasedEvent,
+ MouseButton = MouseButton.Right
+ });
+
+ Assert.True(sut.IsOpen);
+
+ target.RaiseEvent(new PointerReleasedEventArgs
+ {
+ RoutedEvent = InputElement.PointerReleasedEvent,
+ MouseButton = MouseButton.None
+ });
+
+ Assert.False(sut.IsOpen);
+ popupImpl.Verify(x => x.Show(), Times.Once);
+ popupImpl.Verify(x => x.Hide(), Times.Once);
+ }
+ }
+
+ [Fact]
+ public void Right_Clicking_On_Control_Twice_Re_Opens_ContextMenu()
+ {
+ using (Application())
+ {
+ popupImpl.Setup(x => x.Show()).Verifiable();
+ popupImpl.Setup(x => x.Hide()).Verifiable();
+
+ var sut = new ContextMenu();
+ var target = new Panel
+ {
+ ContextMenu = sut
+ };
+ new Window { Content = target };
+
+ target.RaiseEvent(new PointerReleasedEventArgs
+ {
+ RoutedEvent = InputElement.PointerReleasedEvent,
+ MouseButton = MouseButton.Right
+ });
+
+ Assert.True(sut.IsOpen);
+
+ target.RaiseEvent(new PointerReleasedEventArgs
+ {
+ RoutedEvent = InputElement.PointerReleasedEvent,
+ MouseButton = MouseButton.Right
+ });
+
+ Assert.True(sut.IsOpen);
+ popupImpl.Verify(x => x.Hide(), Times.Once);
+ popupImpl.Verify(x => x.Show(), Times.Exactly(2));
+ }
+ }
+
+ [Fact]
+ public void Cancelling_Opening_Does_Not_Show_ContextMenu()
+ {
+ using (Application())
+ {
+ popupImpl.Setup(x => x.Show()).Verifiable();
+
+ bool eventCalled = false;
+ var sut = new ContextMenu();
+ var target = new Panel
+ {
+ ContextMenu = sut
+ };
+ new Window { Content = target };
+
+ sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; };
+
+ target.RaiseEvent(new PointerReleasedEventArgs
+ {
+ RoutedEvent = InputElement.PointerReleasedEvent,
+ MouseButton = MouseButton.Right
+ });
+
+ Assert.True(eventCalled);
+ Assert.False(sut.IsOpen);
+ popupImpl.Verify(x => x.Show(), Times.Never);
+ }
+ }
+
+ [Fact]
+ public void Cancelling_Closing_Leaves_ContextMenuOpen()
+ {
+ using (Application())
+ {
+ popupImpl.Setup(x => x.Show()).Verifiable();
+ popupImpl.Setup(x => x.Hide()).Verifiable();
+
+ bool eventCalled = false;
+ var sut = new ContextMenu();
+ var target = new Panel
+ {
+ ContextMenu = sut
+ };
+ new Window { Content = target };
+
+ sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; };
+
+ target.RaiseEvent(new PointerReleasedEventArgs
+ {
+ RoutedEvent = InputElement.PointerReleasedEvent,
+ MouseButton = MouseButton.Right
+ });
+
+ Assert.True(sut.IsOpen);
+
+ target.RaiseEvent(new PointerReleasedEventArgs
+ {
+ RoutedEvent = InputElement.PointerReleasedEvent,
+ MouseButton = MouseButton.None
+ });
+
+ Assert.True(eventCalled);
+ Assert.True(sut.IsOpen);
+
+ popupImpl.Verify(x => x.Show(), Times.Once());
+ popupImpl.Verify(x => x.Hide(), Times.Never);
+ }
+ }
+
+ private IDisposable Application()
+ {
+ var screen = new Rect(new Point(), new Size(100, 100));
+ var screenImpl = new Mock();
+ screenImpl.Setup(x => x.ScreenCount).Returns(1);
+ screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(screen, screen, true) });
+
+ var windowImpl = new Mock();
+ windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object);
+
+ popupImpl = new Mock();
+ popupImpl.SetupGet(x => x.Scaling).Returns(1);
+
+ var services = TestServices.StyledWindow.With(
+ inputManager: new InputManager(),
+ windowImpl: windowImpl.Object,
+ windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object, () => popupImpl.Object));
+
+ return UnitTestApplication.Start(services);
+ }
+ }
+}