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

Advertisements