Calculation on a realtime changing collection ( in

2019-09-08 17:05发布

问题:

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.

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

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

回答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
        }
    }
}