Request for RadioButton-like mutual exclusive selection using a Group type of property. Ex: <CheckBox Group="Gender" /> <CheckBox Group="Gender" /> <CheckBox Group="Gender" /> When one of the CheckBoxes in the Gender group are checked, any other selections are cleared.
Hello,
There have been additional requests for this feature, therefore I've put together a small example of how you can implement grouping RadCheckBoxes to achieve exclusive selection.
First, you'll need a way to know what RadCheckBoxes need to be grouped. The easiest way to do this would be to subclass the RadCheckBox and add a simple GroupName BindableProperty.
Next, you'll want to subscribe to the IsCheckChanged event. In the event handler, you can take the following steps:
public class GroupedCheckBox : RadCheckBox
{
public GroupedCheckBox()
{
IsCheckedChanged += GroupedCheckBox_IsCheckedChanged;
}
private void GroupedCheckBox_IsCheckedChanged(object sender, IsCheckedChangedEventArgs e)
{
// Only run this logic if IsChecked=True
if (e.NewValue == true)
{
// 1. Get a list of all the other GroupedCheckBoxes
var grid = GetParentGrid();
if(grid == null) return;
var checkBoxes = FindGroupedCheckBoxChildren(grid, grid);
if(checkBoxes == null) return;
// 2. Iterate over the GroupedCheckBoxes, if the GroupName matches, then uncheck it.
foreach (var cb in checkBoxes.Where(cb => cb != this && cb.GroupName == this.GroupName))
{
cb.IsChecked = false;
}
}
}
public static readonly BindableProperty GroupNameProperty = BindableProperty.Create(
"GroupName", typeof(string), typeof(GroupedCheckBox), null);
public string GroupName
{
get => (string)GetValue(GroupNameProperty);
set => SetValue(GroupNameProperty, value);
}
private Grid GetParentGrid()
{
var parent = this.Parent;
while (parent != null)
{
// Return the first Grid
if (parent is Grid grid)
{
return grid;
}
parent = parent.Parent;
}
return null;
}
// Finds GroupedCheckBox children, see original version here https://forums.xamarin.com/discussion/18599/visual-tree-helper
public List<GroupedCheckBox> FindGroupedCheckBoxChildren(VisualElement parentElement, VisualElement whereSearch, string containsStringName = null, List<GroupedCheckBox> result = null)
{
result = result ?? new List<GroupedCheckBox>();
try
{
var props = whereSearch.GetType().GetRuntimeProperties().ToList();
var contProp = props.FirstOrDefault(w => w.Name == "Content");
var childProp = props.FirstOrDefault(w => w.Name == "Children");
if (childProp == null)
{
if (contProp == null) return result;
if (contProp.GetValue(whereSearch) is VisualElement cv) FindGroupedCheckBoxChildren(parentElement, cv, containsStringName, result);
return result;
}
var v = childProp.GetValue(whereSearch) as IEnumerable;
foreach (var i in v)
{
if (i is VisualElement element) FindGroupedCheckBoxChildren(parentElement, element, containsStringName, result);
if (i is GroupedCheckBox ii)
{
if (!string.IsNullOrEmpty(containsStringName))
{
var fields = parentElement.GetType().GetRuntimeFields().Where(w => w.Name.ToLower().Contains(containsStringName.ToLower())).ToList();
var fCheck = fields.Select(f => f.GetValue(parentElement)).Any(fv => fv is GroupedCheckBox && fv == i);
if (!fCheck) continue;
}
result.Add(ii);
}
}
return result;
}
catch { return result; }
}
}
Important: The example has a known Grid element as the top level parent. It then looks for all the GroupedCheckBox children from there. Your application may be different and you need to consider this before using such an approach in a production application.
Find this approach used in the attached example. I've placed two groups of CheckBoxes, one uses a GroupName of "Color" and the other uses a GroupName of "Gender". Checking a checkbox will uncheck any other in the same Group.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:GroupedSelectionDemo.Portable"
x:Class="GroupedSelectionDemo.Portable.MainPage">
<Grid Padding="10">
<StackLayout Spacing="5">
<Label Text="Exclusive Color Selection" />
<StackLayout Orientation="Horizontal">
<local:GroupedCheckBox GroupName="Color"
IsChecked="True"
Margin="0,0,10,0" />
<Label Text="Red" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<local:GroupedCheckBox GroupName="Color"
Margin="0,0,10,0" />
<Label Text="Green" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<local:GroupedCheckBox GroupName="Color"
Margin="0,0,10,0" />
<Label Text="Blue" />
</StackLayout>
<Label Text="Exclusive Gender Selection" />
<StackLayout Orientation="Horizontal">
<local:GroupedCheckBox GroupName="Gender"
IsChecked="True"
Margin="0,0,10,0" />
<Label Text="Male" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<local:GroupedCheckBox GroupName="Gender"
Margin="0,0,10,0" />
<Label Text="Female" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<local:GroupedCheckBox GroupName="Gender"
Margin="0,0,10,0" />
<Label Text="Other" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<local:GroupedCheckBox GroupName="Gender"
Margin="0,0,10,0" />
<Label Text="Prefer not to say" />
</StackLayout>
</StackLayout>
</Grid>
</ContentPage>
I have received questions regarding using such an approach with MVVM or in a DataTemplate. There is no fundamental difference in these scenarios, you are still binding to the IsChecked property of the RadCheckBox.
You can use TwoWay binding to update the values in the bound model. For example:
<StackLayout Orientation="Horizontal">
<local:GroupedCheckBox GroupName="Color" IsChecked="{Binding IsRedChecked, Mode=TwoWay}" Margin="0,0,10,0" />
<Label Text="Red" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<local:GroupedCheckBox GroupName="Color" IsChecked="{Binding IsGreenChecked, Mode=TwoWay}" Margin="0,0,10,0" />
<Label Text="Green" />
</StackLayout>
Regards,
Lance | Team Lead - US DevTools Support
Progress Telerik