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

76 thoughts on “Summary row in DataGrid

    1. 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

  1. 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 ?

  2. 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 ?

    1. 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

  3. 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?

    1. 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

  4. 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?

      1. 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.

          1. 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)

    1. Hi,

      Found the answer :)

      Set Datagrid’s property AreRowGroupHeadersFrozen=”False”

      This will allow you to scroll the GroupHeader horizontally.

      Thanks
      Arun

    2. 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

  5. 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.

    1. 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;
      }
      });
      }
      }

      1. 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

        1. 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;
          }
          });
          }

  6. 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

    1. 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

  7. 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

    1. 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

    1. 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

  8. 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

  9. My code looks like this:
    return (Style)XamlReader.Load(
    @”

    …………

    I get blank page with this, anything in particular I’m doing wrong?
    Thanks

  10. 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

  11. 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

    1. 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.

  12. 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

  13. 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

  14. 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

    1. 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.

  15. 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?

    1. 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.

  16. 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 :)

  17. 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

    1. 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.

      1. 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

  18. 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 ?

    1. 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

  19. 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….

      1. 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

  20. Superb!
    Works like a charm once I set AreRowGroupHeadersFrozen=”False”

    Thanks for the hard work putting this together and sharing it.
    John

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s