Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get IObservable from all Property Changed events on T.MyProperty in SortedList<MyClass>

I have a Class, MyClass that implements INotifyPropertyChanged and has some properties that implement PropertyChanged. When MyClass.MyProperty changes, PropertyChanged fires as expected. Another class contains a SortedList<MyClass> .I've tried merging the events into a single observable in the class that contains the SortedSet<MyClass> and subscribing to it, but it doesn't seem to ever have any events. Here's what I'm trying:

Observable.Merge(MySortedList.ToObservable())
   .Subscribe(evt => Console.WriteLine("{0} changed", evt.MyProperty));

What I'm trying to get is a single observable that contains all of the events from every item in my SortedList<MyClass>. I've tried using ObservableCollection instead, but that doesn't change anything, nor would it be expected to, really, since it doesn't fire collectionchanged when a property of a contained item changes, anyway. I can listen to individual elements in SortedList<MyClass> and see the PropertyChanged event fire, but what I want is a single Observable that contains a stream of ALL of the PropertyChanged events from all of the elements in SortedList<MyClass>.

It seems like this should be something fairly easy to do using Rx, but I can't seem to figure out how.

like image 836
Daniel Winks Avatar asked Sep 11 '13 20:09

Daniel Winks


1 Answers

I have produced an article for the RxCookBook on this subject that you can find here https://github.com/LeeCampbell/RxCookbook/blob/master/Model/CollectionChange.md Further article on PropertyChange notification is here https://github.com/LeeCampbell/RxCookbook/blob/master/Model/PropertyChange.md

It solves what you need by aggregating up the changes from an ObservableCollection<T>. By using the ObservableCollection<T> you also get notifications when items are added or removed from the collection.

If you dont want to use the ObservableCollection<T> (i.e. you only want to track properties at a given snapshot of the collection) then you will need to do something else. First I assume you have an INoftifyPropertyChanged to IObservable<T> extension method or you are just going to use the standard event to IObservable<T> methods.

Next you can project the List of values into a list of change sequences i.e. IEnumerable<T> to IEumerable<IObserable<T>>. This allows you to use Observable.Merge to flatten the list of changes in to a single stream of changes.

Here is a sample if you dont want to use the link above:

void Main()
{
    var myList = new List<MyThing>{
        new MyThing{Name="Lee", Age=31},
        new MyThing{Name="Dave", Age=37},
        new MyThing{Name="Erik", Age=44},
        new MyThing{Name="Bart", Age=24},
        new MyThing{Name="James", Age=32},
    };

    var subscription = Observable.Merge(myList.Select(t=>t.OnAnyPropertyChanges()))
                .Subscribe(x=>Console.WriteLine("{0} is {1}", x.Name, x.Age));

    myList[0].Age = 33;
    myList[3].Name = "Bob";

    subscription.Dispose();
}

// Define other methods and classes here
public class MyThing : INotifyPropertyChanged
{
private string _name;
private int _age;

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

public int Age
{
    get { return _age; }
    set
    {
        _age = value;
        OnPropertyChanged("Age");
    }
}

public event PropertyChangedEventHandler PropertyChanged;

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

public static class NotificationExtensions
{
    /// <summary>
    /// Returns an observable sequence of the source any time the <c>PropertyChanged</c> event is raised.
    /// </summary>
    /// <typeparam name="T">The type of the source object. Type must implement <seealso cref="INotifyPropertyChanged"/>.</typeparam>
    /// <param name="source">The object to observe property changes on.</param>
    /// <returns>Returns an observable sequence of the value of the source when ever the <c>PropertyChanged</c> event is raised.</returns>
    public static IObservable<T> OnAnyPropertyChanges<T>(this T source)
        where T : INotifyPropertyChanged
    {
            return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
                                handler => handler.Invoke,
                                h => source.PropertyChanged += h,
                                h => source.PropertyChanged -= h)
                            .Select(_=>source);
    }
}

Which will output:

Lee is 33
Bob is 24
like image 163
Lee Campbell Avatar answered Oct 20 '22 21:10

Lee Campbell