Skip to main content

How to Group Rows in DevExpress Data Grid for .NET MAUI

  • 12 minutes to read

DataGridView allows you to group data by a single column.

Groups of data rows (rows that have identical values in a specified column) are separated with group rows that a user can tap to expand or collapse groups. You can also display summary information about groups (for example, the sum of values, the number of records, the minimum or maximum value) within group rows.

The DataGridView uses unique integer indexes called row handles to identify group rows and their nested rows:

Data Grid - Grouping

Group headers (also called group rows) display the value (or value range), which is the same for all rows in this group. You can format the group value and display group summaries (the sum of values, number of rows, minimum/maximum value, and so on). Users can collapse and expand groups.

Group Rows in Markup

Enable a column’s IsGrouped option to group grid rows by values in this column. Note that the DataGridView component cannot group rows by multiple data fields.

The following example shows how to group orders by purchase date:

DevExpress MAUI DataGridView - Rows grouped by dates

<dxg:DataGridView ItemsSource="{Binding Orders}">
    <dxg:DataGridView.Columns>
         <!-- ... -->
        <dxg:DateColumn FieldName="Date" DisplayFormat="d"  
                        IsGrouped="true" GroupInterval="Date"/>
         <!-- ... -->
    </dxg:DataGridView.Columns>
</dxg:DataGridView>

Group Rows by Value Ranges

Initially, the DataGridView groups strings and dates by the exact value, but you can group rows by value ranges. Set a column’s GroupInterval property to one of the following modes to specify the value range:

Alphabetical
Data rows are grouped alphabetically instead of by exact values in the group column.
DateMonth
Data rows are grouped by months.
DateYear
Data rows are grouped by years.
DateRange
Data rows are grouped by predefined ranges (“Next Month”, “Earlier this Month”, and so on) instead of exact dates.
DisplayText
Data rows are grouped by the text displayed in cells instead of data field values.

The code below groups data rows by predefined value ranges:

DevExpress MAUI DataGridView - Rows grouped by date ranges

<dxg:DataGridView ItemsSource="{Binding Orders}">
    <dxg:DataGridView.Columns>
         <!-- ... -->
        <dxg:DateColumn x:Name="dateColumn1"
                        FieldName="Date"
                        DisplayFormat="d"  
                        IsGrouped="true"
                        GroupInterval="DateRange"/>
         <!-- ... -->
    </dxg:DataGridView.Columns>
</dxg:DataGridView>

Manage Group Column Visibility

The DataGridView hides the column whose values are used to group rows. Use the ShowGroupedColumns property to specify whether this column is displayed.

The following example displays the Date column used to group grid rows:

DevExpress MAUI DataGridView - The group column is visible

<dxg:DataGridView x:Name="grid" 
                  ItemsSource="{Binding Orders}" 
                  ShowGroupedColumns="True">
    <dxg:DataGridView.Columns>
        <!--...-->
        <dxg:DateColumn FieldName="Date" DisplayFormat="d" 
                        IsGrouped="True" 
                        GroupInterval="Date"/>
        <!--...-->
    </dxg:DataGridView.Columns>
</dxg:DataGridView>

Group Rows in Code

Call the DataGridView.GroupBy method to group data rows in code. The method accepts a data field name or GridColumn object. The code below shows how to group by the Date data field.

<Button Grid.Row="0" Clicked="Button_Clicked" Text="Group Rows"/>
<dxg:DataGridView x:Name="grid" ItemsSource="{Binding Orders} Grid.Row="1">
    <dxg:DataGridView.Columns>
        <dxg:DateColumn FieldName="Date" DisplayFormat="d" x:Name="DateColumn" GroupInterval="Date"/>
        <!--...-->
    </dxg:DataGridView.Columns>
</dxg:DataGridView>
private void Button_Clicked(object sender, EventArgs e) {
    // If you declare columns in markup,
    // you can pass a column object to this method.
    grid.GroupBy(DateColumn);

    // This code line groups orders by date.
    // You can use this overload if columns are generated automatically. 
    grid.GroupBy("Date");
}

Call the ClearGrouping method to ungroup rows in the grid. Use the DataGridView.IsGrouped property to check whether grid data rows are grouped.

Use the GroupCount property to get the number of resulting groups (the number of resulting group rows).

To check whether a row is a group row, call the IsGroupRow method.

Use the following API to obtain information about nested rows of a group:

GetParentRowHandle
Returns the group row handle that identifies the group to which the specified data row belongs.
GetChildRowCount
Returns the number of data rows in a specific group.
GetChildRowHandle
Returns the handle of a row at the specified position within the specified group.

Collapse and Expand Groups

Users can collapse and expand grouped rows. The initial state depends on the AutoExpandAllGroups option. To prevent users from collapsing row groups, set the DataGridView.AllowGroupCollapse property to false.

Collapse and Expand Groups in Code

Use the following methods to collapse and expand row groups in code:

CollapseAllGroups
Collapses all groups of rows.
ExpandAllGroups
Expands all groups of rows.
CollapseGroupRow
Collapses the specified group of rows.
ExpandGroupRow
Expands the specified group of rows.

The IsGroupCollapsed method allows you to determine whether a specific group is collapsed.

Respond to User Actions when Collapsing and Expanding Groups

The DataGridView ships with the following events that fire before/after a group is collapsed/expanded. You can handle these events to cancel a collapse/expand operation, display a notification, and so on:

GroupCollapsing
Occurs before a group of rows is collapsed.
GroupCollapsed
Occurs after a group of rows has been collapsed.
GroupExpanding
Occurs before a group of rows is expanded.
GroupExpanded
Occurs after a group of rows has been expanded.

The example below shows how to prevent the “Today” group in the grid from being collapsed. To obtain the group value, the DataGridView.GetGroupValue method is used.

<dxg:DataGridView x:Name="grid" ItemsSource="{Binding Orders}" 
                  GroupCollapsing="grid_GroupCollapsing">
    <dxg:DataGridView.Columns>
        <!--...-->
        <dxg:DateColumn x:Name="DateColumn"
                        FieldName="Date"
                        DisplayFormat="d"
                        GroupInterval="DateRange" 
                        IsGrouped="True"/>
        <!--...-->
    </dxg:DataGridView.Columns>
</dxg:DataGridView>
private void grid_GroupCollapsing(object sender, RowAllowEventArgs e) { 
    string groupValue = (string)grid.GetGroupValue(e.RowHandle);
    if (groupValue == "Today") e.Allow = false;   
}

Drag Grouped Rows

You can drag a row between groups. If you drop a row onto a different group, the control updates the row’s data accordingly.

To allow drag and drop operations, enable the DataGridView.AllowDragDropRows property. Enable the DataGridView.AllowDragDropSortedRows option so users can drag rows to a new position.

For more information, refer to the following section: Drag and Drop Rows.

Obtain Group Value

The value used to arrange multiple data rows into a single group is displayed in the group header (group row). The displayed value can be the actual data value or a value formatted according to the DisplayFormat expression (refer to SortMode for more information).

You can use the following methods to obtain a value displayed in a specific group’s header:

GetGroupValue
Returns the actual data value.
GetGroupDisplayText
Returns the actual or formatted value depending on the SortMode option.

Group Rows

Show Group Summaries

To display a group summary, add a GridColumnSummary object to the grid’s GroupSummaries collection and specify its Type and FieldName properties.

You can display one or more summaries in a group header. The following aggregate functions are available:

Average
The average value of a group of rows or an entire column.
Count
The number of records in a group or an entire column.
Min
The minimum value in a group of rows or an entire column.
Max
The maximum value in a group of rows or an entire column.
Sum
The sum of values in a group of rows or an entire column.

The code below shows how to display the sum of values in a specific column for each group:

DevExpress MAUI DataGridView - Group summaries

<dxg:DataGridView x:Name="grid" ItemsSource="{Binding Orders}">
    <dxg:DataGridView.GroupSummaries>
        <dxg:GridColumnSummary FieldName="Product.Name" Type="Count"/>
        <dxg:GridColumnSummary FieldName="Product.UnitPrice" Type="Average"/>
        <dxg:GridColumnSummary FieldName="Total" Type="Sum"/>
    </dxg:DataGridView.GroupSummaries>
    <dxg:DataGridView.Columns>
        <dxg:DateColumn FieldName="Date" DisplayFormat="d" x:Name="DateColumn" 
                        GroupInterval="DateRange" IsGrouped="True"/>
        <dxg:TextColumn FieldName="Product.Name" Caption="Product" />
        <dxg:NumberColumn FieldName="Product.UnitPrice" Caption="Price" DisplayFormat="C0"/>
        <dxg:NumberColumn FieldName="Quantity" />
        <dxg:NumberColumn FieldName="Total" 
                          UnboundType="Integer" UnboundExpression="[Quantity] * [Product.UnitPrice]" 
                          IsReadOnly="True" DisplayFormat="C0" />
        <dxg:CheckBoxColumn FieldName="Shipped" />
    </dxg:DataGridView.Columns>
</dxg:DataGridView>

To obtain the resulting summary for a specific column and group, call the DataGridView.GetGroupSummaryValue method.

The DataGridView also allows you to calculate TotalSummaries for columns.

For more information about summaries, refer to the following help topic: Data Summaries.

Format Group Summaries

The default summary displays the field name and the aggregate function followed by the calculated value. The value is formatted according to the GridColumn.DisplayFormat setting. The table below contains the default formats per aggregate function:

Summary Type Total Summary Format Group Summary Format
Average AVG={0:columnDisplayFormat} summaryFieldName: AVG={0:columnDisplayFormat}
Count {0} {0}
Min MIN={0:columnDisplayFormat} summaryFieldName: MIN={0:columnDisplayFormat}
Max MAX={0:columnDisplayFormat} summaryFieldName: MAX={0:columnDisplayFormat}
Sum SUM={0:columnDisplayFormat} summaryFieldName: SUM={0:columnDisplayFormat}

Use the GridColumnSummary.DisplayFormat property to apply a custom format to the summary display text.

DevExpress MAUI DataGridView - Formatted summary display text

<dxg:DataGridView.GroupSummaries>
    <dxg:GridColumnSummary FieldName="Product.UnitPrice" 
                           Type="Average" 
                           DisplayFormat="(Average) = {0:c2}"/>
</dxg:DataGridView.GroupSummaries>

In XAML, insert empty brackets into the beginning of a format string if it starts with a placeholder. Refer to the following page for more information: {} Escape sequence / markup extension. For example: “{}{0:c2}”.

Create Custom Summaries

Handle the DataGridView.CustomSummary event to compute summaries based on a custom rule.

The following example uses predefined aggregate functions (Max and Sum) and a custom rule to calculate group and total summaries for a grid that displays orders grouped by dates.

Data Grid Summaries - Example

Set up the following summaries:

  • A group summary to display the maximum Total value for each group of orders.
  • A total summary to calculate the sum of values in the Total column.
  • A custom total summary to count the number of orders with the false value in the Shipped column.
<dxg:DataGridView x:Name="grid" ItemsSource="{Binding Orders}" 
                  CustomSummary="grid_CalculateCustomSummary">
    <!-- ... -->
    <dxg:DataGridView.GroupSummaries>
        <dxg:GridColumnSummary FieldName="Total" Type="Max"/>
    </dxg:DataGridView.GroupSummaries>

    <dxg:DataGridView.TotalSummaries>
        <dxg:GridColumnSummary FieldName="Total" Type="Sum" 
                               DisplayFormat="Total: {0:C0}"/>
        <dxg:GridColumnSummary FieldName="Shipped" Type="Custom" 
                               DisplayFormat="Not Shipped: {0}"/>
    </dxg:DataGridView.TotalSummaries>
</dxg:DataGridView>
int count;
// ...

void grid_CalculateCustomSummary(object sender, DevExpress.Data.CustomSummaryEventArgs e) {
    if (e.FieldName.ToString () == "Shipped")
        if (e.IsTotalSummary){
            if (e.SummaryProcess == CustomSummaryProcess.Start) {
                count = 0;
            }
            if (e.SummaryProcess == CustomSummaryProcess.Calculate) {
                if (!(bool)e.FieldValue)
                    count++;
                e.TotalValue = count;
            }
        }
}

Create a Custom Grouping Rule

If you group data, the control always sorts the data first. In many cases, sorting and grouping operations arrange rows in the same order. For example, you may want to group date-time values by decades. The control sorts the records in a regular ascending or descending order. It can then separate that sorted data into groups. You need to handle only the DataGridView.CustomGroup event to specify that if values belong to the same decade, then they belong to the same group.

DevExpress MAUI Grid - A custom grouping algorithm is applied after the sorting rows by the date-time column in ascending order

You may also need to group data in such a way, that the order of records is different for grouped and sorted data. For example, you may want to sort numeric values in regular ascending or descending order, but group them based on whether values are odd or even. In that case, you need to handle the DataGridView.CustomGroup event to specify if two values belong in the same group (both odd or both even). You also need to handle the DataGridView.CustomSort event so you can arrange values before they are broken down into groups (first display odd values then even, or vice versa). In this case, make sure that custom sorting is only applied if the data is grouped.

DevExpress MAUI Grid - Custom sorting and grouping are applied to numbers

You can also apply a custom sorting rule before you group data when you do not want to sort rows in a decade:

DevExpress MAUI Grid - A custom grouping algorithm is applied after the sorting rows by the date-time column in ascending order

To apply a custom grouping rule to a grid column, set the column’s SortMode property to Custom and then handle the grid’s CustomGroup event.

The event fires each time the control needs to compare two rows.

The CustomGroupEventArgs.Column property returns the column that is being processed. The CustomGroupEventArgs.SourceIndex1 and CustomGroupEventArgs.SourceIndex2 properties return the indexes of the rows that are compared. The cell values used to compare rows are specified by the CustomGroupEventArgs.Value1 and CustomGroupEventArgs.Value2 properties.

When handling the GridView.CustomGroup event, use the following values to set the CustomGroupEventArgs.GroupsEqual property as the result of the custom comparison operation:

  • True - to indicate that the rows should be combined into the same group.
  • False - to indicate that the rows should be placed within different groups.

The following example groups integers in the numeric grid column by parity:

DevExpress MAUI Grid - Custom Groups

To group rows as in the grid above, follow the steps below:

  1. Handle the DataGridView.CustomSort event to sort values in a custom manner. If users sort data in ascending order, the grid displays even numbers first, then odd numbers. Set the DataGridView.SortMode property to Custom to apply the custom sort algorithm.

  2. Enable the GridColumn.IsGrouped property for a column whose values you want to use to group rows. Handle the DataGridView.CustomGroup event to apply a custom grouping algorithm. This example divides column values into two groups of even and odd values. To show the group column, set the DataGridView.ShowGroupedColumns property to True.

  3. Handle the DataGridView.CustomGroupDisplayText event to format group captions based on a condition. In this example, the display text is set based on the Value property value. The Value property contains the value of the first row in the group.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:dxg="clr-namespace:DevExpress.Maui.DataGrid;assembly=DevExpress.Maui.DataGrid"
             xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls"
             xmlns:local="clr-namespace:CustomGroups" 
             x:Class="CustomGroups.MainPage">
    <ContentPage.BindingContext>
        <local:GridViewModel/>
    </ContentPage.BindingContext>
    <dxg:DataGridView x:Name="grid" ItemsSource="{Binding Data}" 
                      CustomSort="grid_CustomColumnSort" 
                      CustomGroup="grid_CustomColumnGroup" 
                      CustomGroupDisplayText="grid_CustomGroupDisplayText" 
                      ShowGroupedColumns="True" SelectionMode="None">
        <dxg:DataGridView.Columns>
            <dxg:NumberColumn FieldName="Number" Caption="Number" 
                              SortMode="Custom" SortOrder="Ascending" 
                              IsGrouped="True" HorizontalContentAlignment="Start" />
        </dxg:DataGridView.Columns>
    </dxg:DataGridView>
</ContentPage>
using DevExpress.Maui.DataGrid;

namespace DXMauiApp1 {
    public partial class MainPage : ContentPage {
        public MainPage() {
            InitializeComponent();
        }

        private void grid_CustomColumnSort(object sender, CustomSortEventArgs e) {
            if (e.Column.FieldName != "Number") return;
            var v1 = (int)e.Value1;
            var v2 = (int)e.Value2;
            var even1 = v1 % 2 == 0;
            var even2 = v2 % 2 == 0;
            if (even1 ^ even2)
                e.Result = even1 ? -1 : 1;
            else e.Result = Comparer<int>.Default.Compare(v1, v2);
        }

        private void grid_CustomColumnGroup(object sender, CustomGroupEventArgs e) {
            if (e.Column.FieldName != "Number") return;
            var v1 = (int)e.Value1;
            var v2 = (int)e.Value2;
            var isEven1 = v1 % 2 == 0;
            var isEven2 = v2 % 2 == 0;
            e.GroupsEqual = !(isEven1 ^ isEven2);
        }

        private void grid_CustomGroupDisplayText(object sender, CustomGroupDisplayTextEventArgs e) {
            if (e.Column.FieldName != "Number") return;
            e.DisplayText = ((int)e.Value % 2 == 0 ? "Even" : "Odd");
        }
    }

    public class GridViewModel {
        int itemCount = 10;
        Random rnd = new Random();
        public ObservableCollection<DataEntry> Data { get; private set; }
        public GridViewModel() {
            Data = new ObservableCollection<DataEntry>();
            for (int i = 0; i < itemCount; i++) {
                Data.Add(new DataEntry(rnd.Next(0, 100)));
            }
        }
        public class DataEntry {
            public int Number { get; private set; }
            public DataEntry(int number) {
                Number = number;
            }
        }
    }
}

Enable Live Data Recalculation

Enable the DataGridView.AllowLiveDataShaping property to recalculate DataGridView values and refresh it when users filter, group, and sort data.

See Also