/// <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)); }
/// <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> /// <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> /// 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)); });
/// <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> /// <exception cref="ArgumentNullException">nameof(vmProperty) /// or /// nameof(vmProperty).</exception> public IReactiveBinding <TView, TProp> BindCommand <TView, TViewModel, TProp, TControl, TParam>( TViewModel?viewModel, TView view, Expression <Func <TViewModel, TProp?> > vmProperty, Expression <Func <TView, TControl> > controlProperty, Expression <Func <TViewModel, TParam?> > withParameter, string?toEvent = null) where TView : class, IViewFor <TViewModel> where TViewModel : class 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>(); var bindingDisposable = BindCommandInternal(source, view, controlExpression, withParameter.ToObservable(viewModel), toEvent); return(new ReactiveBinding <TView, TProp>( view, controlExpression, vmExpression, source, BindingDirection.OneWay, bindingDisposable)); }
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(); })); }
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)); }
/// <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)); }
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)); }
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> /// 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)); }
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); }