Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

DataGrid | Grouping & Summary Rows

Two features that are really one. GroupBy splits the rows into nested groups; SummaryRows totals a set of rows — and a group is a set of rows, so the same declarations serve the grid’s footer and every group inside it.

GroupBy is a list of columns, outermost first. Grouping is on whenever it has an entry:

@* Blazor - two levels, bound so the page can re-group it *@
<DataGrid TItem="Sale" Items="sales" @bind-GroupBy="groupBy" Groupable="true">
<Columns>
<PropertyColumn Property="x => x.Department" />
<PropertyColumn Property="x => x.Region" />
<PropertyColumn Property="x => x.Rep" />
<PropertyColumn Property="x => x.Revenue" DisplayAs="DataGridColumnFormat.Currency" Decimals="0" />
</Columns>
</DataGrid>
@code {
IReadOnlyList<string> groupBy = ["Department", "Region"];
}
<!-- MAUI - a binding, or inline <x:String> children -->
<shiny:DataGrid ItemsSource="{Binding Sales}" Groupable="True"
GroupBy="{Binding GroupColumns}">
<shiny:DataGridColumn Title="Department" PropertyName="Department" Width="*" />
<shiny:DataGridColumn Title="Region" PropertyName="Region" Width="Auto" />
<shiny:DataGridColumn Title="Rep" PropertyName="Rep" Width="*" />
<shiny:DataGridColumn Title="Revenue" PropertyName="Revenue"
DisplayAs="Currency" Decimals="0" Width="*" />
</shiny:DataGrid>
<!-- ...the same thing, declared inline -->
<shiny:DataGrid.GroupBy>
<x:String>Department</x:String>
<x:String>Region</x:String>
</shiny:DataGrid.GroupBy>
  • Each level indents from its parent, and a header carries the count of every row beneath it — its nested groups included.
  • A group header reads through its column’s own formatting, so it says Revenue: $45,000, never Revenue: 45000.
  • Groups order by key — GroupSortDirection is Ascending by default, and None leaves them in row order (which is what a sort on the grouped column already gives you).
  • GroupsInitiallyExpanded="false" starts them closed. ExpandAllGroups() / CollapseAllGroups() drive them from code, and collapse state is keyed on the group path — a “West” under one department is independent of the “West” under another.
  • GroupHeaderTemplate replaces the header’s content: MAUI’s context is a DataGridGroupHeader, Blazor’s a DataGridGroupInfo<T> (Key, KeyText, Title, Count, Level, Items, IsExpanded).

Paging is skipped while the grid is grouped — a page boundary would slice a group in half.

A summary row is a set of cells, each pointing at a column. A cell either aggregates that column or simply fills its slot with a label — which is what puts the word “Total” in one column and the number in the next. A column with no cell is left blank.

@* Blazor *@
<DataGrid TItem="Sale" Items="sales" @bind-GroupBy="groupBy"
GroupSummaryPlacement="DataGridGroupSummaryPlacement.Footer">
<Columns>...</Columns>
<SummaryRows>
<SummaryRow>
<SummaryCell Column="Rep" Text="Total" Alignment="DataGridCellAlignment.End" />
<SummaryCell Column="Revenue" Aggregate="DataGridAggregateType.Sum" />
</SummaryRow>
<SummaryRow Scope="DataGridSummaryScope.Grid">
<SummaryCell Column="Rep" Text="Average" Alignment="DataGridCellAlignment.End" Bold="false" />
<SummaryCell Column="Revenue" Aggregate="DataGridAggregateType.Average" Bold="false" />
</SummaryRow>
</SummaryRows>
</DataGrid>
<!-- MAUI -->
<shiny:DataGrid.SummaryRows>
<shiny:DataGridSummaryRow>
<shiny:DataGridSummaryCell Column="Rep" Text="Total" Alignment="End" />
<shiny:DataGridSummaryCell Column="Revenue" Aggregate="Sum" />
</shiny:DataGridSummaryRow>
<shiny:DataGridSummaryRow Scope="Grid">
<shiny:DataGridSummaryCell Column="Rep" Text="Average" Alignment="End" Bold="False" />
<shiny:DataGridSummaryCell Column="Revenue" Aggregate="Average" Bold="False" />
</shiny:DataGridSummaryRow>
</shiny:DataGrid.SummaryRows>
Cell property What it does
Column Which column this cell fills — a property name, or a template column’s Title
Text A literal label. Wins over Aggregate
Aggregate Sum, Count, Average, Min, Max, or Custom
CustomAggregate Func<IEnumerable<T>, string> over the rows this summary covers
StringFormat "C0" or "{0:C0}" — both dialects work
Alignment Auto follows the column, so a total lines up under its own values
Bold On by default; off for a secondary row like an average
CellTemplate (MAUI) / child content (Blazor) Owns the slot entirely; the context carries the rows, and the group if there is one

Rows stack in declaration order, which is what a subtotal / tax / total block is.

GroupSummaryPlacement decides where each group’s summary rows sit:

Value Where When it is what you want
Footer (default) After the group’s rows The classic subtotal — it collapses along with the rows it totals
Header Directly under the group’s title Totals stay on screen while the group is collapsed, which turns a collapsed grid into a summary table
Both Both places Long groups, where the total is wanted at each end
None Nowhere Groups stay plain; the grid keeps its own footer

Scope on the row is the other half of the pair: Both (the default) renders it under the grid and inside each group, Grid keeps it out of the groups, Group keeps it out of the footer.

The older per-column Aggregate / FooterTemplate still works and still produces the single footer row it always did — nothing to change:

<shiny:DataGridColumn Title="Revenue" PropertyName="Revenue">
<shiny:DataGridColumn.Aggregate>
<shiny:DataGridAggregateDefinition Type="Sum" Format="C0" />
</shiny:DataGridColumn.Aggregate>
</shiny:DataGridColumn>

Declaring any SummaryRows takes over from it. Reach for them when you want a label beside the number, more than one total row, or different totals for the grid and its groups.