DataGrid calculates the difference between the values ​​in two data cells

In my small application, I have a DataGrid (see screenshot) associated with a list of Measurement objects. A dimension is just a data container with two properties: Date and CounterGas (float). Each Measurement object represents my gas consumption on a specific date.

enter image description here

The dimension list is bound to the DataGrid as follows:

<DataGrid ItemsSource="{Binding Path=Measurements}" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Date" Binding="{Binding Path=Date, StringFormat={}{0:dd.MM.yyyy}}" /> <DataGridTextColumn Header="Counter Gas" Binding="{Binding Path=ValueGas, StringFormat={}{0:F3}}" /> </DataGrid.Columns> </DataGrid> 

Well, now my question :) I would like to have another column next to the Counter Gas column, which shows the difference between the actual counter value and the last counter value.

eg. this additional column should calculate the difference between the value of February 13 and February 6 => 199.789 - 187.115 = 15.674

What is the best way to achieve this? I would like to avoid any calculations in the Measurement class that should just store data. I would rather want a DataGrid to handle calculations. So, is there a way to add another column that just calculates the difference between the values? Maybe use some kind of converter and extreme binding ?; D

PS: Maybe someone with a better reputation could include a screenshot. Thanks:)

+4
source share
1 answer

Extreme snap? No problems.

 <Window.Resources> <local:ItemsDifferenceConverter x:Key="ItemsDifferenceConverter"/> </Window.Resources> <DataGrid ItemsSource="{Binding Path=Measurements}" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Date" Binding="{Binding Path=Date, StringFormat={}{0:dd.MM.yyyy}}" /> <DataGridTextColumn Header="Counter Gas" Binding="{Binding Path=ValueGas, StringFormat={}{0:F3}}" /> <DataGridTextColumn Header="Difference"> <DataGridTextColumn.Binding> <MultiBinding Converter="{StaticResource ItemsDifferenceConverter}" Mode="OneWay"> <Binding Path="."/> <Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" Path="ItemsSource"/> </MultiBinding> </DataGridTextColumn.Binding> </DataGridTextColumn> </DataGrid.Columns> </DataGrid> 

Some kind of converter

 class ItemsDifferenceConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values.Length != 2) return null; var item = values[0] as Measurement; var collection = values[1] as IEnumerable<Measurement>; if (item == null || collection == null) return null; var list = collection.OrderBy(v => v.Date).ToList(); //it will be easier to find a previous date var itemIndex = list.IndexOf(item); if (itemIndex == 0) //First item return null; var diff = item.ValueGas - list[itemIndex - 1].ValueGas; return (diff > 0 ? "+" : "") + diff.ToString(); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new Exception("The method or operation is not implemented."); } } 

But this example does not work with deleting / updating elements of the base collection. In this case, an intermediate ViewModel would be the best choice.

Here is my way to do this. It works with updating, deleting and adding items.

 /// <summary> /// Main ViewModel, contains items for DataGrid /// </summary> public class MeasurementListViewModel { public MeasurementListViewModel(IEnumerable<Measurement> measurements) { this.Items = new ObservableCollection<MeasurementViewModel>(measurements.Select(m=>new MeasurementViewModel(m))); this.Measurements = (ListCollectionView)CollectionViewSource.GetDefaultView(this.Items); this.Items.CollectionChanged += new NotifyCollectionChangedEventHandler(Items_CollectionChanged); foreach(var m in this.Items) m.PropertyChanged += new PropertyChangedEventHandler(Item_PropertyChanged); } //Date or Value were changed void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) { //Update the collection view if refresh isn't possible if (this.Measurements.IsEditingItem) this.Measurements.CommitEdit(); if (this.Measurements.IsAddingNew) this.Measurements.CommitNew(); this.Measurements.Refresh(); } //Items were added or removed void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { //Attach the observer for the properties if (e.NewItems != null) foreach (var vm in e.NewItems.OfType<MeasurementViewModel>()) vm.PropertyChanged += Item_PropertyChanged; //Refresh when it is possible if(!this.Measurements.IsAddingNew && !this.Measurements.IsEditingItem) this.Measurements.Refresh(); } private ObservableCollection<MeasurementViewModel> Items { get; set; } public ListCollectionView Measurements { get; set; } } /// <summary> /// Wraps Measurement class and provide notification of changes /// </summary> public class MeasurementViewModel { public MeasurementViewModel() { this.Model = new Measurement(); } public MeasurementViewModel(Measurement m) { this.Model = m; } public Measurement Model { get; private set; } public DateTime Date { get { return this.Model.Date; } set { this.Model.Date = value; OnPropertyChanged("Date"); } } public double ValueGas { get { return this.Model.ValueGas; } set { this.Model.ValueGas = value; OnPropertyChanged("ValueGas"); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } 

The converter is a little different:

 class ItemsDifferenceConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { var item = values[0] as MeasurementViewModel; var view = values[1] as ICollectionView; if (item == null || view == null) return null; var list = view.SourceCollection.OfType<MeasurementViewModel>().OrderBy(v => v.Date).ToList(); //it will be easier to find a previous date var itemIndex = list.IndexOf(item); if (itemIndex == 0) //First item return null; var diff = item.ValueGas - list[itemIndex - 1].ValueGas; return (diff > 0 ? "+" : "") + diff.ToString(); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new Exception("The method or operation is not implemented."); } } 

And DataGrid :

 <DataGrid ItemsSource="{Binding Path=Measurements}" AutoGenerateColumns="False" CanUserAddRows="True"> <DataGrid.Columns> <DataGridTextColumn Header="Date" Binding="{Binding Path=Date, StringFormat={}{0:dd.MM.yyyy}}" /> <DataGridTextColumn Header="Counter Gas" Binding="{Binding Path=ValueGas, StringFormat={}{0:F3}}" /> <DataGridTextColumn Header="Difference"> <DataGridTextColumn.Binding> <MultiBinding Converter="{StaticResource ItemsDifferenceConverter}" Mode="OneWay"> <Binding Path="."/> <Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" Path="ItemsSource"/> </MultiBinding> </DataGridTextColumn.Binding> </DataGridTextColumn> </DataGrid.Columns> </DataGrid> 
+4
source

Source: https://habr.com/ru/post/1339726/


All Articles