diff --git a/src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs b/src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs index 265dcf49a373..e1a324efa0c9 100644 --- a/src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs +++ b/src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs @@ -24,6 +24,8 @@ public RadioButtonGroupController(Maui.ILayout layout) _layout = (Element)layout; _layout.ChildAdded += ChildAdded; _layout.ChildRemoved += ChildRemoved; + _layout.DescendantAdded += DescendantAdded; + _layout.DescendantRemoved += DescendantRemoved; if (!string.IsNullOrEmpty(_groupName)) { @@ -97,6 +99,30 @@ void ChildRemoved(object sender, ElementEventArgs e) } } + void DescendantAdded(object sender, ElementEventArgs e) + { + if (string.IsNullOrEmpty(_groupName) || _layout == null) + { + return; + } + + if (e.Element is RadioButton radioButton) + { + AddRadioButton(radioButton); + } + } + + void DescendantRemoved(object sender, ElementEventArgs e) + { + if (e.Element is RadioButton radioButton) + { + if (groupControllers.TryGetValue(radioButton, out _)) + { + groupControllers.Remove(radioButton); + } + } + } + internal void HandleRadioButtonValueChanged(RadioButton radioButton) { if (radioButton?.GroupName != _groupName) diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue33264.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue33264.xaml new file mode 100644 index 000000000000..287d5fedd504 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue33264.xaml @@ -0,0 +1,23 @@ + + + + + + diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue33264.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue33264.xaml.cs new file mode 100644 index 000000000000..caa69fa751ce --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue33264.xaml.cs @@ -0,0 +1,68 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 33264, "RadioButtonGroup not working with CollectionView", PlatformAffected.All)] +public partial class Issue33264 : ContentPage +{ + public Issue33264() + { + InitializeComponent(); + BindingContext = new Issue33264ViewModel(); + } + + protected override void OnAppearing() + { + base.OnAppearing(); + Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(500), () => + { + CaptureState("OnAppearing"); + }); + } + + private void CaptureState(string context) + { + var vm = BindingContext as Issue33264ViewModel; + Console.WriteLine($"=== STATE CAPTURE: {context} ==="); + Console.WriteLine($"SelectedValue: {vm?.SelectedValue ?? "null"}"); + Console.WriteLine("=== END STATE CAPTURE ==="); + } +} + +public class Issue33264ViewModel : INotifyPropertyChanged +{ + private string _selectedValue; + + public ObservableCollection Choices { get; } = new ObservableCollection + { + "Choice 1", + "Choice 2", + "Choice 3" + }; + + public string SelectedValue + { + get => _selectedValue; + set + { + if (_selectedValue != value) + { + Console.WriteLine($"=== BINDING UPDATE: SelectedValue changing from '{_selectedValue}' to '{value}' ==="); + _selectedValue = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(SelectedValueDisplay)); + } + } + } + + public string SelectedValueDisplay => string.IsNullOrEmpty(SelectedValue) ? "None" : SelectedValue; + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33264.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33264.cs new file mode 100644 index 000000000000..1e239d7e44ba --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33264.cs @@ -0,0 +1,52 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue33264 : _IssuesUITest +{ + public override string Issue => "RadioButtonGroup not working with CollectionView"; + + public Issue33264(TestDevice device) : base(device) { } + + [Test] + [Category(UITestCategories.RadioButton)] + public void RadioButtonGroupBindingWorksInsideCollectionView() + { + // Wait for page to load + App.WaitForElement("ChoicesCollectionView"); + App.WaitForElement("SelectedValueLabel"); + + // Give it a moment to render + Task.Delay(500).Wait(); + + // Initially, SelectedValue should show "None" + var initialValue = App.FindElement("SelectedValueLabel").GetText(); + Console.WriteLine($"Initial SelectedValue: '{initialValue}'"); + Assert.That(initialValue, Is.EqualTo("None"), "Initial value should be 'None'"); + + // Tap "Choice 2" radio button + App.WaitForElement("Choice 2"); + App.Tap("Choice 2"); + + // Wait for binding update + Task.Delay(1000).Wait(); + + // Verify SelectedValue is updated + var selectedValue = App.FindElement("SelectedValueLabel").GetText(); + Console.WriteLine($"After tapping 'Choice 2', SelectedValue: '{selectedValue}'"); + Assert.That(selectedValue, Is.EqualTo("Choice 2"), "SelectedValue should be updated via binding when RadioButton is checked"); + + // Tap "Choice 3" radio button + App.Tap("Choice 3"); + + // Wait for binding update + Task.Delay(1000).Wait(); + + // Verify SelectedValue is updated again + selectedValue = App.FindElement("SelectedValueLabel").GetText(); + Console.WriteLine($"After tapping 'Choice 3', SelectedValue: '{selectedValue}'"); + Assert.That(selectedValue, Is.EqualTo("Choice 3"), "SelectedValue should be updated when different RadioButton is checked"); + } +}