diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index 1735599988..86499530da 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Generators;
@@ -9,18 +10,19 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
+using Avalonia.Styling;
namespace Avalonia.Controls
{
///
/// A control context menu.
///
- public class ContextMenu : MenuBase
+ public class ContextMenu : MenuBase, ISetterValue
{
private static readonly ITemplate DefaultPanel =
new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical });
private Popup _popup;
- private Control _attachedControl;
+ private List _attachedControls;
private IInputElement _previousFocus;
///
@@ -74,13 +76,14 @@ namespace Avalonia.Controls
if (e.OldValue is ContextMenu oldMenu)
{
control.PointerReleased -= ControlPointerReleased;
- oldMenu._attachedControl = null;
+ oldMenu._attachedControls?.Remove(control);
((ISetLogicalParent)oldMenu._popup)?.SetParent(null);
}
if (e.NewValue is ContextMenu newMenu)
{
- newMenu._attachedControl = control;
+ newMenu._attachedControls ??= new List();
+ newMenu._attachedControls.Add(control);
control.PointerReleased += ControlPointerReleased;
}
}
@@ -96,18 +99,22 @@ namespace Avalonia.Controls
/// The control.
public void Open(Control control)
{
- if (control is null && _attachedControl is null)
+ if (control is null && (_attachedControls is null || _attachedControls.Count == 0))
{
throw new ArgumentNullException(nameof(control));
}
- if (control is object && _attachedControl is object && control != _attachedControl)
+ if (control is object &&
+ _attachedControls is object &&
+ !_attachedControls.Contains(control))
{
throw new ArgumentException(
"Cannot show ContentMenu on a different control to the one it is attached to.",
nameof(control));
}
+ control ??= _attachedControls[0];
+
if (IsOpen)
{
return;
@@ -126,7 +133,12 @@ namespace Avalonia.Controls
_popup.Closed += PopupClosed;
}
- ((ISetLogicalParent)_popup).SetParent(control);
+ if (_popup.Parent != control)
+ {
+ ((ISetLogicalParent)_popup).SetParent(null);
+ ((ISetLogicalParent)_popup).SetParent(control);
+ }
+
_popup.Child = this;
_popup.IsOpen = true;
@@ -155,6 +167,17 @@ namespace Avalonia.Controls
}
}
+ void ISetterValue.Initialize(ISetter setter)
+ {
+ // ContextMenu can be assigned to the ContextMenu property in a setter. This overrides
+ // the behavior defined in Control which requires controls to be wrapped in a .
+ if (!(setter is Setter s && s.Property == ContextMenuProperty))
+ {
+ throw new InvalidOperationException(
+ "Cannot use a control as a Setter value. Wrap the control in a .");
+ }
+ }
+
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
@@ -179,7 +202,7 @@ namespace Avalonia.Controls
SelectedIndex = -1;
IsOpen = false;
- if (_attachedControl is null)
+ if (_attachedControls is null || _attachedControls.Count == 0)
{
((ISetLogicalParent)_popup).SetParent(null);
}
diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
index 28e87dd671..9a81d19bb9 100644
--- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
@@ -1,7 +1,10 @@
using System;
using Avalonia.Input;
+using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Platform;
using Avalonia.UnitTests;
+using Castle.DynamicProxy.Generators;
using Moq;
using Xunit;
@@ -168,6 +171,90 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void Context_Menu_In_Resources_Can_Be_Shared()
+ {
+ using (Application())
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+
+
+
+
+";
+
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var target1 = window.Find("target1");
+ var target2 = window.Find("target2");
+ var mouse = new MouseTestHelper();
+
+ Assert.NotNull(target1.ContextMenu);
+ Assert.NotNull(target2.ContextMenu);
+ Assert.Same(target1.ContextMenu, target2.ContextMenu);
+
+ window.Show();
+
+ var menu = target1.ContextMenu;
+ mouse.Click(target1, MouseButton.Right);
+ Assert.True(menu.IsOpen);
+ mouse.Click(target2, MouseButton.Right);
+ Assert.True(menu.IsOpen);
+ }
+ }
+
+ [Fact]
+ public void Context_Menu_Can_Be_Set_In_Style()
+ {
+ using (Application())
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+
+
+";
+
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var target1 = window.Find("target1");
+ var target2 = window.Find("target2");
+ var mouse = new MouseTestHelper();
+
+ Assert.NotNull(target1.ContextMenu);
+ Assert.NotNull(target2.ContextMenu);
+ Assert.Same(target1.ContextMenu, target2.ContextMenu);
+
+ window.Show();
+
+ var menu = target1.ContextMenu;
+ mouse.Click(target1, MouseButton.Right);
+ Assert.True(menu.IsOpen);
+ mouse.Click(target2, MouseButton.Right);
+ Assert.True(menu.IsOpen);
+ }
+ }
+
[Fact(Skip = "The only reason this test was 'passing' before was that the author forgot to call Window.ApplyTemplate()")]
public void Cancelling_Closing_Leaves_ContextMenuOpen()
{