Browse Source

Fix `MenuItem` enabled state in the presence of sub-items. (#18679)

* Add unit test for sub menu item testing

* Parent menu items should not be disabled on command binding failure.

If a menu has a failed `Command` binding, but also has sub-menu items, then it should not be disabled.

---------

Co-authored-by: Anastassia Pellja <[email protected]>
Steven Kirk 6 tháng trước cách đây
mục cha
commit
b16975cb04

+ 11 - 1
src/Avalonia.Controls/MenuItem.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.Linq;
 using System.Windows.Input;
 using Avalonia.Automation;
@@ -341,7 +342,7 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         IMenuElement? IMenuItem.Parent => Parent as IMenuElement;
 
-        protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
+        protected override bool IsEnabledCore => base.IsEnabled && (HasSubMenu || _commandCanExecute);
 
         /// <inheritdoc/>
         bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
@@ -710,6 +711,15 @@ namespace Avalonia.Controls
             {
                 GroupNameChanged(change);
             }
+            else if (change.Property == ItemCountProperty)
+            {
+                // A menu item with no sub-menu is effectively disabled if its command binding
+                // failed: this means that the effectively enabled state depends on whether the
+                // number of items in the menu is 0 or not.
+                var (o, n) = change.GetOldAndNewValue<int>();
+                if (o == 0 || n == 0)
+                    UpdateIsEffectivelyEnabled();
+            }
         }
         /// <summary>
         /// Called when the <see cref="GroupName"/> property changes.

+ 39 - 0
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@@ -62,6 +62,45 @@ namespace Avalonia.Controls.UnitTests
             Assert.False(target.IsEffectivelyEnabled);
         }
 
+        [Fact]
+        public void MenuItem_With_Styled_Command_Binding_Should_Be_Enabled_With_Child_Missing_Command()
+        {
+            using var app = Application();
+
+            var viewModel = new MenuViewModel("Parent")
+            {
+                Children = [new MenuViewModel("Child")]
+            };
+
+            var contextMenu = new ContextMenu
+            {
+                ItemsSource = new[] { viewModel },
+                Styles =
+                {
+                    new Style(x => x.OfType<MenuItem>())
+                    {
+                        Setters =
+                        {
+                            new Setter(MenuItem.HeaderProperty, new Binding("Header")),
+                            new Setter(MenuItem.ItemsSourceProperty, new Binding("Children")),
+                            new Setter(MenuItem.CommandProperty, new Binding("Command"))
+                        }
+                    }
+                }
+            };
+
+            var window = new Window { ContextMenu = contextMenu };
+            window.Show();
+            contextMenu.Open();
+
+            var parentMenuItem = Assert.IsType<MenuItem>(contextMenu.ContainerFromIndex(0));
+
+            Assert.Same(parentMenuItem.DataContext, viewModel);
+            Assert.Same(parentMenuItem.ItemsSource, viewModel.Children);
+            Assert.True(parentMenuItem.IsEnabled);
+            Assert.True(parentMenuItem.IsEffectivelyEnabled);
+        }
+
         [Fact]
         public void MenuItem_Is_Disabled_When_Bound_Command_Is_Removed()
         {