[FIXED] How to group and bind checkboxes in sfListView?

Issue

I have a ObeservableCollection to be used as data binding to a list view. Following is the example of the list:

[{"Category":"ENGINE","Key":"BATTERY","Value":"BATTERY"},
{"Category":"BRAKE","Key":"BRAKE","Value":"BRAKE PAD"}, 
{"Category":"ENGINE","Key":"COOLANT","Value":"COOLANT & WATER"}]

What I already achieved is something like the following screenshot:

List View

1

Yes, I can use group descriptor to group them based on one of the property in the model and also assign a data template to GroupHeaderTemplate with checkbox on the right.

However, the problem is I can’t access the group checkbox by calling id or name because it’s created dynamically.

My requirement is to link the group(black) and sub items(pink) checkbox together which means when all the sub-items belongs to the group have been selected, the group checkbox should also be selected automatically and vice versa. Once one of item is unselected, then the group checkbox should be unselected as well.

Currently stuck and not sure how to proceed, any help would be appreciate. Thanks~

Edited with code:
GroupedCheckList.xaml

<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:syncfusion="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms"
xmlns:data="clr-namespace:Syncfusion.DataSource;assembly=Syncfusion.DataSource.Portable"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:ASolute_Mobile" xmlns:local1="clr-namespace:ASolute_Mobile.Models.GroupedCheckListModel" xmlns:button="clr-namespace:Syncfusion.XForms.Buttons;assembly=Syncfusion.Buttons.XForms"
x:Class="ASolute_Mobile.FleetScreen.GroupedCheckList">
    <StackLayout x:Name="Fleet_CheckItem" Spacing="15" Padding="15,15,15,15">          
        <syncfusion:SfListView x:Name="groupedListView" ItemsSource="{Binding Check}" AllowGroupExpandCollapse="True">
            <syncfusion:SfListView.DataSource>
                <data:DataSource>
                    <data:DataSource.GroupDescriptors>
                        <data:GroupDescriptor PropertyName="Category">
                        </data:GroupDescriptor>
                    </data:DataSource.GroupDescriptors>
                    </data:DataSource>
            </syncfusion:SfListView.DataSource>
            <syncfusion:SfListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <ViewCell.View>
                            <StackLayout>
                                <StackLayout>
                                    <button:SfCheckBox Text="{Binding Name}" IsChecked="{Binding IsSelected}" CheckedColor="LightCoral"  FontSize="20" StateChanged="CheckBox_StateChanged" />
                                </StackLayout>                           
                            </StackLayout>                                
                        </ViewCell.View>
                    </ViewCell>
                </DataTemplate>
            </syncfusion:SfListView.ItemTemplate>
        </syncfusion:SfListView>
    <Image Source="nextPage.png" WidthRequest="70" HeightRequest="70" VerticalOptions="Center" HorizontalOptions="End" x:Name="fleet_confirm_icon" IsVisible="false">
        <Image.GestureRecognizers>
            <TapGestureRecognizer Tapped="fleet_toNextPage" />
        </Image.GestureRecognizers>
    </Image>
</StackLayout>
</ContentPage>

GroupedCheckList.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using ASolute.Mobile.Models;
using ASolute_Mobile.Models;
using ASolute_Mobile.Models.GroupedCheckListModel;
using ASolute_Mobile.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Syncfusion.XForms.Buttons;
using Xamarin.Forms;

namespace ASolute_Mobile.FleetScreen
{
    public partial class GroupedCheckList : ContentPage
    {
        private ListItems item;
        string recordURL;
        List<clsCheckListItem> checkItems = new 
List<clsCheckListItem>();
    bool shouldLeave = true;

    public GroupedCheckList()
    {
        InitializeComponent();

        shouldLeave = false;

        StackLayout main = new StackLayout();

        Label title1 = new Label
        {
            FontSize = 15,
            Text = "Check List",
            TextColor = System.Drawing.Color.White
        };
        Label title2 = new Label
        {
            FontSize = 10,
            Text = Ultis.Settings.SubTitle,
            TextColor = System.Drawing.Color.White
        };

        main.Children.Add(title1);
        main.Children.Add(title2);

        NavigationPage.SetTitleView(this, main);
        NavigationPage.SetHasBackButton(this, false);

        displayData();
    }

    public GroupedCheckList(ListItems items, string recordUri)
    {

        item = items;
        InitializeComponent();

        shouldLeave = true;

        recordURL = recordUri;
        StackLayout main = new StackLayout();

        Label title1 = new Label
        {
            FontSize = 15,
            Text = "Check List",
            TextColor = System.Drawing.Color.White
        };
        Label title2 = new Label
        {
            FontSize = 10,
            Text = Ultis.Settings.SubTitle,
            TextColor = System.Drawing.Color.White
        };

        main.Children.Add(title1);
        main.Children.Add(title2);

        NavigationPage.SetTitleView(this, main);

        GetData();
    }

    async void GetData()
    {
        try
        {
            var content = await DependencyService.Get<CommonFunctionInterface>().CallWebService(0, null, Ultis.Settings.SessionBaseURI, recordURL, this);

            if (content != null)
            {
                clsResponse response = JsonConvert.DeserializeObject<clsResponse>(content);
                if (response.Result != "")
                {
                    var msgBoxResponse = await DisplayAlert("Info", response.Result, "YES", "NO");
                    if (msgBoxResponse)
                    {
                        displayData();
                    }
                    else
                    {
                        await Navigation.PopAsync();
                    }
                }
                else
                {
                    displayData();
                }
            }
        }
        catch (Exception e)
        {
            await DisplayAlert("Error", e.Message, "OK");
        }

    }

    async void displayData()
    {
        try
        {
            var content1 = await DependencyService.Get<CommonFunctionInterface>().CallWebService(0, null, Ultis.Settings.SessionBaseURI, ControllerUtil.getNewFleetCheckListURL(), this);
            if (content1 != null)
            {
                clsResponse response1 = JsonConvert.DeserializeObject<clsResponse>(content1);
                if (response1.IsGood == true)
                {
                    checkItems = JObject.Parse(content1)["Result"].ToObject<List<clsCheckListItem>>();
                    BindingContext = new GroupedCheckListModel(checkItems);
                    groupedListView.GroupHeaderTemplate = new DataTemplate(() => {
                        StackLayout headerStack = new StackLayout
                        {
                            Orientation = StackOrientation.Horizontal,
                            HorizontalOptions = LayoutOptions.FillAndExpand
                        };
                        StackLayout labelStack = new StackLayout
                        {
                            HorizontalOptions = LayoutOptions.StartAndExpand,                            
                        };
                        Label label = new Label()
                        {
                            FontSize = 22,
                            FontAttributes = FontAttributes.Bold
                        };
                        //Key is the property group.Key, not from our list
                        label.SetBinding(Label.TextProperty, "Key");
                        labelStack.Children.Add(label);
                        StackLayout checkBoxStack = new StackLayout
                        {
                            HorizontalOptions = LayoutOptions.EndAndExpand
                        };
                        SfCheckBox checkBox = new SfCheckBox();
                        checkBox.SetBinding(SfCheckBox.IsCheckedProperty, "IsSelected");                                            
                        checkBox.StateChanged += SfCheckBox_StateChanged;
                        checkBoxStack.Children.Add(checkBox);
                        headerStack.Children.Add(labelStack);
                        headerStack.Children.Add(checkBoxStack);
                        return new ViewCell { View = headerStack, StyleId = "NewViewCell" };
                    });

                    //groupedListView.DataSource.GroupDescriptors.Add(new Syncfusion.DataSource.GroupDescriptor()
                    //{
                    //    PropertyName = "Category",
                    //    KeySelector = (object obj1) =>
                    //    {
                    //        var item = (obj1 as GroupCheckListItem);
                    //        return item.Category[0].ToString();
                    //    }
                    //});
                }
                fleet_confirm_icon.IsVisible = true;
            }
        }
        catch (Exception e)
        {
            await DisplayAlert("Error", e.Message, "OK");
        }

    }

    async void CheckBox_StateChanged(System.Object sender, Syncfusion.XForms.Buttons.StateChangedEventArgs e)
    {            
        
    }

    public async void fleet_toNextPage(object sender, EventArgs e)
    {
        checkItems.Clear();
        try
        {
            //if (fleet_selectAll.IsChecked == true)
            //{
            //    checkItems.Clear();
            //}

            //else
            //{
            ObservableCollection<GroupedCheckListItem> checkedItems = new ObservableCollection<GroupedCheckListItem>();

            checkedItems = GroupedCheckListModel.CheckedList;
            foreach (GroupedCheckListItem item1 in checkedItems)
            {
                if (item1.IsSelected == false)
                {
                    //checkItems.Add(new clsCheckListItem(item1.Category, item1.Name));
                }
            }
            //}

            var content = await DependencyService.Get<CommonFunctionInterface>().CallWebService(0, null, Ultis.Settings.SessionBaseURI, ControllerUtil.getDownloadMenuURL(), this);
            clsResponse response = JsonConvert.DeserializeObject<clsResponse>(content);

            if (response.IsGood == true)
            {
                var menu = JObject.Parse(content)["Result"].ToObject<clsLogin>();
                //await Navigation.PushAsync(new CheckList2(checkItems, menu.CheckListLinkId));
            }
        }
        catch (Exception exception)
        {
            await DisplayAlert("Error", exception.Message, "Ok");
        }
    }

    async void SfCheckBox_StateChanged(System.Object sender, Syncfusion.XForms.Buttons.StateChangedEventArgs e)
    {
        try
        {
            SfCheckBox checkBox = sender as SfCheckBox;
            var binding = checkBox.BindingContext as Syncfusion.DataSource.Extensions.GroupResult;
            var groupedList = checkItems.GroupBy(x => x.Category);
            foreach (var group in groupedList)
            {
                if (group.Key == binding.Key.ToString())
                {
                    ObservableCollection<GroupedCheckListItem> oriItems = new ObservableCollection<GroupedCheckListItem>();
                    oriItems = GroupedCheckListModel.CheckedList;
                    foreach (var item in oriItems)
                    {
                        if (item.Category == group.Key)
                        {
                            item.IsSelected = checkBox.IsChecked.Value ? true : false;
                        }
                    }
                    break;
                }
            }
        }
        catch (Exception ex)
        {
            await DisplayAlert("Error", ex.Message, "Ok");
        }
    }
}
}

GroupedCheckListModel.cs

using ASolute.Mobile.Models;
using ASolute_Mobile.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using Xamarin.Forms;

namespace ASolute_Mobile.Models.GroupedCheckListModel
{
    public class GroupedCheckListModel : PropertyChange
    {
        public static ObservableCollection<GroupedCheckListItem> chkList;

    public ObservableCollection<GroupedCheckListItem> Check
    {
        set { SetProperty(ref chkList, value); }
        get { return chkList; }
    }

    public GroupedCheckListModel(List<clsCheckListItem> listItems)
    {
        Check = new ObservableCollection<GroupedCheckListItem>();

        foreach (clsCheckListItem item in listItems)
        {

            Check.Add(AddNew(item.Category, item.Key, item.Value, false));
        };

    }

    public static ObservableCollection<GroupedCheckListItem> CheckedList
    {
        get { return chkList; }
    }

    private GroupedCheckListItem AddNew(string category, string key, string value, bool isSelected)
    {
        var chkItem = new GroupedCheckListItem(category, key, value, isSelected);

        return chkItem;
    }
}










public class GroupingSelectionConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null)
            return value;

        return value;
        //GroupResult groupResult = value as GroupResult;
        //SfListView listview = parameter as SfListView;

        //var items = new List<MusicInfo>(groupResult.Items.ToList<MusicInfo>());

        //if ((items.All(listitem => listitem.IsSelected == false)))
        //{
        //    for (int i = 0; i < items.Count(); i++)
        //    {
        //        var item = items[i];
        //        (item as MusicInfo).IsSelected = false;
        //        listview.SelectedItems.Remove(item);
        //    }
        //    return ImageSource.FromResource("CustomSelection.Images.NotSelected.png");
        //}

        //else if ((items.All(listitem => listitem.IsSelected == true)))
        //{
        //    for (int i = 0; i < items.Count(); i++)
        //    {
        //        var item = items[i];
        //        (item as MusicInfo).IsSelected = true;
        //        listview.SelectedItems.Add(item);
        //    }

        //    return ImageSource.FromResource("CustomSelection.Images.Selected.png");
        //}

        //else
        //    return ImageSource.FromResource("CustomSelection.Images.Intermediate.png");

    }

  
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

  
}



public class SelectionBoolToImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        //if ((bool)value)
        //    return ImageSource.FromResource("CustomSelection.Images.Selected.png");
        //else
        //    return ImageSource.FromResource("CustomSelection.Images.NotSelected.png");

        if (value == null)
            return value;

        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
}

GroupedCheckListItem

using System;
namespace ASolute_Mobile.Models.GroupedCheckListModel
{
    public class GroupedCheckListItem : PropertyChange
    {
        private string category;
        private string name;
        private string key;
        private bool isSelected;

    public string Category
    {
        set { SetProperty(ref category, value); }
        get { return category; }
    }

    public string Name
    {
        set { SetProperty(ref name, value); }
        get { return name; }
    }

    public string Key
    {
        set { SetProperty(ref key, value); }
        get { return key; }
    }

    public bool IsSelected
    {
        set { SetProperty(ref isSelected, value); }
        get { return isSelected; }
    }

    public GroupedCheckListItem(string category, string key, string name, bool isSelected)
    {
        Category = category;
        Key = key;
        Name = name;
        IsSelected = isSelected;
    }
}
}

Solution

Greetings from Syncfusion,

We would like to let you know that SfListView allows to selecting each group and items in the group like a checkBox selection. The checkBox state in the GroupHeader Template will be updated whenever items select and deselect by using converter.

Please refer the below code snippet,

In XAML:

<ContentPage xmlns:syncfusion="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms">
    <syncfusion:SfListView>
        <syncfusion:SfListView.GroupHeaderTemplate>
            <DataTemplate>
                <ViewCell>
                    <ViewCell.View>
                        <Grid BackgroundColor="#d3d3d3">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="30" />
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <Label Text="{Binding Key}" Grid.Column="1" VerticalTextAlignment="Center"/>
                            <Image Grid.Column="2" IsVisible="{Binding SelectionMode, Source={x:Reference listView}}" HorizontalOptions="Center" VerticalOptions="Center" Source="{Binding ., Converter={StaticResource GroupingSelectionConverter}, ConverterParameter={x:Reference listView}}">
                                <Image.GestureRecognizers>
                                    <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
                                </Image.GestureRecognizers>
                            </Image>
                        </Grid>
                    </ViewCell.View>
                </ViewCell>
            </DataTemplate>
        </syncfusion:SfListView.GroupHeaderTemplate>
    </syncfusion:SfListView>
</ContentPage>

In Converter:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
 if (value == null)
return value;

GroupResult groupResult = value as GroupResult;
SfListView list = parameter as SfListView;

var items = new List<MusicInfo>(groupResult.Items.ToList<MusicInfo>());

 if ((items.All(item => item.IsSelected == false)))
 {
for (int i = 0; i < items.Count(); i++)
{
    var item = items[i];
    (item as MusicInfo).IsSelected = false;
    list.SelectedItems.Remove(item);
}
return ImageSource.FromResource("CustomSelection.Images.NotSelected.png");
}

else if ((items.All(item => item.IsSelected == true)))
{
for (int i = 0; i < items.Count(); i++)
{
    var item = items[i];
    (item as MusicInfo).IsSelected = true;
    list.SelectedItems.Add(item);
}
return ImageSource.FromResource("CustomSelection.Images.Selected.png");
}

else
return ImageSource.FromResource("CustomSelection.Images.Intermediate.png");
}

In xaml.cs page,

using Syncfusion.ListView.XForms.Control.Helpers;
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
var image = (sender as Image);
var groupResult = image.BindingContext as GroupResult;

if (groupResult == null)
    return;
var items = groupResult.Items.ToList<MusicInfo>().ToList();

if ((items.All(listItem => listItem.IsSelected == true)))
{
   for (int i = 0; i < items.Count(); i++)
    {
        var item = items[i];
        (item as MusicInfo).IsSelected = false;
    }
}
else if ((items.All(listItem => listItem.IsSelected == false)))
{
    for (int i = 0; i < items.Count(); i++)
    {
        var item = items[i];
        (item as MusicInfo).IsSelected = true;
    }
}
this.RefreshGroupHeader(groupResult);
listView.RefreshView();
}

private void RefreshGroupHeader(GroupResult group)
{
foreach (var item in this.listView.GetVisualContainer().Children)
{
    if (item.BindingContext == group)
    {
        item.BindingContext = null;
        (item as GroupHeaderItem).Content.BindingContext = null;
    }
}
}

Answered By – Suthi

Answer Checked By – Katrina (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published