Use SetCurrentValue for all IMenuItem properties (#20682)

* Add IMenuItem binding tests

* Use SetCurrentValue for all IMenuItem properties
Fix XMLDoc comments for MenuItem being inherited from an internal type
This commit is contained in:
Tom Edwards
2026-02-13 19:28:21 +01:00
committed by GitHub
parent 1e7f817547
commit f4b5dc95b7
3 changed files with 102 additions and 36 deletions
+8 -27
View File
@@ -1,15 +1,11 @@
using Avalonia.Metadata;
namespace Avalonia.Controls
namespace Avalonia.Controls
{
/// <summary>
/// Represents a <see cref="MenuItem"/>.
/// </summary>
internal interface IMenuItem : IMenuElement
{
/// <summary>
/// Gets or sets a value that indicates whether the item has a submenu.
/// </summary>
/// <inheritdoc cref="MenuItem.HasSubMenu"/>
bool HasSubMenu { get; }
/// <summary>
@@ -17,21 +13,13 @@ namespace Avalonia.Controls
/// </summary>
bool IsPointerOverSubMenu { get; }
/// <summary>
/// Gets or sets a value that indicates whether the submenu of the <see cref="MenuItem"/> is
/// open.
/// </summary>
/// <inheritdoc cref="MenuItem.IsSubMenuOpen"/>
bool IsSubMenuOpen { get; set; }
/// <summary>
/// Gets or sets a value that indicates the submenu that this <see cref="MenuItem"/> is
/// within should not close when this item is clicked.
/// </summary>
/// <inheritdoc cref="MenuItem.StaysOpenOnClick"/>
bool StaysOpenOnClick { get; set; }
/// <summary>
/// Gets a value that indicates whether the <see cref="MenuItem"/> is a top-level main menu item.
/// </summary>
/// <inheritdoc cref="MenuItem.IsTopLevel"/>
bool IsTopLevel { get; }
/// <summary>
@@ -39,20 +27,13 @@ namespace Avalonia.Controls
/// </summary>
IMenuElement? Parent { get; }
/// <summary>
/// Gets toggle type of the menu item.
/// </summary>
/// <inheritdoc cref="MenuItem.ToggleType"/>
MenuItemToggleType ToggleType { get; }
/// <summary>
/// Gets menu item group name when <see cref="ToggleType"/> is <see cref="MenuItemToggleType.Radio"/>.
/// </summary>
/// <inheritdoc cref="MenuItem.GroupName"/>
string? GroupName { get; }
/// <summary>
/// Gets or sets if menu item is checked when <see cref="ToggleType"/> is
/// <see cref="MenuItemToggleType.CheckBox"/> or <see cref="MenuItemToggleType.Radio"/>.
/// </summary>
/// <inheritdoc cref="MenuItem.IsChecked"/>
bool IsChecked { get; set; }
/// <summary>
+28 -3
View File
@@ -288,6 +288,12 @@ namespace Avalonia.Controls
set => SetValue(IsSubMenuOpenProperty, value);
}
bool IMenuItem.IsSubMenuOpen
{
get => IsSubMenuOpen;
set => SetCurrentValue(IsSubMenuOpenProperty, value);
}
/// <summary>
/// Gets or sets a value that indicates the submenu that this <see cref="MenuItem"/> is
/// within should not close when this item is clicked.
@@ -298,27 +304,46 @@ namespace Avalonia.Controls
set => SetValue(StaysOpenOnClickProperty, value);
}
/// <inheritdoc cref="IMenuItem.ToggleType" />
bool IMenuItem.StaysOpenOnClick
{
get => StaysOpenOnClick;
set => SetCurrentValue(StaysOpenOnClickProperty, value);
}
/// <summary>
/// Gets toggle type of the menu item.
/// </summary>
public MenuItemToggleType ToggleType
{
get => GetValue(ToggleTypeProperty);
set => SetValue(ToggleTypeProperty, value);
}
/// <inheritdoc cref="IMenuItem.IsChecked"/>
/// <summary>
/// Gets or sets if menu item is checked when <see cref="ToggleType"/> is
/// <see cref="MenuItemToggleType.CheckBox"/> or <see cref="MenuItemToggleType.Radio"/>.
/// </summary>
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);
}
/// <inheritdoc cref="IMenuItem.GroupName"/>
/// <summary>
/// Gets menu item group name when <see cref="ToggleType"/> is <see cref="MenuItemToggleType.Radio"/>.
/// </summary>
public string? GroupName
{
get => GetValue(GroupNameProperty);
@@ -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;
@@ -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