2

I'm having problems figuring out how to do this. I have two instances (source & target) that implement INotifyPropertyChanged and I'm tracking the PropertyChanged event for both. What I want to do is run an action any time source.PropertyChanged is raised until target.PropertyChanged is raised. I can do that just fine like this:

INotifyPropertyChanged source;
INotifyPropertyChanged target;

var sourcePropertyChanged = Observable
    .FromEvent<PropertyChangedEventArgs>(source, "PropertyChanged")
    .Where(x => x.EventArgs.PropertyName == sourcePropertyName);

var targetPropertyChanged = Observable
    .FromEvent<PropertyChangedEventArgs>(target, "PropertyChanged")
    .Where(x => x.EventArgs.PropertyName == targetPropertyName);

sourcePropertyChanged
    .TakeUntil(targetPropertyChanged)
    .ObserveOnDispatcher()
    .Subscribe(_ => /*Raises target.PropertyChanged for targetPropertyName*/);

The problem I'm having is I want to ignore the PropertyChanged notifications caused by the actions and only stop taking values when the PropertyChanged event is raised by an external source. Is there a good way to get that to happen?

Bryan Anderson
  • 15,969
  • 8
  • 68
  • 83
  • What do you mean by 'PropertyChanged notifications by the actions'? What actions? – Ronald Wildenberg Oct 27 '10 at 15:19
  • @Ronald I updated my explanation to hopefully be a little clearer but by Action I mean function that doesn't return a value. The part represented by `/*Raises target.PropertyChanged for targetPropertyName*/`. – Bryan Anderson Oct 27 '10 at 15:31

3 Answers3

4

There's no built in way of doing what you're talking about. Here's a simple SkipWhen implementation that skips the next source value each time a value is received on the 'other' sequence:

public static IObservable<TSource> SkipWhen(this IObservable<TSource> source, 
    IObservable<TOther> other)
{
    return Observable.Defer<TSource>(() =>
    {
        object lockObject = new object();
        Stack<TOther> skipStack = new Stack<TOther>();

        other.Subscribe(x => { lock(lockObject) { skipStack.Push(x); });

        return source.Where(_ =>
        {
            lock(lockObject);
            {
                if (skipStack.Count > 0)
                {
                    skipStack.Pop();
                    return false;
                }
                else
                {
                    return true;
                }
            }
        });
    });
}

You're code would then be updated like so (see my note below):

INotifyPropertyChanged source;
INotifyPropertyChanged target;

// See the link at the bottom of my answer
var sourcePropertyChanged = source.GetPropertyChangeValues(x => x.SourceProperty);

// Unit is Rx's "void"
var targetChangedLocally = new Subject<Unit>();

var targetPropertyChanged = target.GetPropertyChangeValues(x => x.TargetProperty)
    .SkipWhen(targetChangedLocally);

sourcePropertyChanged
    .TakeUntil(targetPropertyChanged)
    .ObserveOnDispatcher()
    .Subscribe(_ =>
    {
        targetChangedLocally.OnNext();
        /*Raises target.PropertyChanged for targetPropertyName*/
    });

NB: I recently blogged about a strongly typed IObservable wrapper around INotifyPropertyChanged events; feel free to steal that code.

Richard Szalay
  • 83,269
  • 19
  • 178
  • 237
  • That's a pretty nice solution. Unfortunately I'm using Silverlight so I don't have ConcurrentStack available and I'd rather not have to implement one for this. I've updated the tags to show SL. – Bryan Anderson Oct 27 '10 at 18:45
  • @Bryan - That's not a problem. Replace ConcurrentStack with Stack and either add `ObserveOnDispatcher` between `GetPropertyChangeValues` and `SkipWhen` -OR- do your own locking. I've updated by reply to do the latter (since it's less likely to bite you later) – Richard Szalay Oct 27 '10 at 18:58
  • Doh! Thanks. Some days it just doesn't pay to get out of bed. – Bryan Anderson Oct 27 '10 at 20:02
0

There's no built-in way but you could probably filter out events using the Where extension method for observable. The condition to filter on would be the sender of the event. I suppose that the sender of a target.PropertyChanged event is different than the sender of a PropertyChanged event raised by another source.

I'm not entirely sure if this is an approach you can use.

Ronald Wildenberg
  • 31,634
  • 14
  • 90
  • 133
  • Unfortunately the sender will be whatever class is raising the `PropertyChanged` event, in this case `target`, no matter what's happening to cause the event to be raised. – Bryan Anderson Oct 27 '10 at 20:11
0

Using locks in Rx this way is fine. The lock is short lived and doesn't call out to user code.