Unplanned
Last Updated: 22 Jan 2020 17:04 by ADMIN
ADMIN
Lance | Team Lead - US DevTools Support
Created on: 26 Jun 2018 21:19
Category: Checkbox
Type: Feature Request
7
CheckBox: Exclusive Selection
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.
1 comment
ADMIN
Lance | Team Lead - US DevTools Support
Posted on: 22 Jan 2020 17:04

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.

GroupedCheckBox

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:

  1. Traverse the Visual Tree to find other sibling GroupedCheckBox instances.
  2. Check to see if the GroupName matches and uncheck it.

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.

Demo

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>

MVVM Note

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

Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Attached Files: