Get IObservable from all property changes changed in T.MyProperty in SortedList <MyClass>
I have a MyClass class 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 tried to combine events into one observable in a class that contains a SortedSet<MyClass> and subscribed to it, but it seems to have never had any events. Here is what I am trying:
Observable.Merge(MySortedList.ToObservable()) .Subscribe(evt => Console.WriteLine("{0} changed", evt.MyProperty)); What I'm trying to get is one observable that contains all the events from each element in my SortedList<MyClass> . I tried using the ObservableCollection instead, but it does not change anything and will not be expected, since it does not work when replaced when the property of the contained element changes. I can listen to individual elements in the SortedList<MyClass> and see the fire of the PropertyChanged event, but what I want is the only Observable that contains the stream of ALL PropertyChanged events from all elements in the SortedList<MyClass> .
It seems like this should be pretty easy to do with Rx, but I can't figure out how to do this.
I have prepared an article for RxCookBook on this subject, which you can find here https://github.com/LeeCampbell/RxCookbook/blob/master/Model/CollectionChange.md The following article on PropertyChange notification is here https://github.com/ LeeCampbell / RxCookbook / blob / master / Model / PropertyChange.md
It decides what you need by aggregating the changes with ObservableCollection<T> . Using the ObservableCollection<T> , you also get notifications when items are added or removed from the collection.
If you do not want to use ObservableCollection<T> (i.e. you only want to track properties for a given snapshot of the collection), you will need to do something else. First, I assume that you have the extension method INoftifyPropertyChanged to IObservable<T> or you are just going to use the standard event for the IObservable<T> methods.
Then you can project the list of values ββinto the 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 a single stream of changes.
Here is an example if you do not 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); } } It will display:
Lee is 33 Bob is 24 Assuming from your description that you can provide this:
IEnumerable<KeyValuePair<string, IObservable<object>>> observableProperties; You can use this to combine observable properties into one observable:
IObservable<KeyValuePair<string, object>> changes = observableProperties .Select(p => p.Value .Select(o => new KeyValuePair<string, object>(p.Key, o))) .Merge(); If you find this insufficient, you will need to provide more details on why you cannot provide observableProperties .
For example, if I had a class like this:
class MyClass { public IObservable<int> X { get { ... } } public IObservable<string> S { get { ... } } } I could create observableProperties as follows:
var myObject = new MyClass(); var observableProperties = new [] { new KeyValuePair<string, IObservable<object>>("X", myObject.X.Cast<object>()), new KeyValuePair<string, IObservable<object>>("S", myObject.S.Cast<object>()) };