/// <summary> /// WhenAny allows you to observe whenever one or more properties on an /// object have changed, providing an initial value when the Observable /// is set up, unlike ObservableForProperty(). Use this method in /// constructors to set up bindings between properties that also need an /// initial setup. /// </summary> public static IObservable <TRet> WhenAny <TSender, TRet, T1, T2>(this TSender This, Expression <Func <TSender, T1> > property1, Expression <Func <TSender, T2> > property2, Func <IObservedChange <TSender, T1>, IObservedChange <TSender, T2>, TRet> selector) where TSender : IReactiveNotifyPropertyChanged { var slot1 = new ObservedChange <TSender, T1>() { Sender = This, PropertyName = String.Join(".", RxApp.expressionToPropertyNames(property1)), }; T1 slot1Value = default(T1); slot1.TryGetValue(out slot1Value); slot1.Value = slot1Value; IObservedChange <TSender, T1> islot1 = slot1; var slot2 = new ObservedChange <TSender, T2>() { Sender = This, PropertyName = String.Join(".", RxApp.expressionToPropertyNames(property2)), }; T2 slot2Value = default(T2); slot2.TryGetValue(out slot2Value); slot2.Value = slot2Value; IObservedChange <TSender, T2> islot2 = slot2; return(Observable.Create <TRet>(subject => { subject.OnNext(selector(islot1, islot2)); return Observable.Merge( This.ObservableForProperty(property1).Do(x => { lock (slot1) { islot1 = x.fillInValue(); } }).Select(x => selector(islot1, islot2)), This.ObservableForProperty(property2).Do(x => { lock (slot2) { islot2 = x.fillInValue(); } }).Select(x => selector(islot1, islot2)) ).Subscribe(subject); })); }
/// <summary> /// BindTo takes an Observable stream and applies it to a target /// property. Conceptually it is similar to "Subscribe(x => /// target.property = x)", but allows you to use child properties /// without the null checks. /// </summary> /// <param name="target">The target object whose property will be set.</param> /// <param name="property">An expression representing the target /// property to set. This can be a child property (i.e. x.Foo.Bar.Baz).</param> /// <returns>An object that when disposed, disconnects the binding.</returns> public static IDisposable BindTo <TTarget, TValue>( this IObservable <TValue> This, TTarget target, Expression <Func <TTarget, TValue> > property) where TTarget : IReactiveNotifyPropertyChanged { var sourceSub = new MultipleAssignmentDisposable(); var source = This; var subscribify = new Action <TTarget, string[]>((tgt, propNames) => { if (sourceSub.Disposable != null) { sourceSub.Disposable.Dispose(); } object current = tgt; PropertyInfo pi = null; foreach (var propName in propNames.SkipLast(1)) { if (current == null) { return; } pi = RxApp.getPropertyInfoOrThrow(current.GetType(), propName); current = pi.GetValue(current, null); } if (current == null) { return; } pi = RxApp.getPropertyInfoOrThrow(current.GetType(), propNames.Last()); sourceSub.Disposable = This.Subscribe(x => { pi.SetValue(current, x, null); }); }); IDisposable[] toDispose = new IDisposable[] { sourceSub, null }; string[] propertyNames = RxApp.expressionToPropertyNames(property); toDispose[1] = target.ObservableForProperty(property).Subscribe(_ => subscribify(target, propertyNames)); subscribify(target, propertyNames); return(Disposable.Create(() => { toDispose[0].Dispose(); toDispose[1].Dispose(); })); }
/// <summary> /// ObservableForProperty returns an Observable representing the /// property change notifications for a specific property on a /// ReactiveObject. This method (unlike other Observables that return /// IObservedChange) guarantees that the Value property of /// the IObservedChange is set. /// </summary> /// <param name="property">An Expression representing the property (i.e. /// 'x => x.SomeProperty.SomeOtherProperty'</param> /// <param name="beforeChange">If True, the Observable will notify /// immediately before a property is going to change.</param> /// <returns>An Observable representing the property change /// notifications for the given property.</returns> public static IObservable <IObservedChange <TSender, TValue> > ObservableForProperty <TSender, TValue>( this TSender This, Expression <Func <TSender, TValue> > property, bool beforeChange = false) where TSender : INotifyPropertyChanged { var propertyNames = new LinkedList <string>(RxApp.expressionToPropertyNames(property)); var subscriptions = new LinkedList <IDisposable>(propertyNames.Select(x => (IDisposable)null)); var ret = new Subject <IObservedChange <TSender, TValue> >(); /* x => x.Foo.Bar.Baz; * * Subscribe to This, look for Foo * Subscribe to Foo, look for Bar * Subscribe to Bar, look for Baz * Subscribe to Baz, publish to Subject * Return Subject * * If Bar changes (notification fires on Foo), resubscribe to new Bar * Resubscribe to new Baz, publish to Subject * * If Baz changes (notification fires on Bar), * Resubscribe to new Baz, publish to Subject */ subscribeToExpressionChain( This, buildPropPathFromNodePtr(propertyNames.First), This, propertyNames.First, subscriptions.First, beforeChange, ret); return(Observable.Create <IObservedChange <TSender, TValue> >(x => { var disp = ret.Subscribe(x.OnNext, x.OnError, x.OnCompleted); return () => { subscriptions.ForEach(y => y.Dispose()); disp.Dispose(); }; })); }
/// <summary> /// Given a fully filled-out IObservedChange object, SetValueToProperty /// will apply it to the specified object (i.e. it will ensure that /// target.property == This.GetValue() and "replay" the observed change /// onto another object) /// </summary> /// <param name="target">The target object to apply the change to.</param> /// <param name="property">The target property to apply the change to.</param> public static void SetValueToProperty <TSender, TValue, TTarget>( this IObservedChange <TSender, TValue> This, TTarget target, Expression <Func <TTarget, TValue> > property) { object current = target; string[] propNames = RxApp.expressionToPropertyNames(property); PropertyInfo pi; foreach (var propName in propNames.SkipLast(1)) { pi = RxApp.getPropertyInfoOrThrow(current.GetType(), propName); current = pi.GetValue(current, null); } pi = RxApp.getPropertyInfoForProperty(current.GetType(), propNames.Last()); pi.SetValue(current, This.GetValue(), null); }