WPF Datagrid Totals

Apr 18, 2009 at 7:20 PM
Edited Apr 18, 2009 at 11:30 PM
Is it possible to show and update a total row in the WPF datagrid?  I just need to be able to update the total as values in the grid change.  Thanks in advance.
Coordinator
Apr 22, 2009 at 3:15 AM
You can listen for cell changes through CellEditEnding and make the calculations then in the ViewModel.  If this isn't enough to get you started let me know.
Apr 22, 2009 at 4:39 AM

Thanks so much for your response.

My situation is two-fold:  how to even show a total row, then how to update it dynamically.

The grid is setup where each column consists of a stack panel with 3 fields in it that can be check boxes or text boxes.  I actually build this in the code and bind it to a dataset that has the appropriate boolean or numeric values.  This works fine.

It was my hope that I could put the totals in the column header, but the code that I use to get to the header just is not working as expected so I cannot even get the header updated to put in the captions I want.

With regard to using CellEditEnding - it is just not firing.  The only event I can get to fire is the PreviewTextInput.

So, I'm back to:  how to show the total row, then how to update it dynamically?

Thanks again.

Apr 22, 2009 at 1:17 PM
Hi,

Even I am searching for Footer Row solution.

Can someone guide me.
Aug 10, 2009 at 9:18 AM

I'm also looking for an example of adding a footer row.

I did find something over here: http://thibaud60.blogspot.com/2008/10/wpftoolkit-datagrid-with-footer-and.html
but I really want a solution that doesn't modify the toolkit source.

Aug 11, 2009 at 8:43 AM
Edited Aug 11, 2009 at 8:47 AM

Here is a sample of what I got from Vincent.  It uses the MVVM approach.  This has really worked well for me.

The idea is to base the grid on the view-model.  Then every piece of data in the view model has a Property Changed event associated with it so you know whenever a grid cell changes.  You can then update the totals.

I don't see how to attach a zip file here so here's the code extracts.

First the XAML file.

<Window x:Class="DataGridTotalsSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
    Title="Window1" Height="500" Width="500">
    <StackPanel>
        <Button Content="Debug" Click="Button_Click"/>
       
        <toolkit:DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" CanUserAddRows="False">
            <toolkit:DataGrid.Columns>
                <toolkit:DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                <toolkit:DataGridTemplateColumn>
                    <toolkit:DataGridTemplateColumn.Header>
                        <Grid Margin="5">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition></ColumnDefinition>
                                <ColumnDefinition></ColumnDefinition>
                                <ColumnDefinition></ColumnDefinition>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Margin="5"
                   Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGrid}},
                                Path=DataContext.TotalExisting, StringFormat='Total Existing: {0}'}"/>
                            <TextBlock Grid.Column="1" Margin="5"
                                       Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGrid}}, Path=DataContext.TotalPlanned, StringFormat='Total Planned: {0}'}"/>
                            <TextBlock Grid.Column="2" Margin="5" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGrid}}, Path=DataContext.TotalAchieved, StringFormat='Total Achieved: {0}'}"/>
                        </Grid>
                    </toolkit:DataGridTemplateColumn.Header>                   
                    <toolkit:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <CheckBox IsChecked="{Binding Path=IsExisting, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="IsExisting" />
                                <CheckBox IsChecked="{Binding Path=IsPlanned, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="IsPlanned" />
                                <CheckBox IsChecked="{Binding Path=IsAchieved, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="IsAchieved" />
                            </StackPanel>
                        </DataTemplate>
                    </toolkit:DataGridTemplateColumn.CellTemplate>
                </toolkit:DataGridTemplateColumn>
            </toolkit:DataGrid.Columns>
        </toolkit:DataGrid>
    </StackPanel>
</Window>

 

Now the code-behind.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace DataGridTotalsSample
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private ViewModel _vm;

        public Window1()
        {
            InitializeComponent();

            _vm = new ViewModel();
            this.DataContext = _vm;
            System.Diagnostics.Trace.WriteLine("Window1.");

        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {

        }
    }

    public class ViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<ItemData> _items;
        private int _totalExisting;
        private int _totalPlanned;
        private int _totalAchieved;

        public ViewModel()
        {
            Items = new ObservableCollection<ItemData>
            {
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" },
                new ItemData { Name = "item1" }
            };
            System.Diagnostics.Trace.WriteLine("ViewModel.");
        }

        #region Properties

        public ObservableCollection<ItemData> Items
        {
            get {
                System.Diagnostics.Trace.WriteLine("Items Get");
                return _items; }
            set
            {
            System.Diagnostics.Trace.WriteLine("Items Set");
                if (_items != null)
                {
                    foreach (var item in Items)
                    {
                        item.PropertyChanged -= OnItemPropertyChanged;                       
                    }
                    _items.CollectionChanged -= OnItemsCollectionChanged;
                }

                _items = value;

                _items.CollectionChanged += OnItemsCollectionChanged;
                foreach (var item in Items)
                {
                    item.PropertyChanged += OnItemPropertyChanged;
                }

                OnPropertyChanged("Items");
            }
       }

        public int TotalExisting
        {
            get {
                System.Diagnostics.Trace.WriteLine("TotalExisting Get");
                return _totalExisting; }
            set
            {
                System.Diagnostics.Trace.WriteLine("TotalExisting Set");
                _totalExisting = value;
                OnPropertyChanged("TotalExisting");
            }
        }

        public int TotalPlanned
        {
            get { return _totalPlanned; }
            set
            {
                _totalPlanned = value;
                OnPropertyChanged("TotalPlanned");
            }
        }

        public int TotalAchieved
        {
            get { return _totalAchieved; }
            set
            {
                _totalAchieved = value;
                OnPropertyChanged("TotalAchieved");
            }
        }

        #endregion Properties

        #region Helpers

        private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("OnItemPropertyChanged");
            CalculateTotals();
        }

        private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("OnItemsCollectionCHanged");
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (ItemData item in e.NewItems)
                {
                    item.PropertyChanged += OnItemPropertyChanged;
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove ||
                     e.Action == NotifyCollectionChangedAction.Replace ||
                     e.Action == NotifyCollectionChangedAction.Reset)
            {
                foreach (ItemData item in e.OldItems)
                {
                    item.PropertyChanged -= OnItemPropertyChanged;
                }
            }
        }

        private void CalculateTotals()
        {
            int tempTotalExisting = 0;
            int tempTotalPlanned = 0;
            int tempTotalAchieved = 0;

            System.Diagnostics.Trace.WriteLine("CalculateTotals");
            foreach (var item in Items)
            {
                tempTotalExisting = tempTotalExisting + (item.IsExisting ? 1 : 0);
                tempTotalPlanned = tempTotalPlanned + (item.IsPlanned ? 1 : 0);
                tempTotalAchieved = tempTotalAchieved + (item.IsAchieved ? 1 : 0);
            }

            TotalExisting = tempTotalExisting;
            TotalPlanned = tempTotalPlanned;
            TotalAchieved = tempTotalAchieved;
        }

        #endregion Helpers

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            System.Diagnostics.Trace.WriteLine("OnPropertyChanged");
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion INotifyPropertyChanged
    }

    public class ItemData : INotifyPropertyChanged
    {
        #region Properties

        private bool _isExisting;
        public bool IsExisting
        {
            get {
                System.Diagnostics.Trace.WriteLine("isExisting Get");
                return _isExisting;
            }
            set
            {
                System.Diagnostics.Trace.WriteLine("isExisting Set");
                _isExisting = value;
                OnPropertyChanged("IsExisting");
            }
        }

        private bool _isPlanned;
        public bool IsPlanned
        {
            get { return _isPlanned; }
            set
            {
                _isPlanned = value;
                OnPropertyChanged("IsPlanned");
            }
        }

        private bool _isAchieved;
        public bool IsAchieved
        {
            get { return _isAchieved; }
            set
            {
                _isAchieved = value;
                OnPropertyChanged("IsAchieved");
            }
        }

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }

        #endregion Properties

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            System.Diagnostics.Trace.WriteLine("OnpropertyCHanged");
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion INotifyPropertyChanged
    }
}