Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 4 additions & 27 deletions src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public RadioButtonGroupController(Maui.ILayout layout)
}

_layout = (Element)layout;
_layout.ChildAdded += ChildAdded;
_layout.ChildRemoved += ChildRemoved;
_layout.DescendantAdded += DescendantAdded;
_layout.DescendantRemoved += DescendantRemoved;
Comment on lines +25 to +26
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions significant changes to RadioButton.cs, RadioButtonGroup.cs, and test files (Issue33264.xaml, Issue33264.xaml.cs, and Issue33264.cs) that are not present in this diff. The actual changes in this PR are limited to:

  1. RadioButtonGroupController.cs - Event handler changes from ChildAdded/ChildRemoved to DescendantAdded/DescendantRemoved
  2. RadioButtonTests.cs - Two new unit tests

Please verify that all intended changes were included in this pull request, or update the PR description to accurately reflect the actual changes being made.

Copilot uses AI. Check for mistakes.

if (!string.IsNullOrEmpty(_groupName))
{
Expand All @@ -50,7 +50,7 @@ internal void HandleRadioButtonGroupSelectionChanged(RadioButton radioButton)
_layout.SetValue(RadioButtonGroup.SelectedValueProperty, radioButton.Value);
}

void ChildAdded(object sender, ElementEventArgs e)
void DescendantAdded(object sender, ElementEventArgs e)
{
if (string.IsNullOrEmpty(_groupName) || _layout == null)
{
Expand All @@ -61,19 +61,9 @@ void ChildAdded(object sender, ElementEventArgs e)
{
AddRadioButton(radioButton);
}
else
{
foreach (var element in e.Element.Descendants())
{
if (element is RadioButton childRadioButton)
{
AddRadioButton(childRadioButton);
}
}
}
}

void ChildRemoved(object sender, ElementEventArgs e)
void DescendantRemoved(object sender, ElementEventArgs e)
{
if (e.Element is RadioButton radioButton)
{
Expand All @@ -82,19 +72,6 @@ void ChildRemoved(object sender, ElementEventArgs e)
groupControllers.Remove(radioButton);
}
}
else
{
foreach (var element in e.Element.Descendants())
{
if (element is RadioButton radioButton1)
{
if (groupControllers.TryGetValue(radioButton1, out _))
{
groupControllers.Remove(radioButton1);
}
}
}
}
}

internal void HandleRadioButtonValueChanged(RadioButton radioButton)
Expand Down
90 changes: 90 additions & 0 deletions src/Controls/tests/Core.UnitTests/RadioButtonTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,5 +319,95 @@ public void ValuePropertyCanBeSetToNull()

Assert.Null(radioButton.Value);
}

[Fact]
public void RadioButtonGroupWorksWithDynamicallyAddedDescendants()
{
// Simulates CollectionView scenario where RadioButtons are added as descendants
// rather than direct children (they're inside ItemTemplate)
var layout = new StackLayout();
var groupName = "choices";

// Set up RadioButtonGroup on parent layout
layout.SetValue(RadioButtonGroup.GroupNameProperty, groupName);
layout.SetValue(RadioButtonGroup.SelectedValueProperty, null);

// Create a container that simulates CollectionView item container
var itemContainer = new StackLayout();
layout.Children.Add(itemContainer);

// Create RadioButtons and add them to the nested container
// This triggers DescendantAdded events (like CollectionView does)
var radioButton1 = new RadioButton() { Value = "Choice 1" };
var radioButton2 = new RadioButton() { Value = "Choice 2" };
var radioButton3 = new RadioButton() { Value = "Choice 3" };

itemContainer.Children.Add(radioButton1);
itemContainer.Children.Add(radioButton2);
itemContainer.Children.Add(radioButton3);

// Verify RadioButtons received the group name from ancestor
Assert.Equal(groupName, radioButton1.GroupName);
Assert.Equal(groupName, radioButton2.GroupName);
Assert.Equal(groupName, radioButton3.GroupName);

// Verify SelectedValue is initially null
Assert.Null(layout.GetValue(RadioButtonGroup.SelectedValueProperty));

// Check a RadioButton
radioButton2.IsChecked = true;

// Verify SelectedValue binding is updated
Assert.Equal("Choice 2", layout.GetValue(RadioButtonGroup.SelectedValueProperty));

// Check another RadioButton
radioButton3.IsChecked = true;

// Verify SelectedValue binding updates again
Assert.Equal("Choice 3", layout.GetValue(RadioButtonGroup.SelectedValueProperty));

// Verify only one RadioButton is checked
Assert.False(radioButton1.IsChecked);
Assert.False(radioButton2.IsChecked);
Assert.True(radioButton3.IsChecked);
}

[Fact]
public void RadioButtonGroupSelectedValueBindingWorksWithNestedDescendants()
{
// Tests that setting SelectedValue on the group selects the correct descendant RadioButton
var layout = new StackLayout();
var groupName = "choices";

layout.SetValue(RadioButtonGroup.GroupNameProperty, groupName);

// Nested container simulating CollectionView
var itemContainer = new StackLayout();
layout.Children.Add(itemContainer);

var radioButton1 = new RadioButton() { Value = "Choice 1" };
var radioButton2 = new RadioButton() { Value = "Choice 2" };
var radioButton3 = new RadioButton() { Value = "Choice 3" };

itemContainer.Children.Add(radioButton1);
itemContainer.Children.Add(radioButton2);
itemContainer.Children.Add(radioButton3);

// Set SelectedValue from the group (simulates binding update)
layout.SetValue(RadioButtonGroup.SelectedValueProperty, "Choice 2");

// Verify the correct RadioButton is checked
Assert.False(radioButton1.IsChecked);
Assert.True(radioButton2.IsChecked);
Assert.False(radioButton3.IsChecked);

// Change SelectedValue
layout.SetValue(RadioButtonGroup.SelectedValueProperty, "Choice 3");

// Verify selection updates
Assert.False(radioButton1.IsChecked);
Assert.False(radioButton2.IsChecked);
Assert.True(radioButton3.IsChecked);
}
}
}
Loading