Browse Source

fix(controls): fix radio MenuItem grouping in MenuFlyout (#21356)

pull/21371/head
yoyo 5 months ago
committed by GitHub
parent
commit
60db99ac89
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
  2. 9
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  3. 16
      src/Avalonia.Controls/MenuItem.cs
  4. 17
      src/Avalonia.Controls/RadioButtonGroupManager.cs
  5. 41
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

9
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml

@ -54,6 +54,15 @@
IsHitTestVisible="False" /> IsHitTestVisible="False" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem GroupName="Radio-ableMenuItem"
Header="Menu Item with _RadioButton1"
IsChecked="True"
StaysOpenOnClick="True"
ToggleType="Radio" />
<MenuItem GroupName="Radio-ableMenuItem"
Header="Menu Item with _RadioButton2"
StaysOpenOnClick="True"
ToggleType="Radio" />
</MenuFlyout> </MenuFlyout>
</Border.ContextFlyout> </Border.ContextFlyout>
<TextBlock Text="Defined in XAML" /> <TextBlock Text="Defined in XAML" />

9
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@ -43,6 +43,15 @@
IsHitTestVisible="False" /> IsHitTestVisible="False" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem GroupName="Radio-ableMenuItem"
Header="Menu Item with _RadioButton1"
IsChecked="True"
StaysOpenOnClick="True"
ToggleType="Radio" />
<MenuItem GroupName="Radio-ableMenuItem"
Header="Menu Item with _RadioButton2"
StaysOpenOnClick="True"
ToggleType="Radio" />
<MenuItem Header="Menu Item that won't close on click" StaysOpenOnClick="True" /> <MenuItem Header="Menu Item that won't close on click" StaysOpenOnClick="True" />
</ContextMenu> </ContextMenu>
</Border.ContextMenu> </Border.ContextMenu>

16
src/Avalonia.Controls/MenuItem.cs

@ -14,6 +14,7 @@ using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -387,7 +388,9 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
IEnumerable<IMenuItem> IMenuElement.SubItems => LogicalChildren.OfType<IMenuItem>(); IEnumerable<IMenuItem> IMenuElement.SubItems => LogicalChildren.OfType<IMenuItem>();
private IMenuInteractionHandler? MenuInteractionHandler => this.FindLogicalAncestorOfType<MenuBase>()?.InteractionHandler; private IMenuInteractionHandler? MenuInteractionHandler =>
this.FindLogicalAncestorOfType<MenuBase>()?.InteractionHandler ??
this.FindAncestorOfType<MenuBase>()?.InteractionHandler;
/// <summary> /// <summary>
/// Opens the submenu. /// Opens the submenu.
@ -470,6 +473,7 @@ namespace Avalonia.Controls
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
TryUpdateCanExecute(); TryUpdateCanExecute();
RegisterInMenuInteractionHandler();
} }
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
@ -908,6 +912,16 @@ namespace Avalonia.Controls
SelectedItem = null; 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 ICommandSource.CanExecuteChanged(object sender, EventArgs e) => CanExecuteChangedHandler(sender, e);
void IClickableControl.RaiseClick() void IClickableControl.RaiseClick()

17
src/Avalonia.Controls/RadioButtonGroupManager.cs

@ -41,6 +41,23 @@ internal class RadioButtonGroupManager
_registeredGroups.Add(groupName, group); _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<IRadioButton>(radioButton)); group.Add(new WeakReference<IRadioButton>(radioButton));
} }
} }

41
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -560,6 +560,47 @@ namespace Avalonia.Controls.UnitTests
Assert.True(menuItem3.IsChecked); 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] [Fact]
public void Radio_Menu_Group_Can_Be_Changed_In_Runtime() public void Radio_Menu_Group_Can_Be_Changed_In_Runtime()
{ {

Loading…
Cancel
Save