Describe the bug
RadioMenuFlyoutItem keys its internal s_selectionMap by GroupName. When IsChecked is set before GroupName — e.g. when those attributes appear in that order in XAML, or when GroupName is changed in code while the item is checked — the item ends up registered under both the previous (default "") key and the new key. The stale entry is never cleaned up, so an unrelated RadioMenuFlyoutItem whose GroupName happens to match the stale key (most commonly the default empty string) will incorrectly uncheck the original item when it becomes checked.
Why is this important?
Apps that bind IsChecked and GroupName to view-model state, or that simply rely on standard XAML attribute order (IsChecked="True" GroupName="..."), end up with radio items that silently un-check each other across unrelated menus / groups. The behavior is non-deterministic from the app developer's perspective because it depends on XAML attribute order in the parsed source.
Steps to reproduce the bug
- Create a blank WinUI 3 (WinAppSDK) project.
- Replace
MainWindow.xaml with two MenuFlyouts. The first contains a RadioMenuFlyoutItem declared as IsChecked="True" GroupName="MyGroup" (in that attribute order) plus a sibling in "MyGroup". The second contains a single RadioMenuFlyoutItem with no GroupName set.
- Open the first menu (to fire
OnLoaded on the checked item), then close it.
- Open the second menu, then close it.
- Programmatically set the second menu's item's
IsChecked = true.
Minimal XAML + code-behind:
<StackPanel>
<Button Content="Open Menu A">
<Button.Flyout>
<MenuFlyout>
<RadioMenuFlyoutItem x:Name="ItemA"
IsChecked="True" GroupName="MyGroup"
Text="A (pre-checked, MyGroup)"/>
<RadioMenuFlyoutItem GroupName="MyGroup" Text="B (MyGroup)"/>
</MenuFlyout>
</Button.Flyout>
</Button>
<Button Content="Open Menu B">
<Button.Flyout>
<MenuFlyout>
<RadioMenuFlyoutItem x:Name="ItemC" Text="C (no group)"/>
</MenuFlyout>
</Button.Flyout>
</Button>
<Button Content="Check C" Click="OnCheckC"/>
<TextBlock x:Name="Status"/>
</StackPanel>
private void OnCheckC(object sender, RoutedEventArgs e)
{
ItemC.IsChecked = true;
Status.Text = $"ItemA.IsChecked = {ItemA.IsChecked} (expected: True)";
}
Actual behavior
After step 5, ItemA.IsChecked becomes False, even though ItemA is in "MyGroup" and ItemC is in the default empty group — they should be independent.
Expected behavior
ItemA.IsChecked should remain True. Items in different GroupNames should not affect one another.
NuGet package version
1.8.260317003
Windows version
Windows 11 (24H2): Build 26100
Additional context
Root cause is in RadioMenuFlyoutItem::UpdateCheckedItemInGroup / OnPropertyChanged (file controls/dev/RadioMenuFlyoutItem/RadioMenuFlyoutItem.cpp). When s_GroupNameProperty changes, m_groupName is updated but no cleanup of any previous registration in s_selectionMap is performed. OnLoaded only inserts under the current group; it does not erase the prior entry. OnUnloaded then calls SharedHelpers::EraseIfExists(*s_selectionMap, m_groupName), which is keyed by the current GroupName, so it can erase the wrong entry while leaving the stale one in place.
Suggested fix: track the GroupName actually used at registration time (separate from m_groupName) and, on every re-registration, remove the prior entry if it still points to this.
Describe the bug
RadioMenuFlyoutItemkeys its internals_selectionMapbyGroupName. WhenIsCheckedis set beforeGroupName— e.g. when those attributes appear in that order in XAML, or whenGroupNameis changed in code while the item is checked — the item ends up registered under both the previous (default"") key and the new key. The stale entry is never cleaned up, so an unrelatedRadioMenuFlyoutItemwhoseGroupNamehappens to match the stale key (most commonly the default empty string) will incorrectly uncheck the original item when it becomes checked.Why is this important?
Apps that bind
IsCheckedandGroupNameto view-model state, or that simply rely on standard XAML attribute order (IsChecked="True" GroupName="..."), end up with radio items that silently un-check each other across unrelated menus / groups. The behavior is non-deterministic from the app developer's perspective because it depends on XAML attribute order in the parsed source.Steps to reproduce the bug
MainWindow.xamlwith twoMenuFlyouts. The first contains aRadioMenuFlyoutItemdeclared asIsChecked="True" GroupName="MyGroup"(in that attribute order) plus a sibling in"MyGroup". The second contains a singleRadioMenuFlyoutItemwith noGroupNameset.OnLoadedon the checked item), then close it.IsChecked = true.Minimal XAML + code-behind:
Actual behavior
After step 5,
ItemA.IsCheckedbecomesFalse, even thoughItemAis in"MyGroup"andItemCis in the default empty group — they should be independent.Expected behavior
ItemA.IsCheckedshould remainTrue. Items in differentGroupNames should not affect one another.NuGet package version
1.8.260317003
Windows version
Windows 11 (24H2): Build 26100
Additional context
Root cause is in
RadioMenuFlyoutItem::UpdateCheckedItemInGroup/OnPropertyChanged(filecontrols/dev/RadioMenuFlyoutItem/RadioMenuFlyoutItem.cpp). Whens_GroupNamePropertychanges,m_groupNameis updated but no cleanup of any previous registration ins_selectionMapis performed.OnLoadedonly inserts under the current group; it does not erase the prior entry.OnUnloadedthen callsSharedHelpers::EraseIfExists(*s_selectionMap, m_groupName), which is keyed by the currentGroupName, so it can erase the wrong entry while leaving the stale one in place.Suggested fix: track the
GroupNameactually used at registration time (separate fromm_groupName) and, on every re-registration, remove the prior entry if it still points tothis.