开发者

Rx: Ignoring updates caused by Subscribers

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 a开发者_Go百科nd only stop taking values when the PropertyChanged event is raised by an external source. Is there a good way to get that to happen?


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.


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜