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
Hi
Could you plz post this solution
Hi,
what 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.
You said you will post a solution which takes care of more than one grouping.I tried your solution but its failing for multiple grouping
Hi,
I had another post
https://leeontech.wordpress.com/2010/03/25/summary-rows-in-datagrid-with-multiple-groups/
Thanks buddy. 🙂
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?
Has anyone tried porting this to WPF. I can’t seems to get it to work?
Hi,
I did not try it myself.
Hi.
Thank you for this sample
I Found one issue after implementing in my module.
If the datagrid having Frozen Columns. The summary row (total columns) are overlaping in the Frozen Columns.
(Frozen Column) (Non-Frozen Column)
Employee ID Employee Name Week1 Week2 Week3 Week4………Week20
—————– ————- —-– ——– ——– —–—- ———
>EmployeeID(224193) (tot)11 7 14 10 9
224193 Gowthaman 5 2 6 8 7
224193 Gowthaman 6 5 8 2 2
After scrolling Horizantally the grid would be like below
(Frozen Column) (Non-Frozen Column)
Employee ID Employee Name Week3 Week4…Week20
———– ————- —– —– ——
(tot)11 7 14 10 9
224193 Gowthaman 6 8 7
224193 Gowthaman 8 2 2
I have set the property AreRowGroupHeadersFrozen=”False”.
Please do the neeful for me.
Thanks,
Gowthaman
Gowthama –
I got the same question as yours.
I set AreRowGroupHeadersFrozen as false and has FrozenColumnCount as 1, when to drop the scroll bar, the header overlaps.
Did you find the answer? If so, could you please share with me?
Thank you very much
Hi Lee,
Thanks for the post first of all. I get the same problem as Brian above, I can’t get hold of the DataGridColumnHeadersPresenter. I tried in the DataGrid_Loaded event but no luck, any suggestions?
Many thanks
Hi,
You could try getting in Layout_Updated event of the datagrid. make sure you have conditional code to get it if it is firsttime. As the Layoutupdated event will be raised many times
To add to the above VisualTreeHelper.GetChildrenCount(element) comes back with 0 children for some reason?!
Looks like the VisualTree has not been created yet. you have to find an event where you are sure that UI has been created after binding data
The above was sorted, my headers stackpanel list was never null…Thanks.
I have another issue now, the styles for the RowGroupHeaders can’t be coded in the xaml of the main page as I don’t know the total number of columns on the datagrid until runtime. I’m thinking of using XamlReader.Load but how do I create the styles, do I need to have all xmlns imports as well?
Thanks
yes, you would need to have all the xmlns:xxx declerations also.
My code looks like this:
return (Style)XamlReader.Load(
@”
…………
I get blank page with this, anything in particular I’m doing wrong?
Thanks
Sorry unable to post the XML here
Hi lee,
u have used “Binding Name” for column value.
I want column header as group header.
how to do??
please help
Hi lee,
u have used “Binding Name” for column value.
I want column header as group header.
how to do??
I want use column header as group header and want to customize it.
please help
Hi,
I am not sure I understand it completely.
Hi
thank you veru much for your article. Very Helpful.
I’ve got another question, When a cell gets updated I need to recalculate a group header. how can it be trigered?
thanks
Alex
Hi,
If the items implement Inotifypropertychanged then we might be able to find the stackpanels and do something similar to what is done in resize methods except that we are going to update the content. I did not try it myself.
Lee,
I’m having trouble downloading the code… Something about internet security I think. I’ve tried setting the downloaded site as a trusted site and still no luck. I’ve also tried “Unblocking” the zip file with no luck.
Can you email this code to me?
Thanks!
-Andrew
Lee,
Please disregard the previous post. I was able to get it working by simply rebuilding the solution.
Thanks for this code, it’s awesome.
-Andrew
Hi Lee –
Very cool – been looking for something similar for a while. Got a question though –
If a particular group spans multiple pages (if you have a datapager present), the sums only total the current page’s view. Do you know of a solution to generate totals for the full grouped collection (including subsequent pages)?
Thank you sir.
Kevin
Hi,
I am afraid not. There are multiple issues in that scenario, the data for the other pages might be loaded or maybe not, at the best case we would be showing the total of the loaded data. I dont think it would be possible to do what you are after particularly if the data is loaded on demand.
Thanks for the quick response..
Hello,
I would like to implement summary row in my solution too, but the problem is that my dataGrid has for ItemsSource a List<Dictionary>, because my data can come from different sources (and it cannot be held in a class structure like data in the above example). Can you help me figure this out?
Hi,
If you are using SL5. you could dynamically create a class, but if you using SL4 Send me a sample project. I can take a look.
How to set the DataGrid so that at the start of groups gonna be rolled-up not expanded as it now ?
you could loop throguh the groups and collapse them one by one. something like showed in this post
http://subodhnpushpak.wordpress.com/2009/04/22/collapsing-groups-in-silverlight-datagrid/
WOW !!! – amazing responsiveness 🙂
A link that you gave me helped a lot !!!
First of all thx for post about summary row and thx for solution 🙂
Hi Lee,
Nice Article !
I am currently experiencing problem with dynamic cols, as the no of cols for datagrid in my case is not fixed and it has been populated on user request.
How do I generate the template style you have created for DataGridRowHeader using Code Behind where I can manage no of cols easily.
Thanks,
Parth
Hi,
It would be difficult to do that in code behind. I dont think this method is sutiable for generalizing the Idea. Not worth the effort.
Lee,
Agreed with your Point of View, however your approach is little rigid in nature and thus setting up no of cols in XAML
in dynamic grid environment is somewhat
not suitable in my case.
Can you please explain how one can use the above solution provided to dynamic cols where no of cols and their respective headers are always based on user selection of the section. I have used ObservableCollection class to populate Dictionary Class and thus always dynamic in case of Cols Headers and Summary.
Regards,
Parth
Hi,
There is a Datasourcecreator class in this post. Which can generate a class at runtime to bind to datagrid
http://blog.bodurov.com/How-to-Bind-Silverlight-DataGrid-From-IEnumerable-of-IDictionary
I have another one question how pass not one, but two values to myConverter ?
I try to do it through XAML but Silverlight doesn’t support IMultiValueConverter.
Or how pass value (property) to myConverter from MainPage class or how create clone of instance of MaingPage without using MEF ?
Hi,
I dont think there is a way to pass multiple parameters. if the other property can be set in XAML. you could add a public property in the converter class and set it when it is instantiated in the resources
Hi Lee,
It was an excellent post, and it solved the issue of having summary columns on the group header. But I am having a issue, Where i am populating 10 columns on the grid, with the freezing columns count of 2. Based upon your example, i can generate the Summary values on the group header, but with the above proposed solution of “AreRowGroupHeadersFrozen=”False” the whole group header moves along with the datagrid scroll bar. if i choose it o True, the group header freezes there. Can you please provide the solution as it is very important….
Hi,
I am not sure I can fix, but if you send me a smple without dependencies, I can take a look
Thanks fro responding, can i have your mail id for sending the file as attachment. Or I just used your example code for testing this. Just populated the Orders and Total repeatedly as 15 columns and made the Freezecolumncount = 2 and AreRowGroupHeadersFrozen=”False” on the datagrid xaml. also had the static width to get the Horizontal scrollbar. It will be much helpful as it is more important…
Thanks
Prakash
Hi, I know its been a very very long time, but was this ever solved?
Thanks
Simon
Superb!
Works like a charm once I set AreRowGroupHeadersFrozen=”False”
Thanks for the hard work putting this together and sharing it.
John
can I have the totals at the bottom instead of the top? (like an invoice)
I think you could modify the groupheaderstyle to move the totals form top of the geoup to after the group is done.