From 60db99ac89038ed845c90506ac3bb875ba13c304 Mon Sep 17 00:00:00 2001 From: yoyo <143886016+zrt2399@users.noreply.github.com> Date: Thu, 14 May 2026 18:09:48 +0800 Subject: [PATCH] fix(controls): fix radio MenuItem grouping in MenuFlyout (#21356) --- .../Pages/ContextFlyoutPage.xaml | 9 ++++ .../ControlCatalog/Pages/ContextMenuPage.xaml | 9 ++++ src/Avalonia.Controls/MenuItem.cs | 16 +++++++- .../RadioButtonGroupManager.cs | 17 ++++++++ .../MenuItemTests.cs | 41 +++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml index fa52c05bfa..afb823dd84 100644 --- a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml +++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml @@ -54,6 +54,15 @@ IsHitTestVisible="False" /> + + diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml b/samples/ControlCatalog/Pages/ContextMenuPage.xaml index 4d1d02aed8..9bb988ff50 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml @@ -43,6 +43,15 @@ IsHitTestVisible="False" /> + + diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 6bd0383463..b992a2343b 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -14,6 +14,7 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Reactive; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -387,7 +388,9 @@ namespace Avalonia.Controls /// IEnumerable IMenuElement.SubItems => LogicalChildren.OfType(); - private IMenuInteractionHandler? MenuInteractionHandler => this.FindLogicalAncestorOfType()?.InteractionHandler; + private IMenuInteractionHandler? MenuInteractionHandler => + this.FindLogicalAncestorOfType()?.InteractionHandler ?? + this.FindAncestorOfType()?.InteractionHandler; /// /// Opens the submenu. @@ -470,6 +473,7 @@ namespace Avalonia.Controls base.OnAttachedToVisualTree(e); TryUpdateCanExecute(); + RegisterInMenuInteractionHandler(); } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) @@ -908,6 +912,16 @@ namespace Avalonia.Controls SelectedItem = null; } + private void RegisterInMenuInteractionHandler() + { + if (ToggleType != MenuItemToggleType.Radio || MenuInteractionHandler is not DefaultMenuInteractionHandler handler) + { + return; + } + + handler.OnGroupOrTypeChanged(this, null); + } + void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => CanExecuteChangedHandler(sender, e); void IClickableControl.RaiseClick() diff --git a/src/Avalonia.Controls/RadioButtonGroupManager.cs b/src/Avalonia.Controls/RadioButtonGroupManager.cs index c3d744c7c8..5bcea0bf70 100644 --- a/src/Avalonia.Controls/RadioButtonGroupManager.cs +++ b/src/Avalonia.Controls/RadioButtonGroupManager.cs @@ -41,6 +41,23 @@ internal class RadioButtonGroupManager _registeredGroups.Add(groupName, group); } + int i = 0; + while (i < group.Count) + { + if (!group[i].TryGetTarget(out var current)) + { + group.RemoveAt(i); + continue; + } + + if (current == radioButton) + { + return; + } + + i++; + } + group.Add(new WeakReference(radioButton)); } } diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs index c143f8db12..a159e6a9f2 100644 --- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs @@ -560,6 +560,47 @@ namespace Avalonia.Controls.UnitTests Assert.True(menuItem3.IsChecked); } + [Fact] + public void Radio_MenuItem_In_Same_Group_In_MenuFlyout_Is_Unchecked() + { + using var app = Application(); + + var menuItem1 = new MenuItem + { + GroupName = "A", + IsChecked = true, + StaysOpenOnClick = true, + ToggleType = MenuItemToggleType.Radio, + }; + var menuItem2 = new MenuItem + { + GroupName = "A", + IsChecked = false, + StaysOpenOnClick = true, + ToggleType = MenuItemToggleType.Radio, + }; + + var flyout = new MenuFlyout + { + Items = + { + menuItem1, + menuItem2, + } + }; + var button = new Button { ContextFlyout = flyout }; + var window = new Window { Content = button }; + + window.Show(); + flyout.ShowAt(button); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded, TestContext.Current.CancellationToken); + + menuItem2.IsChecked = true; + + Assert.False(menuItem1.IsChecked); + Assert.True(menuItem2.IsChecked); + } + [Fact] public void Radio_Menu_Group_Can_Be_Changed_In_Runtime() {