/// <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, bool skipInitial = true) { if (This == null) { throw new ArgumentNullException("Sender"); } var propertyNames = Reflection.ExpressionToPropertyNames(property); /* 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 */ return(SubscribeToExpressionChain <TSender, TValue>( This, propertyNames, beforeChange, skipInitial)); }
/// <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) { Reflection.SetValueToPropertyChain(target, Reflection.ExpressionToPropertyNames(property), This.GetValue()); }
/// <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) { var slot1 = new ObservedChange <TSender, T1>() { Sender = This, PropertyName = String.Join(".", Reflection.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(".", Reflection.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> /// Returns the name of a property which has been changed. /// </summary> /// <typeparam name="TSender">The sender type.</typeparam> /// <typeparam name="TValue">The value type.</typeparam> /// <param name="item">The observed change.</param> /// <returns> /// The name of the property which has changed. /// </returns> public static string GetPropertyName <TSender, TValue>(this IObservedChange <TSender, TValue> item) { if (item is null) { throw new ArgumentNullException(nameof(item)); } return(Reflection.ExpressionToPropertyNames(item.Expression)); }
public IDisposable OneWayBind <TViewModel, TView, TVMProp, TVProp>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TVMProp> > vmProperty, Expression <Func <TView, TVProp> > viewProperty, Func <TVMProp> fallbackValue = null, object conversionHint = null) where TViewModel : class where TView : IViewFor { if (viewProperty == null) { var viewPropChain = Reflection.getDefaultViewPropChain(view, Reflection.ExpressionToPropertyNames(vmProperty)); var viewType = Reflection.GetTypesForPropChain(typeof(TView), viewPropChain).Last(); var converter = getConverterForTypes(typeof(TVMProp), viewType); if (converter == null) { throw new ArgumentException(String.Format("Can't convert {0} to {1}. To fix this, register a IBindingTypeConverter", typeof(TVMProp), viewType)); } return(Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty) .SelectMany(x => { object tmp; if (!converter.TryConvert(x, viewType, conversionHint, out tmp)) { return Observable.Empty <object>(); } return Observable.Return(tmp); }) .Subscribe(x => Reflection.SetValueToPropertyChain(view, viewPropChain, x, false))); } else { var converter = getConverterForTypes(typeof(TVMProp), typeof(TVProp)); if (converter == null) { throw new ArgumentException(String.Format("Can't convert {0} to {1}. To fix this, register a IBindingTypeConverter", typeof(TVMProp), typeof(TVProp))); } return(Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty) .SelectMany(x => { object tmp; if (!converter.TryConvert(x, typeof(TVProp), conversionHint, out tmp)) { return Observable.Empty <TVProp>(); } return Observable.Return(tmp == null ? default(TVProp) : (TVProp)tmp); }) .BindTo(view, viewProperty, () => { object tmp; return converter.TryConvert(fallbackValue(), typeof(TVProp), conversionHint, out tmp) ? (TVProp)tmp : default(TVProp); })); } }
IDisposable bindCommandInternal <TView, TViewModel, TProp, TParam>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TProp> > propertyName, Func <object, object> viewPropGetter, IObservable <TParam> withParameter, string toEvent, out IObservable <TProp> changed, Func <ICommand, ICommand> commandFixuper = null) where TViewModel : class where TView : class, IViewFor <TViewModel> where TProp : ICommand { var vmPropChain = Reflection.ExpressionToPropertyNames(propertyName); IDisposable disp = Disposable.Empty; changed = Reflection.ViewModelWhenAnyValue(viewModel, view, propertyName).Publish().RefCount(); var propSub = changed.Subscribe(x => { disp.Dispose(); if (x == null) { disp = Disposable.Empty; return; } var vmString = String.Join(".", vmPropChain); var target = viewPropGetter(view); if (target == null) { this.Log().Error("Binding {0}.{1} => {2}.{1} failed because target is null", typeof(TViewModel).FullName, vmString, view.GetType().FullName); disp = Disposable.Empty; } var cmd = commandFixuper != null ? commandFixuper(x) : x; if (toEvent != null) { disp = CreatesCommandBinding.BindCommandToObject(cmd, target, withParameter.Select(y => (object)y), toEvent); } else { disp = CreatesCommandBinding.BindCommandToObject(cmd, target, withParameter.Select(y => (object)y)); } }); return(Disposable.Create(() => { propSub.Dispose(); disp.Dispose(); })); }
/// <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, Func <TValue> fallbackValue = null) { var pn = Reflection.ExpressionToPropertyNames(property); var lastValue = default(TValue); return(Observable.Merge(target.WhenAny(property, _ => lastValue).Skip(1), This) .Subscribe(x => { lastValue = x; Reflection.SetValueToPropertyChain(target, pn, x); })); }
/// <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) { var propertyNames = new LinkedList <string>(Reflection.ExpressionToPropertyNames(property)); var subscriptions = new LinkedList <IDisposable>(propertyNames.Select(x => (IDisposable)null)); var ret = new Subject <IObservedChange <TSender, TValue> >(); if (This == null) { throw new ArgumentNullException("Sender"); } /* 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); return () => { subscriptions.ForEach(y => y.Dispose()); disp.Dispose(); }; })); }
public IDisposable AsyncOneWayBind <TViewModel, TView, TProp, TOut>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TProp> > vmProperty, Expression <Func <TView, TOut> > viewProperty, Func <TProp, IObservable <TOut> > selector, Func <TOut> fallbackValue = null) where TViewModel : class where TView : IViewFor { if (viewProperty == null) { var viewPropChain = Reflection.getDefaultViewPropChain(view, Reflection.ExpressionToPropertyNames(vmProperty)); return(Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty) .SelectMany(selector) .Subscribe(x => Reflection.SetValueToPropertyChain(view, viewPropChain, x, false))); } return(Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty) .SelectMany(selector) .BindTo(view, viewProperty, fallbackValue)); }
/// <summary> /// Returns the current value of a property given a notification that /// it has changed. /// </summary> /// <returns>The current value of the property</returns> public static string GetPropertyName <TSender, TValue>(this IObservedChange <TSender, TValue> This) { return(Reflection.ExpressionToPropertyNames(This.Expression)); }
public IDisposable Bind <TViewModel, TView, TVMProp, TVProp, TDontCare>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TVMProp> > vmProperty, Expression <Func <TView, TVProp> > viewProperty, IObservable <TDontCare> signalViewUpdate, object conversionHint) where TViewModel : class where TView : IViewFor { var signalInitialUpdate = new Subject <bool>(); var vmPropChain = Reflection.ExpressionToPropertyNames(vmProperty); string[] viewPropChain; if (viewProperty == null) { viewPropChain = Reflection.getDefaultViewPropChain(view, vmPropChain); } else { viewPropChain = Reflection.ExpressionToPropertyNames(viewProperty); } var vmToViewConverter = getConverterForTypes(typeof(TVMProp), typeof(TVProp)); var viewToVMConverter = getConverterForTypes(typeof(TVProp), typeof(TVMProp)); if (vmToViewConverter == null || viewToVMConverter == null) { throw new ArgumentException( String.Format("Can't two-way convert between {0} and {1}. To fix this, register a IBindingTypeConverter", typeof(TVMProp), typeof(TVProp))); } var somethingChanged = Observable.Merge( Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty).Select(_ => true), signalInitialUpdate, signalViewUpdate != null ? signalViewUpdate.Select(_ => false) : view.WhenAnyDynamic(viewPropChain, x => (TVProp)x.Value).Select(_ => false)); string vmChangedString = String.Format("Setting {0}.{1} => {2}.{3}: ", typeof(TViewModel).Name, String.Join(".", vmPropChain), typeof(TView).Name, String.Join(".", viewPropChain)); string viewChangedString = String.Format("Setting {0}.{1} => {2}.{3}: ", typeof(TView).Name, String.Join(".", viewPropChain), typeof(TViewModel).Name, String.Join(".", vmPropChain)); var changeWithValues = somethingChanged.Select(isVm => { TVMProp vmValue; TVProp vValue; if (!Reflection.TryGetValueForPropertyChain(out vmValue, view.ViewModel, vmPropChain) || !Reflection.TryGetValueForPropertyChain(out vValue, view, viewPropChain)) { return(null); } if (isVm) { var vmAsView = (TVProp)vmToViewConverter.Convert(vmValue, typeof(TVProp), conversionHint); var changed = EqualityComparer <TVProp> .Default.Equals(vValue, vmAsView) != true; if (!changed) { return(null); } this.Log().Info(vmChangedString + (vmAsView != null ? vmAsView.ToString() : "(null)")); return(Tuple.Create((object)vmAsView, isVm)); } else { var vAsViewModel = (TVMProp)viewToVMConverter.Convert(vValue, typeof(TVMProp), conversionHint); var changed = EqualityComparer <TVMProp> .Default.Equals(vmValue, vAsViewModel) != true; if (!changed) { return(null); } this.Log().Info(viewChangedString + (vAsViewModel != null ? vAsViewModel.ToString() : "(null)")); return(Tuple.Create((object)vAsViewModel, isVm)); } }); var ret = changeWithValues.Subscribe(isVmWithLatestValue => { if (isVmWithLatestValue == null) { return; } if (isVmWithLatestValue.Item2) { Reflection.SetValueToPropertyChain(view, viewPropChain, isVmWithLatestValue.Item1, false); } else { Reflection.SetValueToPropertyChain(view.ViewModel, vmPropChain, isVmWithLatestValue.Item1, false); } }); // NB: Even though it's technically a two-way bind, most people // want the ViewModel to win at first. signalInitialUpdate.OnNext(true); return(ret); }
public IReactiveBinding <TView, TViewModel, TProp> BindCommand <TView, TViewModel, TProp, TControl, TParam>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TProp> > propertyName, Expression <Func <TView, TControl> > controlName, IObservable <TParam> withParameter, string toEvent = null) where TViewModel : class where TView : class, IViewFor <TViewModel> where TProp : ICommand { var ctlName = Reflection.SimpleExpressionToPropertyName(controlName); var viewPropGetter = Reflection.GetValueFetcherForProperty(typeof(TView), ctlName); IObservable <TProp> changed; IDisposable bindingDisposable = bindCommandInternal(viewModel, view, propertyName, viewPropGetter, withParameter, toEvent, out changed); return(new ReactiveBinding <TView, TViewModel, TProp>(view, viewModel, new string[] { ctlName }, Reflection.ExpressionToPropertyNames(propertyName), changed, BindingDirection.OneWay, bindingDisposable)); }