diff --git a/src/Avalonia.Controls/IMenuItem.cs b/src/Avalonia.Controls/IMenuItem.cs index 49ac0bac1b..68874333ef 100644 --- a/src/Avalonia.Controls/IMenuItem.cs +++ b/src/Avalonia.Controls/IMenuItem.cs @@ -1,15 +1,11 @@ -using Avalonia.Metadata; - -namespace Avalonia.Controls +namespace Avalonia.Controls { /// /// Represents a . /// internal interface IMenuItem : IMenuElement { - /// - /// Gets or sets a value that indicates whether the item has a submenu. - /// + /// bool HasSubMenu { get; } /// @@ -17,21 +13,13 @@ namespace Avalonia.Controls /// bool IsPointerOverSubMenu { get; } - /// - /// Gets or sets a value that indicates whether the submenu of the is - /// open. - /// + /// bool IsSubMenuOpen { get; set; } - /// - /// Gets or sets a value that indicates the submenu that this is - /// within should not close when this item is clicked. - /// + /// bool StaysOpenOnClick { get; set; } - /// - /// Gets a value that indicates whether the is a top-level main menu item. - /// + /// bool IsTopLevel { get; } /// @@ -39,22 +27,15 @@ namespace Avalonia.Controls /// IMenuElement? Parent { get; } - /// - /// Gets toggle type of the menu item. - /// + /// MenuItemToggleType ToggleType { get; } - - /// - /// Gets menu item group name when is . - /// + + /// string? GroupName { get; } - - /// - /// Gets or sets if menu item is checked when is - /// or . - /// + + /// bool IsChecked { get; set; } - + /// /// Raises a click event on the menu item. /// diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index dcdcec069a..231dcc6a53 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -288,6 +288,12 @@ namespace Avalonia.Controls set => SetValue(IsSubMenuOpenProperty, value); } + bool IMenuItem.IsSubMenuOpen + { + get => IsSubMenuOpen; + set => SetCurrentValue(IsSubMenuOpenProperty, value); + } + /// /// Gets or sets a value that indicates the submenu that this is /// within should not close when this item is clicked. @@ -298,27 +304,46 @@ namespace Avalonia.Controls set => SetValue(StaysOpenOnClickProperty, value); } - /// + bool IMenuItem.StaysOpenOnClick + { + get => StaysOpenOnClick; + set => SetCurrentValue(StaysOpenOnClickProperty, value); + } + + /// + /// Gets toggle type of the menu item. + /// public MenuItemToggleType ToggleType { get => GetValue(ToggleTypeProperty); set => SetValue(ToggleTypeProperty, value); } - /// + /// + /// Gets or sets if menu item is checked when is + /// or . + /// public bool IsChecked { get => GetValue(IsCheckedProperty); set => SetValue(IsCheckedProperty, value); } + bool IMenuItem.IsChecked + { + get => IsChecked; + set => SetCurrentValue(IsCheckedProperty, value); + } + bool IRadioButton.IsChecked { get => IsChecked; set => SetCurrentValue(IsCheckedProperty, value); } - /// + /// + /// Gets menu item group name when is . + /// public string? GroupName { get => GetValue(GroupNameProperty); diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index 7c133c50a7..821d1ec599 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -1,6 +1,9 @@ using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; +using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.UnitTests; @@ -12,11 +15,11 @@ namespace Avalonia.Controls.UnitTests.Platform public class DefaultMenuInteractionHandlerTests : ScopedTestBase { static PointerPressedEventArgs CreatePressed(object source) => new PointerPressedEventArgs(source, - new FakePointer(), (Visual)source, default,0, new PointerPointProperties (RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), + new FakePointer(), (Visual)source, default, 0, new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), default); - + static PointerReleasedEventArgs CreateReleased(object source) => new PointerReleasedEventArgs(source, - new FakePointer(), (Visual)source, default,0, + new FakePointer(), (Visual)source, default, 0, new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonReleased), default, MouseButton.Left); @@ -224,6 +227,63 @@ namespace Avalonia.Controls.UnitTests.Platform target.KeyDown(menu, e); } + + private class MenuItemVM : INotifyPropertyChanged + { + public bool IsChecked { get; set { field = value; OnPropertyChanged(); } } + public bool IsSubMenuOpen { get; set { field = value; OnPropertyChanged(); } } + + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new(propertyName)); + } + + [Fact] + public void Doesnt_Replace_IsChecked_Binding() + { + var target = new DefaultMenuInteractionHandler(false); + var menu = new Menu(); + var vm = new MenuItemVM(); + + var item = new MenuItem + { + DataContext = vm, + [!MenuItem.IsCheckedProperty] = new Binding(nameof(MenuItemVM.IsChecked)) { Priority = BindingPriority.Style, Mode = BindingMode.TwoWay }, + ToggleType = MenuItemToggleType.CheckBox, + }; + menu.Items.Add(item); + + target.KeyDown(item, new KeyEventArgs { Key = Key.Enter, Source = menu }); + + Assert.True(item.IsChecked); + + vm.IsChecked = false; + + Assert.False(item.IsChecked); + } + + [Fact] + public void Doesnt_Replace_IsSubMenuOpen_Binding() + { + var target = new DefaultMenuInteractionHandler(false); + var menu = new Menu(); + var vm = new MenuItemVM(); + + var item = new MenuItem + { + DataContext = vm, + [!MenuItem.IsSubMenuOpenProperty] = new Binding(nameof(MenuItemVM.IsSubMenuOpen)) { Priority = BindingPriority.Style, Mode = BindingMode.TwoWay }, + Items = { new MenuItem() } + }; + + target.KeyDown(item, new KeyEventArgs { Key = Key.Enter, Source = menu }); + + Assert.True(item.IsSubMenuOpen); + + vm.IsSubMenuOpen = false; + + Assert.False(item.IsSubMenuOpen); + } } public class NonTopLevel : ScopedTestBase