Skip to content

Commit c1a0c7c

Browse files
asi-evinEvin Ballantyne
authored andcommitted
Radio Button Null Selection Fix (#31175)
* Added UI test for null selectin bug * Handle when group update changes to no values * Added unit test * new file to file scoped namespace * Ignore child added if layout is null * Piggy backed off of existing UI sample * removing unused using * reverting whitespace * removing unused using * Have RadioButton default to itself for uniqueness * Added comment justifying madness * less hacky way * Less hacky and added unit test --------- Co-authored-by: Evin Ballantyne <[email protected]>
1 parent 123a6ef commit c1a0c7c

File tree

5 files changed

+63
-11
lines changed

5 files changed

+63
-11
lines changed

src/Controls/samples/Controls.Sample/Pages/Controls/RadioButtonGalleries/RadioButtonGroupBindingGallery.xaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<RowDefinition></RowDefinition>
1919
<RowDefinition></RowDefinition>
2020
<RowDefinition></RowDefinition>
21+
<RowDefinition></RowDefinition>
2122
</Grid.RowDefinitions>
2223

2324
<Grid.ColumnDefinitions>
@@ -28,15 +29,16 @@
2829
<Label Grid.ColumnSpan="2" Text="The RadioButtons in this Grid have a GroupName and Selection bound to a ViewModel."></Label>
2930

3031
<Label Text="{Binding GroupName, StringFormat='The GroupName is {0}'}" Grid.Row="1" />
31-
<Label Text="{Binding Selection, StringFormat='The Selection is {0}'}" Grid.Row="1" Grid.Column="1" />
32+
<Label Text="{Binding Selection, StringFormat='The Selection is {0}', TargetNullValue='The Selection is (null)'}" Grid.Row="1" Grid.Column="1" />
3233

3334
<RadioButton Content="Option A" Value="A" Grid.Row="2"></RadioButton>
3435
<RadioButton Content="Option B" Value="B" Grid.Row="2" Grid.Column="1"></RadioButton>
3536
<RadioButton Content="Option C" Value="C" Grid.Row="3"></RadioButton>
3637
<RadioButton Content="Option D" Value="D" Grid.Row="3" Grid.Column="1"></RadioButton>
3738

38-
<Button Margin="5" Grid.ColumnSpan="2" Grid.Row="4" Text="Set selection in view model to 'B'" Clicked="Button_Clicked"></Button>
39-
</Grid>
39+
<Button Margin="5" Grid.ColumnSpan="2" Grid.Row="4" Text="Set selection in view model to 'B'" Clicked="Set_Button_Clicked"></Button>
40+
<Button Margin="5" Grid.ColumnSpan="2" Grid.Row="5" Text="Clear selection in view model to 'null'" Clicked="Clear_Button_Clicked"></Button>
41+
</Grid>
4042
</StackLayout>
4143
</ContentPage.Content>
4244
</ContentPage>

src/Controls/samples/Controls.Sample/Pages/Controls/RadioButtonGalleries/RadioButtonGroupBindingGallery.xaml.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,15 @@ public RadioButtonGroupBindingGallery()
1515
BindingContext = _viewModel;
1616
}
1717

18-
private void Button_Clicked(object sender, System.EventArgs e)
18+
private void Set_Button_Clicked(object sender, System.EventArgs e)
1919
{
2020
_viewModel.Selection = "B";
2121
}
22+
23+
private void Clear_Button_Clicked(object sender, System.EventArgs e)
24+
{
25+
_viewModel.Selection = null;
26+
}
2227
}
2328

2429
public class RadioButtonGroupBindingModel : INotifyPropertyChanged

src/Controls/src/Core/RadioButton/RadioButton.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ public partial class RadioButton : TemplatedView, IElementConfiguration<RadioBut
5959
/// <summary>Bindable property for <see cref="Value"/>.</summary>
6060
public static readonly BindableProperty ValueProperty =
6161
BindableProperty.Create(nameof(Value), typeof(object), typeof(RadioButton), null,
62-
propertyChanged: (b, o, n) => ((RadioButton)b).OnValuePropertyChanged());
62+
propertyChanged: (b, o, n) => ((RadioButton)b).OnValuePropertyChanged(),
63+
coerceValue: (b, o) => o ?? b);
6364

6465
/// <summary>Bindable property for <see cref="IsChecked"/>.</summary>
6566
public static readonly BindableProperty IsCheckedProperty = BindableProperty.Create(
@@ -212,6 +213,9 @@ public RadioButton()
212213
{
213214
_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<RadioButton>>(() =>
214215
new PlatformConfigurationRegistry<RadioButton>(this));
216+
217+
//initialize Value to prevent null value
218+
Value = this;
215219
}
216220

217221
/// <inheritdoc/>
@@ -433,12 +437,17 @@ void HandleRadioButtonGroupSelectionChanged(RadioButton selected, RadioButtonGro
433437

434438
void HandleRadioButtonGroupValueChanged(Element layout, RadioButtonGroupValueChanged args)
435439
{
436-
if (IsChecked || string.IsNullOrEmpty(GroupName) || GroupName != args.GroupName || !object.Equals(Value, args.Value) || !MatchesScope(args))
440+
if (string.IsNullOrEmpty(GroupName) || GroupName != args.GroupName || !MatchesScope(args))
437441
{
438442
return;
439443
}
440444

441-
SetValue(IsCheckedProperty, true, specificity: SetterSpecificity.FromHandler);
445+
var isValueMatching = object.Equals(Value, args.Value);
446+
447+
if (IsChecked != isValueMatching)
448+
{
449+
SetValue(IsCheckedProperty, isValueMatching, specificity: SetterSpecificity.FromHandler);
450+
}
442451
}
443452

444453
static View BuildDefaultTemplate()

src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ void HandleRadioButtonValueChanged(RadioButton radioButton, RadioButtonValueChan
7474

7575
void ChildAdded(object sender, ElementEventArgs e)
7676
{
77-
if (string.IsNullOrEmpty(_groupName))
77+
if (string.IsNullOrEmpty(_groupName) || _layout == null)
7878
{
7979
return;
8080
}
@@ -135,15 +135,18 @@ void UpdateGroupNames(Element element, string name, string oldName = null)
135135

136136
void SetSelectedValue(object radioButtonValue)
137137
{
138+
if(object.Equals(_selectedValue, radioButtonValue))
139+
{
140+
return;
141+
}
142+
138143
_selectedValue = radioButtonValue;
139144

140-
if (radioButtonValue != null)
141-
{
142145
#pragma warning disable CS0618 // TODO: Remove when we internalize/replace MessagingCenter
143146
MessagingCenter.Send<Element, RadioButtonGroupValueChanged>(_layout, RadioButtonGroup.GroupValueChangedMessage,
144147
new RadioButtonGroupValueChanged(_groupName, RadioButtonGroup.GetVisualRoot(_layout), radioButtonValue));
145148
#pragma warning restore CS0618 // Type or member is obsolete
146-
}
149+
147150
}
148151

149152
void SetGroupName(string groupName)

src/Controls/tests/Core.UnitTests/RadioButtonTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,5 +236,38 @@ public void GroupSelectedValueUpdatesWhenSelectedButtonValueUpdates()
236236

237237
Assert.Equal("updated", layout.GetValue(RadioButtonGroup.SelectedValueProperty));
238238
}
239+
240+
[Fact]
241+
public void GroupNullSelectionClearsAnySelection()
242+
{
243+
var layout = new Grid();
244+
layout.SetValue(RadioButtonGroup.GroupNameProperty, "foo");
245+
246+
var radioButton1 = new RadioButton() { Value = 1, IsChecked = true };
247+
var radioButton2 = new RadioButton() { Value = 2 };
248+
var radioButton3 = new RadioButton() { Value = 3 };
249+
250+
layout.Children.Add(radioButton1);
251+
layout.Children.Add(radioButton2);
252+
layout.Children.Add(radioButton3);
253+
254+
Assert.Equal(1, layout.GetValue(RadioButtonGroup.SelectedValueProperty));
255+
256+
layout.SetValue(RadioButtonGroup.SelectedValueProperty, null);
257+
258+
Assert.False(radioButton1.IsChecked);
259+
}
260+
261+
[Fact]
262+
public void ValuePropertyCoercedToItselfIfSetToNull()
263+
{
264+
var radioButton = new RadioButton();
265+
266+
Assert.Equal(radioButton, radioButton.Value);
267+
268+
radioButton.Value = null;
269+
270+
Assert.Equal(radioButton, radioButton.Value);
271+
}
239272
}
240273
}

0 commit comments

Comments
 (0)