Summary row in DataGrid
SummaryRow in DataGrid is one of the frequently asked request. Here is one way we can customize the GroupHeader in Datagrid to create one
By default when we do grouping in the datagrid we get something like this
more often we need something like this.

Here are the steps
1.Add a style for the groupheader, In the above snapshot we see 4 columns, so we assume we need the Groupheader also to have 4 columns
We start by changing the existing GroupHeader Style. In the style, we see a stackPanel. we dont want this
<StackPanel Grid.Column=”3″ Grid.Row=”1″ Orientation=”Horizontal” VerticalAlignment=”Center” Margin=”0,1,0,1″>
<TextBlock x:Name=”PropertyNameElement” Margin=”4,0,0,0″ Visibility=”{TemplateBinding PropertyNameVisibility}”/>
<TextBlock Margin=”4,0,0,0″ Text=”{Binding Name}” />
<TextBlock x:Name=”ItemCountElement” Margin=”4,0,0,0″ Visibility=”{TemplateBinding ItemCountVisibility}”/>
</StackPanel>
we will replace that stackpanel with this, we have to name it something so we can get to this when we want the cells to resize. We will add 4 controls in this case as we have 4 columns in the datagrid. Any control would work here, I went with datagridcell to keep them consitent with colors.
<StackPanel x:Name=”ghsp” Orientation=”Horizontal” Grid.Column=”3″ Grid.Row=”1″ VerticalAlignment=”Center” Margin=”0,1,0,1″>
<my:DataGridCell Content=”{Binding Name}”/>
<my:DataGridCell Content=”"/>
<my:DataGridCell HorizontalContentAlignment=”Right” Content=”{Binding Converter={StaticResource myConverter}, ConverterParameter=Orders}”/>
<my:DataGridCell HorizontalContentAlignment=”Right” Content=”{Binding Converter={StaticResource myConverter}, ConverterParameter=Total,StringFormat=’{0:C}’}”/>
</StackPanel>
1st column is to display the name of the group, 2nd we dont want any summary in that, 3rd,4th will have the sum of orders and Totals
The rest of the xaml is just styling the headers for right alignment, etc
The code in the converter looks like this
public class MyConverter : IValueConverter
{
// This converts the DateTime object to the string to display.
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
CollectionViewGroup cvg = value as CollectionViewGroup;
string param = parameter as string;
if (param == “Orders”)
return cvg.Items.Sum(x => (x as Product).Orders);
else
return cvg.Items.Sum(x => (x as Product).Total);
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
2.Binding the data
products = new List<Product>();
products.Add(new Product { Category = “Beverages”, Name = “Chai”, Orders = 110, Total = 648.00M });
products.Add(new Product { Category = “Beverages”, Name = “Chai”, Orders = 120, Total = 259.20M });
products.Add(new Product { Category = “Condiments”, Name = “Northwoods Cranberry Sauce”, Orders = 120, Total = 216.00M });
products.Add(new Product { Category = “Condiments”, Name = “Grandma’s Boysenberry Spread”, Orders = 130, Total = 1200.00M });
products.Add(new Product { Category = “Dairy Products”, Name = “Queso Cabrales”, Orders = 150, Total = 1400.00M });
products.Add(new Product { Category = “Dairy Products”, Name = “Queso Manchego La Pastora”, Orders = 160, Total = 1280.00M });
products.Add(new Product { Category = “Confections”, Name = “Pavlova”, Orders = 210, Total = 1500.00M });
products.Add(new Product { Category = “Confections”, Name = “Teatime Chocolate Biscuits”, Orders = 114, Total = 800.00M });
PagedCollectionView collectionViewSource = new PagedCollectionView(products);
SortDescription sortDescription = new SortDescription(“Category”, ListSortDirection.Ascending);
collectionViewSource.SortDescriptions.Add(sortDescription);
GroupDescription groupDescription = new PropertyGroupDescription(“Category”);
collectionViewSource.GroupDescriptions.Add(groupDescription);
datagrid1.ItemsSource = collectionViewSource;
The above code is pretty standard, we need to add handlers so that the groupheader cells are resized when header cells of the datagrid
datagrid1.LayoutUpdated += new EventHandler(datagrid1_LayoutUpdated);
datagrid1.UnloadingRowGroup += (s, e) => { Resize(); };
datagrid1.LoadingRowGroup += (s, e) => { refreshUI = true; };
void datagrid1_LayoutUpdated(object sender, EventArgs e)
{
if (refreshUI && headers == null)
{
dghc = datagrid1.GetChildrenByType<DataGridColumnHeadersPresenter>().FirstOrDefault();
foreach (DataGridColumnHeader dgch in dghc.Children)
{
dgch.SizeChanged += (s, args) => { Resize(); };
}
}
if (refreshUI)
Resize();
refreshUI = false;
}
void Resize()
{
headers = datagrid1.GetChildrenByType<StackPanel>().Where(x => x.Name == “ghsp”).ToList();
headers.ForEach(x =>
{
for (int i = 1; i < dghc.Children.Count – 2; i++)
(x.Children[i - 1] as DataGridCell).Width = dghc.Children[i].RenderSize.Width;
});
}
what we are doing in the event handlers is, when the groups are loading/unloading or the datagrid is scrolled we adjust the width of the cells in the groupheader
3.If you look at the how the columns are defined in the datagrid
<my:DataGrid.Columns>
<my:DataGridTextColumn Header=”Category” Binding=”{Binding Category}” Width=”Auto”/>
<my:DataGridTextColumn Header=”Name” Binding=”{Binding Name}” Width=”*”/>
<my:DataGridTextColumn HeaderStyle=”{StaticResource headerStyle}” CellStyle=”{StaticResource cellStyle}” Header=”Orders” Binding=”{Binding Orders}” Width=”Auto” />
<my:DataGridTextColumn HeaderStyle=”{StaticResource headerStyle}” CellStyle=”{StaticResource cellStyle}” Header=”Total” Binding=”{Binding Total}” Width=”150″/>
<my:DataGridTemplateColumn Header=”" Width=”Auto” Visibility=”Collapsed”>
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>
</my:DataGrid.Columns>
There is an extra column at the end and Visibility=”Collapsed” is set. This is a hack as I could not get the last column to align right. If you dont have a summary column at the end. you dont need that.
you can download the code here

Hi,
nice post. This is what I would need in Silverlight 3. Does it work in Silverlight 3?
Tomas
It should work. As far as I know there isn’t anything specific to SL4. let me know if you have issues
Is there any way to style the DataGridRowGroupHeader to have vertial alignment = bottom?
Hi,
what is the size of the header, do you have screenshot of what it might look like?
Something like http://cid-289eaf995528b9fd.skydrive.live.com/self.aspx/Public/bottom.png
would the convertor be more generic if had
return cvg.Items.Sum(x => (Decimal.Parse((x.GetType().GetProperty(param).GetValue(x, null)).ToString())));
you should be able to set the height (see the edits in Italics)
<Style TargetType=”my:DataGridRowGroupHeader”>
<Setter Property=”Cursor” Value=”Arrow” />
<Setter Property=”IsTabStop” Value=”False” />
<Setter Property=”Background” Value=”#FFE4E8EA” />
<Setter Property=”Height” Value=”30″/>
….
and also in
<StackPanel x:Name=”ghsp” Orientation=”Horizontal” Grid.Column=”3″ Grid.Row=”1″ VerticalAlignment=”Bottom” Margin=”0,1,0,1″>
<StackPanel.Resources>
<Style TargetType=”my:DataGridCell”>
<Setter Property=”Height” Value=”30″/> <Setter Property=”Background” Value=”Transparent” />
<Setter Property=”HorizontalContentAlignment” Value=”Stretch” />
<Setter Property=”VerticalContentAlignment” Value=”Bottom” />
<Setter Property=”IsTabStop” Value=”False” />
<Setter Property=”FontWeight” Value=”Black”/>
…
you could also use generic converter like you said if they are all doing the same type of aggregation
I still cant manage to get the group headers below the rows like in my screenshot, but i managed to get i working with autogeneration using the dataannotations for grouping on the source object http://cid-289eaf995528b9fd.skydrive.live.com/self.aspx/Public/Auto-groupheaders.zip
I love your sample, it is really useful
I enjoy your site and I have bookmarked it, Kind Regards
Bernice Johanson
Gucci Shoes
Gucci Sandals
Hi ! Thanks for this trick, it’ll help me a lot.
I have a question about the RowHeader Category binding.
We can see in MainPage.xaml line 172 “” but the Title of the row is the Category and not the Name ! I don’t understand.
I succeded in adding the sum but not the RowHeader Title.
I try “” but i doesn’t work.
Can you help me ?
Hi,
I am not sure I understand. do you have a sample that I can look at?
Hi, your customization of the DataGridRowGroupHeader is very interesting. I already did that part but maybe you could help me by going a step further. I’m trying to figure out how can I get the converter to be recalled after a cell has been edited. My problem is that I need to have editable TextBox instead of TextBlock inside my grid, but when I change a value, the converter isn’t recalled on the summary row.
Any idea ?
The summary rows are built when the datagrid’s ItemsSource is set or when the rows come into view not when you change a value in the textbox. you might have to hook a handler or something and refresh bindings on the control
Excellent blog post! This works fine when grouping on one column but breaks if I’m using two.
- Beverages
– Beer
— Budweiser
— Carlsberg
Any idea on how to solve that?
Dan,
I did not have multiple groups in mind at that time. I will see if we can extend the same approach to work with multiple groups and update the post
Dan,
We should be able to extend this using 1 style for each Groupheader. I will post a solution
The scrolling does not scroll the group header cells. I have a handler on the VerticalScrollBar Scroll event and I call Resize(). The group header totals just sit there. Is there any way to make them scroll with the rest of the columns?
could you send me the sample project?
It’s part of a large solution. I meant to state “HorizontalScrollBar”. Just make your sample data grid have a MaxWidth of 300 or something to see what I am talking about. The columns scroll but the header data cells stay stationary.
Hi,
looks like if we have H. scrollbar, I see the same issues. Not sure how to get around that.
Hi Lee,
Thanks for the artcle. It was really very helpful. did you find how can we make Group Header scrollable horizontally? (Ref Steve Mann’s question)
Hi,
Found the answer
Set Datagrid’s property AreRowGroupHeadersFrozen=”False”
This will allow you to scroll the GroupHeader horizontally.
Thanks
Arun
Hi,
Thanks for posting the answer, that was a nice find.
Hey,
So I downloaded your sample and it works. I tried copying the code MainPage.xaml and MainPage.xaml.cs into a TabItem in my app and now it goes into an infinite loop getting null pointer exceptions datagrid1_LayoutUpdated. For some reason the line that gets the DataGridColumnHeadersPresenter always returns null.
dghc = datagrid1.GetChildrenByType().FirstOrDefault();
Any idea why this might happen? I can’t for the life of me figure out what the difference is between the two environments.
Well, I still don’t know what the difference is between the environments, but I put in some null checking and it works. Here is the revised code:
void datagrid1_LayoutUpdated(object sender, EventArgs e)
{
if (refreshUI && headers == null)
{
dghc = datagrid1.GetChildrenByType().FirstOrDefault();
if (dghc != null)
{
foreach (DataGridColumnHeader dgch in dghc.Children)
{
dgch.SizeChanged += (s, args) => { Resize(); };
}
}
}
if (refreshUI)
Resize();
refreshUI = false;
}
void Resize()
{
headers = datagrid1.GetChildrenByType().Where(x => x.Name == “ghsp”).ToList();
if (headers != null)
{
headers.ForEach(x =>
{
if (dghc != null)
{
for (int i = 1; i < dghc.Children.Count – 2; i++)
(x.Children[i - 1] as DataGridCell).Width = dghc.Children[i].RenderSize.Width;
}
});
}
}
Hi,
Until the layout happens we cannot get the items in the VisualTree. Have to be careful to make sure we dont have any code in LayoutUpdated event handler that causes recursion as it will be called many times
Actually disregard that last comment about the null checking fixing it. dghc never gets a value ever. Apparently inside GetChildrenByType, the call to VisualTreeHelper.GetChildrenCount(element) always returns zero. That null check that I put in there prevents the null pointer exception, but because dghc never has a value the code inside the null check never gets called. End effect is that the layout of the group header is never updated.
I couldn’t figure out how to get a handle to presenter after spending a bunch of hours on this, so I implemented something slightly different. I wish I could get the event based method to work because it would be much more efficient, but here are the changes:
bool refreshUI = true;
List headers = null;
DataGridColumnHeadersPresenter dghc = null;
List columnWidths = new List();
void datagrid1_LayoutUpdated(object sender, EventArgs e)
{
if (columnWidths.Count == 0 || DidColumnWidthChange())
{
columnWidths.Clear();
foreach (DataGridColumn column in datagrid1.Columns)
{
columnWidths.Add(column.ActualWidth);
}
Resize();
}
}
private bool DidColumnWidthChange()
{
for (int i = 0; i < datagrid1.Columns.Count(); i++)
{
if (datagrid1.Columns.ElementAt(i).ActualWidth != columnWidths.ElementAt(i))
return true;
}
return false;
}
void Resize()
{
headers = datagrid1.GetChildrenByType().Where(x => x.Name == “ghsp”).ToList();
headers.ForEach(x =>
{
for (int i = 0; i < x.Children.Count(); i++)
{
DataGridCell groupHeader = x.Children[i] as DataGridCell;
DataGridColumn column = datagrid1.Columns[i] as DataGridColumn;
groupHeader.Width = column.Width.DesiredValue;
}
});
}
did you try datagrid loaded event?