static ObservableAsPropertyHelper <TRet> observableToProperty <TObj, TRet>( this TObj This, IObservable <TRet> observable, Expression <Func <TObj, TRet> > property, TRet initialValue = default(TRet), IScheduler scheduler = null) where TObj : ReactiveObject { Contract.Requires(This != null); Contract.Requires(observable != null); Contract.Requires(property != null); Expression expression = Reflection.Rewrite(property.Body); if (expression.GetParent().NodeType != ExpressionType.Parameter) { throw new ArgumentException("Property expression must be of the form 'x => x.SomeProperty'"); } var ret = new ObservableAsPropertyHelper <TRet>(observable, _ => This.raisePropertyChanged(expression.GetMemberInfo().Name), initialValue, scheduler); return(ret); }
private static ObservableAsPropertyHelper <TRet> ObservableToProperty <TObj, TRet>( this TObj target, IObservable <TRet> observable, Expression <Func <TObj, TRet> > property, Func <TRet> getInitialValue, bool deferSubscription = false, IScheduler?scheduler = null) where TObj : class, IReactiveObject { if (target is null) { throw new ArgumentNullException(nameof(target)); } if (observable is null) { throw new ArgumentNullException(nameof(observable)); } if (property is null) { throw new ArgumentNullException(nameof(property)); } Expression expression = Reflection.Rewrite(property.Body); var parent = expression.GetParent(); if (parent is null) { throw new ArgumentException("The property expression does not have a valid parent.", nameof(property)); } if (parent.NodeType != ExpressionType.Parameter) { throw new ArgumentException("Property expression must be of the form 'x => x.SomeProperty'"); } var memberInfo = expression.GetMemberInfo(); if (memberInfo is null) { throw new ArgumentException("The property expression does not point towards a valid member.", nameof(property)); } string name = memberInfo.Name; if (expression is IndexExpression) { name += "[]"; } return(new ObservableAsPropertyHelper <TRet>( observable, _ => target.RaisingPropertyChanged(name), _ => target.RaisingPropertyChanging(name), getInitialValue, deferSubscription, scheduler)); }
/// <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> /// <typeparam name="TSender">The sender type.</typeparam> /// <typeparam name="TValue">The value type.</typeparam> /// <typeparam name="TTarget">The target type.</typeparam> /// <param name="item"> /// The <see cref="IObservedChange{TSender, TValue}"/> instance to use as a /// value to apply. /// </param> /// <param name="target"> /// The target object to apply the change to. /// </param> /// <param name="property"> /// The target property to apply the change to. /// </param> internal static void SetValueToProperty <TSender, TValue, TTarget>( this IObservedChange <TSender, TValue> item, TTarget target, Expression <Func <TTarget, TValue> > property) { Reflection.TrySetValueToPropertyChain(target, Reflection.Rewrite(property.Body).GetExpressionChain(), item.GetValue()); }
public static IObservable <IObservedChange <TSender, TValue> > SubscribeToExpressionChain <TSender, TValue> ( this TSender source, Expression expression, bool beforeChange = false, bool skipInitial = true) { IObservable <IObservedChange <object, object> > notifier = Observable.Return(new ObservedChange <object, object>(null, null, source)); IEnumerable <Expression> chain = Reflection.Rewrite(expression).GetExpressionChain(); notifier = chain.Aggregate(notifier, (n, expr) => n .Select(y => nestedObservedChanges(expr, y, beforeChange)) .Switch()); if (skipInitial) { notifier = notifier.Skip(1); } notifier = notifier.Where(x => x.Sender != null); var r = notifier.Select(x => { // ensure cast to TValue will succeed, throw useful exception otherwise var val = x.GetValue(); if (val != null && !(val is TValue)) { throw new InvalidCastException(string.Format("Unable to cast from {0} to {1}.", val.GetType(), typeof(TValue))); } return(new ObservedChange <TSender, TValue>(source, expression, (TValue)val)); }); return(r.DistinctUntilChanged(x => x.Value)); }
/// <summary> /// Bind a command from the ViewModel to an explicitly specified control /// on the View. /// </summary> /// <typeparam name="TView">The view type.</typeparam> /// <typeparam name="TViewModel">The view model type.</typeparam> /// <typeparam name="TProp">The property type.</typeparam> /// <typeparam name="TControl">The control type.</typeparam> /// <typeparam name="TParam">The parameter type.</typeparam> /// <param name="viewModel">The View model.</param> /// <param name="view">The View.</param> /// <param name="vmProperty">The ViewModel command to bind.</param> /// <param name="controlProperty">The name of the control on the view.</param> /// <param name="withParameter">The ViewModel property to pass as the /// param of the ICommand.</param> /// <param name="toEvent">If specified, bind to the specific event /// instead of the default. /// NOTE: If this parameter is used inside WhenActivated, it's /// important to dispose the binding when the view is deactivated.</param> /// <returns>A class representing the binding. Dispose it to disconnect /// the binding.</returns> public IReactiveBinding <TView, TProp> BindCommand <TView, TViewModel, TProp, TControl, TParam>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TProp> > vmProperty, Expression <Func <TView, TControl> > controlProperty, Func <TParam> withParameter, string?toEvent = null) where TViewModel : class where TView : class, IViewFor <TViewModel> where TProp : ICommand { if (vmProperty is null) { throw new ArgumentNullException(nameof(vmProperty)); } if (controlProperty is null) { throw new ArgumentNullException(nameof(controlProperty)); } var vmExpression = Reflection.Rewrite(vmProperty.Body); var controlExpression = Reflection.Rewrite(controlProperty.Body); var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Cast <TProp>(); IDisposable bindingDisposable = BindCommandInternal(source, view, controlExpression, Observable.Defer(() => Observable.Return(withParameter())), toEvent ?? string.Empty, cmd => { if (!(cmd is IReactiveCommand rc)) { return(new RelayCommand(cmd.CanExecute, _ => cmd.Execute(withParameter()))); } return(ReactiveCommand.Create(() => ((ICommand)rc).Execute(null), rc.CanExecute)); });
/// <inheritdoc /> public IDisposable BindInteraction <TViewModel, TView, TInput, TOutput, TDontCare>( TViewModel?viewModel, TView view, Expression <Func <TViewModel, Interaction <TInput, TOutput> > > propertyName, Func <InteractionContext <TInput, TOutput>, IObservable <TDontCare> > handler) where TViewModel : class where TView : class, IViewFor { if (propertyName is null) { throw new ArgumentNullException(nameof(propertyName)); } if (handler is null) { throw new ArgumentNullException(nameof(handler)); } var vmExpression = Reflection.Rewrite(propertyName.Body); var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Cast <Interaction <TInput, TOutput> >(); var interactionDisposable = new SerialDisposable(); return(source .Where(x => x is not null) .Do(x => interactionDisposable.Disposable = x.RegisterHandler(handler)) .Finally(() => interactionDisposable.Dispose()) .Subscribe(_ => { }, ex => this.Log().Error(ex, $"{vmExpression} Interaction Binding received an Exception!"))); }
/// <summary> /// Bind a command from the ViewModel to an explicitly specified control /// on the View. /// </summary> /// <typeparam name="TView">The view type.</typeparam> /// <typeparam name="TViewModel">The view model type.</typeparam> /// <typeparam name="TProp">The property type.</typeparam> /// <typeparam name="TControl">The control type.</typeparam> /// <typeparam name="TParam">The parameter type.</typeparam> /// <returns>A class representing the binding. Dispose it to disconnect /// the binding.</returns> /// <param name="viewModel">The View model.</param> /// <param name="view">The View.</param> /// <param name="vmProperty">The ViewModel command to bind.</param> /// <param name="controlProperty">The name of the control on the view.</param> /// <param name="withParameter">The ViewModel property to pass as the /// param of the ICommand.</param> /// <param name="toEvent">If specified, bind to the specific event /// instead of the default. /// NOTE: If this parameter is used inside WhenActivated, it's /// important to dispose the binding when the view is deactivated.</param> public IReactiveBinding <TView, TViewModel, TProp> BindCommand <TView, TViewModel, TProp, TControl, TParam>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TProp> > vmProperty, Expression <Func <TView, TControl> > controlProperty, IObservable <TParam> withParameter, string toEvent = null) where TViewModel : class where TView : class, IViewFor <TViewModel> where TProp : ICommand { var vmExpression = Reflection.Rewrite(vmProperty.Body); var controlExpression = Reflection.Rewrite(controlProperty.Body); var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Cast <TProp>(); IDisposable bindingDisposable = BindCommandInternal(source, view, controlExpression, withParameter, toEvent); return(new ReactiveBinding <TView, TViewModel, TProp>( view, viewModel, controlExpression, vmExpression, source, BindingDirection.OneWay, bindingDisposable)); }
/// <summary> /// Bind a command from the ViewModel to an explicitly specified control /// on the View. /// </summary> /// <typeparam name="TView">The view type.</typeparam> /// <typeparam name="TViewModel">The view model type.</typeparam> /// <typeparam name="TProp">The property type.</typeparam> /// <typeparam name="TControl">The control type.</typeparam> /// <typeparam name="TParam">The parameter type.</typeparam> /// <param name="viewModel">The View model.</param> /// <param name="view">The View.</param> /// <param name="vmProperty">The ViewModel command to bind.</param> /// <param name="controlProperty">The name of the control on the view.</param> /// <param name="withParameter">The ViewModel property to pass as the /// param of the ICommand.</param> /// <param name="toEvent">If specified, bind to the specific event /// instead of the default. /// NOTE: If this parameter is used inside WhenActivated, it's /// important to dispose the binding when the view is deactivated.</param> /// <returns>A class representing the binding. Dispose it to disconnect /// the binding.</returns> public IReactiveBinding <TView, TViewModel, TProp> BindCommand <TView, TViewModel, TProp, TControl, TParam>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TProp> > vmProperty, Expression <Func <TView, TControl> > controlProperty, Func <TParam> withParameter, string toEvent = null) where TViewModel : class where TView : class, IViewFor <TViewModel> where TProp : ICommand { var vmExpression = Reflection.Rewrite(vmProperty.Body); var controlExpression = Reflection.Rewrite(controlProperty.Body); var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Cast <TProp>(); IDisposable bindingDisposable = BindCommandInternal(source, view, controlExpression, Observable.Defer(() => Observable.Return(withParameter())), toEvent, cmd => { var rc = cmd as IReactiveCommand; if (rc == null) { return(new RelayCommand(cmd.CanExecute, _ => cmd.Execute(withParameter()))); } return(ReactiveCommand.Create(() => ((ICommand)rc).Execute(null), rc.CanExecute)); }); return(new ReactiveBinding <TView, TViewModel, TProp>( view, viewModel, controlExpression, vmExpression, source, BindingDirection.OneWay, bindingDisposable)); }
/// <summary> /// Converts to observable. /// </summary> /// <typeparam name="TSource">The type of the view model.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="expression">The expression.</param> /// <param name="source">The view model.</param> /// <param name="beforeChange">if set to <c>true</c> [before change].</param> /// <param name="skipInitial">if set to <c>true</c> [skip initial].</param> /// <returns> /// An observable Result. /// </returns> public static IObservable <TResult?> ToObservable <TSource, TResult>( this Expression <Func <TSource, TResult?> > expression, TSource?source, bool beforeChange = false, bool skipInitial = false) // TODO: Create Test { var sParam = Reflection.Rewrite(expression.Body); return(source.SubscribeToExpressionChain <TSource, TResult?>(sParam, beforeChange, skipInitial, RxApp.SuppressViewCommandBindingMessage) .Select(x => x.GetValue()) .Retry()); }
public static IReactiveBinding <TView, TViewModel, TProp> BindCommand <TView, TViewModel, TProp, TControl, TParam>( this ICommandBinderImplementation This, TViewModel viewModel, TView view, Expression <Func <TViewModel, TProp> > propertyName, Expression <Func <TView, TControl> > controlName, Expression <Func <TViewModel, TParam> > withParameter, string toEvent = null) where TViewModel : class where TView : class, IViewFor <TViewModel> where TProp : ICommand { var paramExpression = Reflection.Rewrite(withParameter.Body); var param = Reflection.ViewModelWhenAnyValue(viewModel, view, paramExpression); return(This.BindCommand(viewModel, view, propertyName, controlName, param, toEvent)); }
private static ObservableAsPropertyHelper <TRet> ObservableToProperty <TObj, TRet>( this TObj target, IObservable <TRet> observable, Expression <Func <TObj, TRet> > property, TRet initialValue = default(TRet), bool deferSubscription = false, IScheduler?scheduler = null) where TObj : class, IReactiveObject { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (observable == null) { throw new ArgumentNullException(nameof(observable)); } if (property == null) { throw new ArgumentNullException(nameof(property)); } Expression expression = Reflection.Rewrite(property.Body); if (expression.GetParent().NodeType != ExpressionType.Parameter) { throw new ArgumentException("Property expression must be of the form 'x => x.SomeProperty'"); } var name = expression.GetMemberInfo().Name; if (expression is IndexExpression) { name += "[]"; } return(new ObservableAsPropertyHelper <TRet>( observable, _ => target.RaisingPropertyChanged(name), _ => target.RaisingPropertyChanging(name), initialValue, deferSubscription, scheduler)); }
/// <summary> /// Creates a one-way binding, i.e. a binding that flows from the /// <paramref name="viewModel"/> to the <paramref name="view"/> only. This binding will /// attempt to convert the value of the view model property to the view property if they /// are not of the same type. /// </summary> /// <typeparam name="TViewModel">The type of the view model that is bound.</typeparam> /// <typeparam name="TView">The type of the view that is bound.</typeparam> /// <typeparam name="TVMProp">The type of the property bound on the view model.</typeparam> /// <typeparam name="TVProp">The type of the property bound on the view.</typeparam> /// <param name="viewModel">The instance of the view model to bind to.</param> /// <param name="view">The instance of the view to bind to.</param> /// <param name="vmProperty"> /// An expression representing the property to be bound to on the view model. /// This can be a child property, for example <c>x => x.Foo.Bar.Baz</c> in which case /// the binding will attempt to subscribe recursively to updates in order to /// always get the last value of the property chain. /// </param> /// <param name="viewProperty"> /// An expression representing the property to be bound to on the view. /// This can be a child property, for example <c>x => x.Foo.Bar.Baz</c> in which case /// the binding will attempt to subscribe recursively to updates in order to /// always set the correct property. /// /// If it is left null, the framework will attempt to automagically figure out /// the control and property that is to be bound, by looking for a control of the /// same name as the <paramref name="vmProperty"/>, and its most natural property. /// </param> /// <param name="conversionHint"> /// An object that can provide a hint for the converter. /// The semantics of this object is defined by the converter used. /// </param> /// <param name="vmToViewConverterOverride"> /// Delegate to convert the value of the view model's property's type to a value of the /// view's property's type. /// </param> /// <returns> /// An instance of <see cref="IDisposable"/> that, when disposed, /// disconnects the binding. /// </returns> /// <exception cref="ArgumentException"> /// There is no registered converter from <typeparamref name="TVMProp"/> to <typeparamref name="TVProp"/>. /// </exception> public IReactiveBinding <TView, TViewModel, TVProp> OneWayBind <TViewModel, TView, TVMProp, TVProp>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TVMProp> > vmProperty, Expression <Func <TView, TVProp> > viewProperty, object conversionHint = null, IBindingTypeConverter vmToViewConverterOverride = null) where TViewModel : class where TView : class, IViewFor { var vmExpression = Reflection.Rewrite(vmProperty.Body); var viewExpression = Reflection.Rewrite(viewProperty.Body); var viewType = viewExpression.Type; var converter = vmToViewConverterOverride ?? GetConverterForTypes(typeof(TVMProp), viewType); if (converter == null) { throw new ArgumentException($"Can't convert {typeof(TVMProp)} to {viewType}. To fix this, register a IBindingTypeConverter"); } var ret = EvalBindingHooks(viewModel, view, vmExpression, viewExpression, BindingDirection.OneWay); if (!ret) { return(null); } var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression) .SelectMany(x => { object tmp; if (!converter.TryConvert(x, viewType, conversionHint, out tmp)) { return(Observable <TVProp> .Empty); } return(Observable.Return(tmp == null ? default(TVProp) : (TVProp)tmp)); }); IDisposable disp = BindToDirect(source, view, viewExpression); return(new ReactiveBinding <TView, TViewModel, TVProp>(view, viewModel, viewExpression, vmExpression, source, BindingDirection.OneWay, disp)); }
/// <summary> /// BindTo takes an Observable stream and applies it to a target /// property. Conceptually it is similar to <c>Subscribe(x => /// target.property = x)</c>, but allows you to use child properties /// without the null checks. /// </summary> /// <typeparam name="TValue">The source type.</typeparam> /// <typeparam name="TTarget">The target object type.</typeparam> /// <typeparam name="TTValue">The type of the property on the target object.</typeparam> /// <param name="observedChange">The observable to apply to the target property.</param> /// <param name="target">The target object whose property will be set.</param> /// <param name="propertyExpression"> /// An expression representing the target property to set. /// This can be a child property (i.e. <c>x.Foo.Bar.Baz</c>).</param> /// <param name="conversionHint"> /// An object that can provide a hint for the converter. /// The semantics of this object is defined by the converter used. /// </param> /// <param name="vmToViewConverterOverride"> /// Delegate to convert the value of the view model's property's type to a value of the /// view's property's type. /// </param> /// <returns>An object that when disposed, disconnects the binding.</returns> public IDisposable BindTo <TValue, TTarget, TTValue>( IObservable <TValue> observedChange, TTarget target, Expression <Func <TTarget, TTValue> > propertyExpression, object conversionHint = null, IBindingTypeConverter vmToViewConverterOverride = null) where TTarget : class { if (target == null) { throw new ArgumentNullException(nameof(target)); } var viewExpression = Reflection.Rewrite(propertyExpression.Body); var ret = EvalBindingHooks(observedChange, target, null, viewExpression, BindingDirection.OneWay); if (!ret) { return(Disposable.Empty); } var converter = vmToViewConverterOverride ?? GetConverterForTypes(typeof(TValue), typeof(TTValue)); if (converter == null) { throw new ArgumentException($"Can't convert {typeof(TValue)} to {typeof(TTValue)}. To fix this, register a IBindingTypeConverter"); } var source = observedChange.SelectMany(x => { object tmp; if (!converter.TryConvert(x, typeof(TTValue), conversionHint, out tmp)) { return(Observable <TTValue> .Empty); } return(Observable.Return(tmp == null ? default(TTValue) : (TTValue)tmp)); }); return(BindToDirect(source, target, viewExpression)); }
public IReactiveBinding <TView, TViewModel, TProp> BindCommand <TView, TViewModel, TProp, TControl, TParam>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TProp> > vmProperty, Expression <Func <TView, TControl> > controlProperty, Func <TParam> withParameter, string toEvent = null) where TViewModel : class where TView : class, IViewFor <TViewModel> where TProp : ICommand { var vmExpression = Reflection.Rewrite(vmProperty.Body); var controlExpression = default(Expression); if (controlProperty == null) { controlExpression = Reflection.getViewExpression(view, vmExpression); } else { controlExpression = Reflection.Rewrite(controlProperty.Body); } var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Cast <TProp>(); IDisposable bindingDisposable = bindCommandInternal(source, view, controlExpression, Observable.Empty <object>(), toEvent, cmd => { var rc = cmd as IReactiveCommand; if (rc == null) { return(new RelayCommand(cmd.CanExecute, _ => cmd.Execute(withParameter()))); } var ret = ReactiveCommand.Create(rc.CanExecuteObservable); ret.Subscribe(_ => rc.Execute(withParameter())); return(ret); }); return(new ReactiveBinding <TView, TViewModel, TProp>(view, viewModel, controlExpression, vmExpression, source, BindingDirection.OneWay, bindingDisposable)); }
/// <summary> /// Creates a one way binding with a selector, i.e. a binding that flows from the /// <paramref name="viewModel"/> to the <paramref name="view"/> only, and where the value of the view model /// property is mapped through the <paramref name="selector"/> before being set to the view. /// </summary> /// <typeparam name="TViewModel">The type of the view model that is bound.</typeparam> /// <typeparam name="TView">The type of the view that is bound.</typeparam> /// <typeparam name="TProp">The type of the property bound on the view model.</typeparam> /// <typeparam name="TOut">The return type of the <paramref name="selector"/>.</typeparam> /// <param name="viewModel">The instance of the view model to bind to.</param> /// <param name="view">The instance of the view to bind to.</param> /// <param name="vmProperty"> /// An expression representing the property to be bound to on the view model. /// This can be a child property, for example <c>x => x.Foo.Bar.Baz</c> in which case /// the binding will attempt to subscribe recursively to updates in order to /// always get the last value of the property chain. /// </param> /// <param name="viewProperty"> /// An expression representing the property to be bound to on the view. /// This can be a child property, for example <c>x => x.Foo.Bar.Baz</c> in which case /// the binding will attempt to subscribe recursively to updates in order to /// always set the correct property. /// /// If it is left null, the framework will attempt to automagically figure out /// the control and property that is to be bound, by looking for a control of the /// same name as the <paramref name="vmProperty"/>, and its most natural property. /// </param> /// <param name="selector"> /// A function that will be used to transform the values of the property on the view model /// before being bound to the view property. /// </param> /// <returns> /// An instance of <see cref="IDisposable"/> that, when disposed, /// disconnects the binding. /// </returns> public IReactiveBinding <TView, TViewModel, TOut> OneWayBind <TViewModel, TView, TProp, TOut>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TProp> > vmProperty, Expression <Func <TView, TOut> > viewProperty, Func <TProp, TOut> selector) where TViewModel : class where TView : class, IViewFor { var vmExpression = Reflection.Rewrite(vmProperty.Body); var viewExpression = Reflection.Rewrite(viewProperty.Body); var ret = EvalBindingHooks(viewModel, view, vmExpression, viewExpression, BindingDirection.OneWay); if (!ret) { return(null); } var source = Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Select(x => (TProp)x).Select(selector); IDisposable disp = BindToDirect(source, view, viewExpression); return(new ReactiveBinding <TView, TViewModel, TOut>(view, viewModel, viewExpression, vmExpression, source, BindingDirection.OneWay, disp)); }
private IReactiveBinding <TView, TViewModel, Tuple <object, bool> > BindImpl <TViewModel, TView, TVMProp, TVProp, TDontCare>( TViewModel viewModel, TView view, Expression <Func <TViewModel, TVMProp> > vmProperty, Expression <Func <TView, TVProp> > viewProperty, IObservable <TDontCare> signalViewUpdate, OutFunc <TVMProp, TVProp> vmToViewConverter, OutFunc <TVProp, TVMProp> viewToVmConverter) where TView : class, IViewFor where TViewModel : class { var signalInitialUpdate = new Subject <bool>(); var vmExpression = Reflection.Rewrite(vmProperty.Body); var viewExpression = Reflection.Rewrite(viewProperty.Body); var signalObservable = signalViewUpdate != null ? signalViewUpdate.Select(_ => false) : view.WhenAnyDynamic(viewExpression, x => (TVProp)x.Value).Select(_ => false); var somethingChanged = Observable.Merge( Reflection.ViewModelWhenAnyValue(viewModel, view, vmExpression).Select(_ => true), signalInitialUpdate.Select(_ => true), signalObservable); var changeWithValues = somethingChanged.Select(isVm => { TVMProp vmValue; TVProp vValue; if (!Reflection.TryGetValueForPropertyChain(out vmValue, view.ViewModel, vmExpression.GetExpressionChain()) || !Reflection.TryGetValueForPropertyChain(out vValue, view, viewExpression.GetExpressionChain())) { return(null); } if (isVm) { TVProp vmAsView; if (!vmToViewConverter(vmValue, out vmAsView) || EqualityComparer <TVProp> .Default.Equals(vValue, vmAsView)) { return(null); } return(Tuple.Create((object)vmAsView, isVm)); } TVMProp vAsViewModel; if (!viewToVmConverter(vValue, out vAsViewModel) || EqualityComparer <TVMProp> .Default.Equals(vmValue, vAsViewModel)) { return(null); } return(Tuple.Create((object)vAsViewModel, isVm)); }); var ret = EvalBindingHooks(viewModel, view, vmExpression, viewExpression, BindingDirection.TwoWay); if (!ret) { return(null); } IObservable <Tuple <object, bool> > changes = changeWithValues.Where(tuple => tuple != null).Publish().RefCount(); IDisposable disp = changes.Subscribe(isVmWithLatestValue => { if (isVmWithLatestValue.Item2) { Reflection.TrySetValueToPropertyChain(view, viewExpression.GetExpressionChain(), isVmWithLatestValue.Item1, false); } else { Reflection.TrySetValueToPropertyChain(view.ViewModel, vmExpression.GetExpressionChain(), 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(new ReactiveBinding <TView, TViewModel, Tuple <object, bool> >( view, viewModel, viewExpression, vmExpression, changes, BindingDirection.TwoWay, disp)); }