/// <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));
        }
Esempio n. 2
0
        /// <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));
            });
Esempio n. 5
0
        /// <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));
        }
Esempio n. 6
0
        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);
                }));
            }
        }
Esempio n. 7
0
        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();
            }));
        }
Esempio n. 8
0
        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));
        }
Esempio n. 9
0
        /// <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 =&gt; 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 =&gt; 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));
        }
Esempio n. 10
0
        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));
        }
Esempio n. 11
0
        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));
        }
Esempio n. 12
0
        /// <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 =&gt; 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 =&gt; 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));
        }
Esempio n. 13
0
        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));
        }
Esempio n. 14
0
        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);
        }