Grouping in MAUI DataGrid (SfDataGrid)

27 Jun 202424 minutes to read

Grouping in a datagrid refers to the process of organizing and categorizing data based on specific criteria or field values. It allows you to group related records together, creating a hierarchical structure within the datagrid. Each group is identified by its CaptionSummaryRow to get the underlying records in view.

NOTE

  • To update the grouping for the newly added row or column, set the SfDataGrid.View.LiveDataUpdateMode to LiveDataUpdateMode.AllowDataShaping.
  • When BeginInit method is called, it suspends all the updates until EndInit method is called.

Programmatic grouping

The SfDataGrid allows to perform grouping programmatically by adding the GroupColumnDescription object in the SfDataGrid.GroupColumnDescriptions collection. It groups the data based on the GroupColumnDescription object added to this collection.

GroupColumnDescription object holds following two properties:

  • ColumnName: The name of the grouped column.
  • Converter: It accepts an IValueConverter as input that helps to apply custom grouping.

To apply column grouping, please refer to the following code example:

<syncfusion:SfDataGrid x:Name="dataGrid"
                        ItemsSource="{Binding OrderInfoCollection}">
    <syncfusion:SfDataGrid.GroupColumnDescriptions>
        <syncfusion:GroupColumnDescription ColumnName="Name" />
    </syncfusion:SfDataGrid.GroupColumnDescriptions>
</syncfusion:SfDataGrid>
dataGrid.GroupColumnDescriptions.Add (new GroupColumnDescription () {
    ColumnName = "Name",
});

The following screenshot shows the rendered output when grouping is applied:

DataGrid with grouping

MultiGrouping

The SfDataGrid also allows to group the data against one or more columns using the SfDataGrid.GroupingMode property. When the GroupingMode is set as GroupingMode.Multiple, the data is organized into a hierarchical tree structure based on identical values of that column. The multi-grouping feature works similarly to the multi-sorting feature. Initially, the data is grouped according to the first column added in the GroupColumnDescriptions collection. When more columns are added to the GroupColumnDescriptions, the newly added column will be grouped in consideration of the previous group(s). This results in a tree-like hierarchy. Refer to the following code snippet to enable MultiGrouping:

<syncfusion:SfDataGrid  x:Name="dataGrid"
                        ItemsSource="{Binding OrderInfoCollection}"
                        GroupingMode="Multiple">
                        
    <syncfusion:SfDataGrid.GroupColumnDescriptions>
        <syncfusion:GroupColumnDescription ColumnName="Name" />
        <syncfusion:GroupColumnDescription ColumnName="ShipCity" />
    </syncfusion:SfDataGrid.GroupColumnDescriptions>
</syncfusion:SfDataGrid>
this.dataGrid.GroupingMode = GroupingMode.Multiple;
    dataGrid.GroupColumnDescriptions.Add (new GroupColumnDescription () {
    ColumnName = "Name" });
    dataGrid.GroupColumnDescriptions.Add (new GroupColumnDescription () {
    ColumnName = "ShipCity" });

The following screenshot shows the multi-grouping:
DataGrid with multi-column grouping

Customize indent column width

The width of indent column can be customized by the IndentColumnWidth property. The default width of the indent column is 35.

<syncfusion:SfDataGrid  x:Name="dataGrid"
                        ItemsSource="{Binding OrderInfoCollection}"
                        IndentColumnWidth="60" />
this.dataGrid.IndentColumnWidth = 60;

Customize indent column background color

The indent columns can be customized by writing the implicit style for the DataGridIndentCell.

The following code snippet shows how to apply a background color to the indent cell based on the column index:

<ContentPage.Resources>
        <ResourceDictionary>
            <local:CellStyleConverter x:Key="cellStyleConverter" />
            <Style  TargetType="syncfusion:DataGridIndentCell">
                <Setter Property="Background"
                        Value="{Binding Source={RelativeSource Mode=Self}, 
                        Converter={StaticResource 
                        Key=cellStyleConverter}}" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
    <syncfusion:SfDataGrid  x:Name="dataGrid"
                            ItemsSource="{Binding Orders}" />
    <syncfusion:SfDataGrid.GroupColumnDescriptions>
        <syncfusion:GroupColumnDescription ColumnName="Name" />
        <syncfusion:GroupColumnDescription ColumnName="ShipCity" />
    </syncfusion:SfDataGrid.GroupColumnDescriptions>
public class CellStyleConverter : IValueConverter
{
    object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo info)
    {
        var rowType = ((value as DataGridIndentCell)?.Parent as DataGridRow)?.DataRow?.RowType.ToString();
        if (rowType == "HeaderRow")
            return Colors.Bisque;
        else if (rowType == "CaptionCoveredRow")
            return Colors.LightBlue;
        else
            return Colors.PaleVioletRed;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

SfDataGrid with IndentColumnBackgroundColor

Custom grouping

The SfDataGrid allows to group a column based on custom logic when the standard grouping techniques do not meet the requirements.

Using IValueConverter

To achieve this, you need to write a converter that implements IValueConverter with custom grouping logic and assign that converter to the GroupColumnDescription.Converter property.

To set a custom grouping converter for the group description that is added to group the freight column, follow the code example below:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                xmlns:syncfusion="clr-namespace:Syncfusion.Maui.DataGrid;assembly=Syncfusion.Maui.DataGrid"
                xmlns:local="clr-namespace:GroupingUI"
                x:Class="GroupingUI.MainPage">

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:GroupConverter x:Key="groupConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <ContentPage.BindingContext>
        <local:ViewModel x:Name="viewModel" />
    </ContentPage.BindingContext>

    <syncfusion:SfDataGrid x:Name="dataGrid"
                            ItemsSource="{Binding OrderInfoCollection}">

        <syncfusion:SfDataGrid.GroupColumnDescriptions>
            <syncfusion:GroupColumnDescription ColumnName="Freight"
                                                Converter="{StaticResource groupConverter}" />
        </syncfusion:SfDataGrid.GroupColumnDescriptions>
    </syncfusion:SfDataGrid>
</ContentPage>
dataGrid.GroupColumnDescriptions.Add (new GroupColumnDescription () {
    ColumnName = "Freight",
    Converter = new GroupConverter()
});

The following code example illustrates the converter used for applying custom grouping logic.

public class GroupConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var orderInfo = value as OrderInfo;
        if (orderInfo.Freight > 0 && orderInfo.Freight <= 250)
            return "<=250";
        else if (orderInfo.Freight > 250 && orderInfo.Freight <= 500)
            return ">250 & <=500";
        else if (orderInfo.Freight > 500 && orderInfo.Freight <= 750)
            return ">500 & <=750";
        else
            return ">1000";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

Using KeySelector

To achieve this, specify the custom logic in GroupColumnDescription.KeySelector property and column name in GroupColumnDescription.ColumnName property.

In the below example, the Date column is grouped based on weeks.

//Apply the CustomGrouping for Date Column by using KeySelector.
dataGrid.GroupColumnDescriptions.Add(new GroupColumnDescription()
{
    ColumnName = "Date",
    KeySelector = (string ColumnName, object o) =>
        {
            var dt = DateTime.Now;
            var item = (o as SalesByDate).Date;
            var days = (int)Math.Floor((dt - item).TotalDays);
            var dayOfWeek = (int)dt.DayOfWeek;
            var difference = days - dayOfWeek;
            if (days <= dayOfWeek)
            {
                if (days == 0)
                    return "TODAY";
                if (days == 1)
                    return "YESTERDAY";
                return item.Date.DayOfWeek.ToString().ToUpper();
            }
            if (difference > 0 && difference <= 7)
                return "LAST WEEK";
            if (difference > 7 && difference <= 14)
                return "TWO WEEKS AGO";
            if (difference > 14 && difference <= 21)
                return "THREE WEEKS AGO";
            if (dt.Year == item.Date.Year && dt.Month == item.Date.Month)
                return "EARLIER THIS MONTH";
            if (DateTime.Now.AddMonths(-1).Month == item.Date.Month)
                return "LAST MONTH";
            return "OLDER";
        }

});

Sorting the records in the grouped columns

In custom grouping, you can sort all the inner records of each group by setting GroupColumnDescription.SortGroupRecords property as true. This will sort the records based on the GroupColumnDescription.ColumnName property.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                xmlns:syncfusion="clr-namespace:Syncfusion.Maui.DataGrid;assembly=Syncfusion.Maui.DataGrid"
                xmlns:local="clr-namespace:GroupingUI"
                x:Class="GroupingUI.MainPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:GroupOrderNoConverter x:Key="groupOrderNoConverter" />
            <local:PriceConverter x:Key="priceConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <ContentPage.BindingContext>
        <local:ViewModel x:Name="viewModel" />
    </ContentPage.BindingContext>

    <syncfusion:SfDataGrid x:Name="dataGrid"
                            ItemsSource="{Binding OrderInfoCollection}">
        <Syncfusion:SfDataGrid.GroupColumnDescriptions>
            <Syncfusion:GroupColumnDescription ColumnName="OrderID"
                                                Converter="{StaticResource groupOrderNoConverter}"
                                                SortGroupRecords="false" />
            <Syncfusion:GroupColumnDescription ColumnName="Price"
                                                Converter="{StaticResource priceConverter}"
                                                SortGroupRecords="True" />
        </Syncfusion:SfDataGrid.GroupColumnDescriptions>
    </syncfusion:SfDataGrid>
</ContentPage>
datagrid.GroupColumnDescriptions.Add(new GroupColumnDescription()
{
    ColumnName = "OrderID",
    Converter = new GroupOrderNoConverter(),
    SortGroupRecords = false
});
datagrid.GroupColumnDescriptions.Add(new GroupColumnDescription()
{
    ColumnName = "Price",
    Converter = new PriceConverter(),
    SortGroupRecords = true
});  
			
public class GroupOrderNoConvertor : IValueConverter
{
	public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
		var orderInfo = value as OrderInfo;
        if (orderInfo.OrderID < 10)
            return "<10";
        if (orderInfo.OrderID >= 10 && orderInfo.OrderID < 15)
            return "Between 10 To 15";
        if (orderInfo.OrderID >= 15 && orderInfo.OrderID <= 20)
            return "Between 15 To 20";
        else
            return ">20";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

public class PriceConverter : IValueConverter
{
	public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var orderInfo = value as OrderInfo;
        if (orderInfo.Price < 10)
            return "<10";
        if (orderInfo.Price >= 10 && orderInfo.Price < 16)
            return "Between 10 To 15";
        if (orderInfo.Price >= 15 && orderInfo.Price <= 20)
            return "Between 15 To 20";
        return ">20";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

As you can see in the below screenshot, the records are sorted based on the OrderNo column, since SortGroupRecords is set as true and it is not sorted further based on Price column since SortGroupRecords is set as false.

SfDataGrid with SortGroupRecords

Expand groups initially

To expand all groups initially, set the SfDataGrid.AutoExpandGroups to true. While grouping any column, all groups will be in expanded state.

<syncfusion:SfDataGrid  x:Name="dataGrid"
                        AutoExpandGroups="True"
                        AllowGroupExpandCollapse="True"
                        ItemsSource="{Binding OrderInfoCollection}" />
this.dataGrid.AutoExpandGroups = true;
this.dataGrid.AllowGroupExpandCollapse = true;

Expand or collapse the groups

To expand and collapse the groups at runtime, you can simply set the SfDataGrid.AllowGroupExpandCollapse property to true. The groups will be in expanded state by default.

<syncfusion:SfDataGrid  x:Name="dataGrid"
                        AllowGroupExpandCollapse="True"
                        ItemsSource="{Binding OrderInfoCollection}" />
this.dataGrid.AllowGroupExpandCollapse = true;

Expand or collapse all the groups

To expand and collapse the groups programmatically, you can simply invoke the SfDataGrid.ExpandAllGroups and SfDataGrid.CollapseAllGroups methods.

this.dataGrid.ExpandAllGroups();
this.dataGrid.CollapseAllGroups();

Expand or collapse a specific group

To expand and collapse specific groups programmatically, you can simply invoke the SfDataGrid.ExpandGroup and SfDataGrid.CollapseGroup methods.

var group = (dataGrid.View.Groups[0] as Group);

this.dataGrid.ExpandGroup(group);
this.dataGrid.CollapseGroup(group);

DataGrid with group expanding and collapsing

Display based grouping using GroupMode property

By default, column grouping occurs based on the value in the underlying collection thereby creating a new group for each new value of that column. However, a column can also be grouped based on the Display value by setting the DataGridColumn.GroupMode property as Display. In the following code example, set the DataGridColumn.Format property as “#” which displays only the rounded off value in the DataGridCell:

<syncfusion:DataGridTextColumn HeaderText="Order ID"
                                MappingName="OrderID"
                                GroupMode="Display"
                                Format="#" />
DataGridTextColumn orderID = new DataGridTextColumn();
orderID.MappingName = "OrderID";
orderID.GroupMode = Syncfusion.Maui.Data.DataReflectionMode.Display;
orderID.Format = "#";

The following screenshot shows a comparison between two group modes. GroupMode.Value on the Top and GroupMode.Display on the Bottom:
DataGrid with group mode
DataGrid with group mode

Clearing or removing a group

To clear the applied grouping, you can simply remove the particular item from the SfDataGrid.GroupColumnDescriptions collection or clear the entire collection.

//Clearing the Group
dataGrid.GroupColumnDescriptions.Clear();

//Removing the Group based on group item
var groupColumn = dataGrid.GroupColumnDescriptions[1];
dataGrid.GroupColumnDescriptions.Remove(groupColumn);
    
//Removing the Group based on group index
dataGrid.GroupColumnDescriptions.RemoveAt(0);

Events

GroupExpanding

The GroupExpanding event occurs when a group is being expanded.

The DataGridColumnGroupChangingEventArgs of the GroupExpanding event provides information about the expanding group and has the following members:

Syncfusion.Data.Group: Gets the expanded group.

Cancel: Determines whether to cancel group expansion.

Cancel the group expansion by setting DataGridColumnGroupChangingEventArgs.Cancel to true.

this.dataGrid.GroupExpanding += dataGrid_GroupExpanding;

void dataGrid_GroupExpanding(object sender, EventArgs e)
{
    if (e.Group.Key.Equals(1001))    
        e.Cancel = true;    
}

GroupExpanded event

The SfDataGrid.GroupExpanded event occurs after a group is expanded.

The DataGridColumnGroupChangedEventArgs of the GroupExpanded event provides information about the expanded group and it has the following member:

Syncfusion.Data.Group: Retrieves the expanded group.

GroupCollapsing event

The SfDataGrid.GroupCollapsing event occurs when group is being collapsed.

The DataGridColumnGroupChangingEventArgs of the GroupCollapsing event provides the information about the collapsing group and it contains the following member:

Syncfusion.Data.Group: Gets the collapsed group.

Cancel: Determines whether to cancel group collapsing.

Cancel the group is being collapsed by setting the DataGridColumnGroupChangingEventArgs.Cancel to true.

this.dataGrid.GroupCollapsing += dataGrid_GroupCollapsing;

void dataGrid_GroupCollapsing(object sender, EventArgs e)
{
    if (e.Group.Key.Equals(1001))    
        e.Cancel = true;    
}

GroupCollapsed event

The SfDataGrid.GroupCollapsed event occurs after group collapsed.

The DataGridColumnGroupChangedEventArgs of the GroupCollapsed event provides the information about collapsed group and it contains the following member:

Syncfusion.Data.Group: Gets the collapsed group.

Customize grouped column visibility

The visibility of the grouped column can be customized by the SfDataGrid.ShowColumnWhenGrouped property. By default, all the grouped columns are visible. Refer to the following code snippet:

<syncfusion:SfDataGrid  x:Name="dataGrid"
                        AutoGenerateColumns="True"
                        ItemsSource="{Binding OrderInfoCollection}"
                        ShowColumnWhenGrouped="False" />
this.dataGrid.ShowColumnWhenGrouped = false;

Load group icon through template

The SfDataGrid uses an icon to indicate the expand and collapse state of groups. You can personalize the group icon by using the sfDataGrid.GroupExpandCollapseTemplate property. This property allows you to define a custom template that will be displayed in its normal form when the group is expanded, and it will rotate downwards when the group is collapsed. To implement this, refer to the following code snippet:

<syncfusion:SfDataGrid VerticalOptions="FillAndExpand"
                            HorizontalOptions="FillAndExpand"
                            ItemsSource="{Binding OrderInfoCollection}"
                            x:Name="dataGrid"
                            GroupingMode="Multiple"
                            AllowGroupExpandCollapse="True"
                            AutoGenerateColumnsMode="None"
                            >
        <syncfusion:SfDataGrid.GroupExpandCollapseTemplate>
            <DataTemplate>
                <Image Source="downward_icon.png"/>
            </DataTemplate>
        </syncfusion:SfDataGrid.GroupExpandCollapseTemplate>
        
        <syncfusion:SfDataGrid.GroupColumnDescriptions>
            <syncfusion:GroupColumnDescription ColumnName="Name" />
            <syncfusion:GroupColumnDescription ColumnName="ShipCity" />
        </syncfusion:SfDataGrid.GroupColumnDescriptions>
</syncfusion:SfDataGrid>
this.dataGrid.GroupingMode = GroupingMode.Multiple;
this.dataGrid.AllowGroupExpandCollapse = true;
dataGrid.GroupExpandCollapseTemplate = new DataTemplate(() =>
        {
            var imageView1 = new Image()
            {
                Source = "downward_icon.png",
                Aspect = Aspect.AspectFit,
            };
            return imageView1;
        });

DataGrid with template

Load group icon through template selector

When choosing a GroupExpandCollapseTemplate as a DataTemplateSelector, you have the option to supply distinct templates for both the expanded and collapsed states of the group

<syncfusion:SfDataGrid VerticalOptions="FillAndExpand"  
                            HorizontalOptions="FillAndExpand"
                            ItemsSource="{Binding OrderInfoCollection}"
                            x:Name="dataGrid"
                            GroupingMode="Multiple"
                            AllowGroupExpandCollapse="True"
                            AutoGenerateColumnsMode="None"
                            >
        <syncfusion:SfDataGrid.GroupExpandCollapseTemplate>
            <local:ExpandCollapseTemplate ExpandTemplate="{StaticResource ExpandIcon }"
                                            CollapseTemplate="{StaticResource CollapseIcon}" />
        </syncfusion:SfDataGrid.GroupExpandCollapseTemplate>
        
        <syncfusion:SfDataGrid.GroupColumnDescriptions>
            <syncfusion:GroupColumnDescription ColumnName="Name" />
            <syncfusion:GroupColumnDescription ColumnName="ShipCity" />
        </syncfusion:SfDataGrid.GroupColumnDescriptions>
</syncfusion:SfDataGrid>
public class ExpandCollapseTemplate : DataTemplateSelector
{
    public DataTemplate ExpandTemplate { get; set; }

    public DataTemplate CollapseTemplate { get; set; }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        var isExpanded = (item as Group).IsExpanded;
        if (isExpanded)
        {
            return ExpandTemplate;
        }
        else
        {
            return CollapseTemplate;
        }
    }
}

DataGrid with template selector