Get IObservable from all Property Changed events o

2020-06-03 09:03发布

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.

2条回答
ゆ 、 Hurt°
2楼-- · 2020-06-03 09:27

Assuming from your description that you're able to provide this:

IEnumerable<KeyValuePair<string, IObservable<object>>> observableProperties;

You can use this to merge the property observables into a single 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'll need to give more details about why you can't 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 like this:

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>())
};
查看更多
够拽才男人
3楼-- · 2020-06-03 09:31

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
查看更多
登录 后发表回答