0

I have a changing collection of changing objects(add/delete in Collection as well as property changes are also possible).

I want to get a new collection by some calculation on this Collection (summation / multiplication over some fields of all the objects having same value of a field). And bind calculated list to UI.

for Ex: So I have a list of rate and qty of different qualities of several fruits and now I want to find a new list having avg rate and total quantity of each fruit(irrespective of quality) given that rate & quantity can be changes at run time as well as some more qualities and fruit-species can be added to collection.

enter image description here

Now If rates or quantity changes at runtime in first List changes should be reflected in second list also. enter image description here

Please suggest which methods should be used to achieve it considering we are binding the list to a Datagrid.

veerendra gupta
  • 625
  • 1
  • 8
  • 17
  • LINQ should be your friend - GroupBy, Sum and Average methods. – Konrad Kokosa Jul 15 '14 at 14:15
  • Have you tried anything yet? Any code we could see? – Kaizen Programmer Jul 15 '14 at 14:17
  • Would that be a wise idea to do all the calculation again and again on every single change? That would cause performance hit.And In my case I am having 100-400 items in first list and 4 more properties which are changing.So if I do this calculation for each change then on an avg frequency of the calculation 40-50. – veerendra gupta Jul 15 '14 at 14:22
  • @ micheal: groupBy(Name) & then aggregation on all those group but that is too slow. – veerendra gupta Jul 15 '14 at 14:23
  • I am subscribing to notifypropertychanged and calculationchange and fire the linq query eachtime. Is there anyway to control these calculation time - let say run linq query after each 50 MS and if possible on background so that UI doesn't freeze. – veerendra gupta Jul 15 '14 at 14:29

1 Answers1

1

There is a neat Reactive Extension called Buffer:

var o = new ObservableCollection<long>();
var s1 = Observable.Interval(TimeSpan.FromMilliseconds(100)).Subscribe(o.Add);
var s2 =
    Observable.FromEventPattern<NotifyCollectionChangedEventArgs>(o, "CollectionChanged")
        .Buffer(TimeSpan.FromMilliseconds(500))
        .Subscribe(
            s =>
            {
                Console.WriteLine("Last received {0}. Current count: {1}", Convert.ToInt64(s.Last().EventArgs.NewItems[0]), o.Count);                            
            });

This will allow you to receive on collection changed events as they occur, but ignore all except the last one.

UPDATE 1

It is a known issue that CollectionChanged does not fire when items are updated. The MSDN documentation is simply incorrect. You will have to implement a TrulyObservableCollection but your data elements must implement INotifyChanged interface as well.

namespace ConsoleApplication1
{
    #region

    using System;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Linq;
    using System.Reactive.Linq;
    using System.Threading;

    #endregion

    internal class Program
    {
        #region Methods

        private static void Main(string[] args)
        {
            var autoReset = new AutoResetEvent(false);
            var r = new Random();
            var o = new TrulyObservableCollection<DataPoint>();
            var subscription1 = Observable.Interval(TimeSpan.FromSeconds(1)).Take(3).Subscribe(
                i =>
                {
                    o.Add(
                        new DataPoint
                        {
                            ItemCount = r.Next(100)
                        });
                    Console.WriteLine("Fire1 {0}", i);
                });
            var subscription2 =
                Observable.FromEventPattern<NotifyCollectionChangedEventArgs>(o, "CollectionChanged")
                    .Subscribe(s => { Console.WriteLine("List changed. Current total {0}", o.Sum(s1 => s1.ItemCount)); });
            var subscription3 = Observable.Interval(TimeSpan.FromSeconds(1)).Delay(TimeSpan.FromSeconds(3)).Take(3).Finally(
                () =>
                {
                    o.Clear();
                    autoReset.Set();
                }).Subscribe(
                    i =>
                    {
                        if (o.Any())
                        {
                            o[r.Next(o.Count)].ItemCount = r.Next(100);
                            Console.WriteLine("Fire3 {0}", i);
                        }
                    });
            autoReset.WaitOne();
        }

        #endregion

        public class TrulyObservableCollection<T> : ObservableCollection<T>
            where T : INotifyPropertyChanged
        {
            #region Constructors and Destructors

            public TrulyObservableCollection() { CollectionChanged += this.TrulyObservableCollection_CollectionChanged; }

            #endregion

            #region Methods

            private void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.NewItems != null)
                {
                    foreach (Object item in e.NewItems)
                    {
                        (item as INotifyPropertyChanged).PropertyChanged += this.item_PropertyChanged;
                    }
                }
                if (e.OldItems != null)
                {
                    foreach (Object item in e.OldItems)
                    {
                        (item as INotifyPropertyChanged).PropertyChanged -= this.item_PropertyChanged;
                    }
                }
            }

            private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                OnCollectionChanged(a);
            }

            #endregion
        }

        private class DataPoint : INotifyPropertyChanged
        {
            #region Fields

            private int itemCount;

            #endregion

            #region Public Events

            public event PropertyChangedEventHandler PropertyChanged;

            #endregion

            #region Public Properties

            public int ItemCount
            {
                get { return itemCount; }
                set
                {
                    itemCount = value;
                    this.OnPropertyChanged("ItemCount");
                }
            }

            #endregion

            #region Methods

            protected virtual void OnPropertyChanged(string propertyName = null)
            {
                var handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }

            #endregion
        }
    }
}
Darek
  • 4,353
  • 28
  • 45
  • And how to subscribe for property changes of each item in Rx? – veerendra gupta Jul 15 '14 at 19:23
  • ObservableCollection.CollectionChanged Event - Occurs when an item is added, removed, changed, moved, or the entire list is refreshed. – Darek Jul 15 '14 at 21:03
  • However, based on my testing, it does not seem to fire when items are modified, despite what the documentation says. – Darek Jul 15 '14 at 22:59
  • One thing to keep in mind ... The Buffer extension will keep firing even if there are no updates to the collection, which caught me by surprise. You might want to cancel the subscription once there are no more updates expected, for example, if the number of updated items, remains 0 for some predefined time. – Darek Jul 16 '14 at 12:40
  • One more question: When OnCollectionChanged event is fired with args.Reset It does not provide anything in e.NewItems or e.OldItems(both are null in case of reset) then How would I know which objects have been changed. PS: Clearing the whole list and adding all items again would be really expensive operation in my case. – veerendra gupta Jul 28 '14 at 07:01
  • Reference : http://stackoverflow.com/questions/4495904/what-is-notifycollectionchangedaction-reset-value – veerendra gupta Jul 28 '14 at 07:04