Skip to content
Closed
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
26 changes: 26 additions & 0 deletions src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down Expand Up @@ -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)
Expand Down
23 changes: 23 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue33264.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue33264"
Title="Issue 33264">

<StackLayout Padding="20" Spacing="10" RadioButtonGroup.GroupName="choices" RadioButtonGroup.SelectedValue="{Binding SelectedValue}">
<Label Text="RadioButtonGroup not working with CollectionView" FontSize="18" FontAttributes="Bold"/>

<Label Text="Select an option below:" FontSize="14"/>

<CollectionView ItemsSource="{Binding Choices}" AutomationId="ChoicesCollectionView">
<CollectionView.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding}" Value="{Binding}" GroupName="choices" AutomationId="{Binding}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

<Label Text="Selected value:" FontSize="14" FontAttributes="Bold"/>
<Label Text="{Binding SelectedValueDisplay}" FontSize="16" AutomationId="SelectedValueLabel"/>
</StackLayout>
</ContentPage>
68 changes: 68 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue33264.xaml.cs
Original file line number Diff line number Diff line change
@@ -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<string> Choices { get; } = new ObservableCollection<string>
{
"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));
}
}
Original file line number Diff line number Diff line change
@@ -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();
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Using Task.Delay().Wait() with a hardcoded delay is not a reliable approach for UI testing. This pattern is discouraged in the MAUI test infrastructure. Instead, rely on App.WaitForElement() which has built-in retry logic. If you need additional time for rendering, App.WaitForElement() already handles this with its timeout mechanism.

Copilot uses AI. Check for mistakes.

// 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();
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Using Task.Delay().Wait() for hardcoded wait times is unreliable for UI tests. App.WaitForElement() already includes appropriate timeout logic. Remove this line and rely on the WaitForElement calls to handle timing.

Copilot uses AI. Check for mistakes.

// 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();
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Using Task.Delay().Wait() for hardcoded wait times is unreliable for UI tests. App.WaitForElement() already includes appropriate timeout logic. Remove this line and rely on the WaitForElement calls to handle timing.

Copilot uses AI. Check for mistakes.

// 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");
}
}
Loading