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()
{