/// <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> /// 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); }
/// <summary> /// Attempts to return the current value of a property given a /// notification that it has changed. If any property in the /// property expression is null, false is returned. /// </summary> /// <param name="changeValue">The value of the property expression.</param> /// <returns>True if the entire expression was able to be followed, false otherwise</returns> public static bool TryGetValue <TSender, TValue>(this IObservedChange <TSender, TValue> This, out TValue changeValue) { if (!Equals(This.Value, default(TValue))) { changeValue = This.Value; return(true); } object current = This.Sender; string[] propNames = null; lock (propStringToNameCache) { propNames = propStringToNameCache.Get(This.PropertyName); } PropertyInfo pi; foreach (var propName in propNames.SkipLast(1)) { if (current == null) { changeValue = default(TValue); return(false); } pi = RxApp.getPropertyInfoOrThrow(current.GetType(), propName); current = pi.GetValue(current, null); } if (current == null) { changeValue = default(TValue); return(false); } pi = RxApp.getPropertyInfoOrThrow(current.GetType(), propNames.Last()); changeValue = (TValue)pi.GetValue(current, null); return(true); }