/// <summary>
        /// ObservableForProperty returns an Observable representing the
        /// property change notifications for a specific property on a
        /// ReactiveObject. This method (unlike other Observables that return
        /// IObservedChange) guarantees that the Value property of
        /// the IObservedChange is set.
        /// </summary>
        /// <param name="property">An Expression representing the property (i.e.
        /// 'x => x.SomeProperty.SomeOtherProperty'</param>
        /// <param name="beforeChange">If True, the Observable will notify
        /// immediately before a property is going to change.</param>
        /// <returns>An Observable representing the property change
        /// notifications for the given property.</returns>
        public static IObservable <IObservedChange <TSender, TValue> > ObservableForProperty <TSender, TValue>(
            this TSender This,
            Expression <Func <TSender, TValue> > property,
            bool beforeChange = false,
            bool skipInitial  = true)
        {
            if (This == null)
            {
                throw new ArgumentNullException("Sender");
            }

            var propertyNames = Reflection.ExpressionToPropertyNames(property);

            /* x => x.Foo.Bar.Baz;
             *
             * Subscribe to This, look for Foo
             * Subscribe to Foo, look for Bar
             * Subscribe to Bar, look for Baz
             * Subscribe to Baz, publish to Subject
             * Return Subject
             *
             * If Bar changes (notification fires on Foo), resubscribe to new Bar
             *  Resubscribe to new Baz, publish to Subject
             *
             * If Baz changes (notification fires on Bar),
             *  Resubscribe to new Baz, publish to Subject
             */

            return(SubscribeToExpressionChain <TSender, TValue>(
                       This,
                       propertyNames,
                       beforeChange,
                       skipInitial));
        }
Esempio n. 2
0
 /// <summary>
 /// Given a fully filled-out IObservedChange object, SetValueToProperty
 /// will apply it to the specified object (i.e. it will ensure that
 /// target.property == This.GetValue() and "replay" the observed change
 /// onto another object)
 /// </summary>
 /// <param name="target">The target object to apply the change to.</param>
 /// <param name="property">The target property to apply the change to.</param>
 public static void SetValueToProperty <TSender, TValue, TTarget>(
     this IObservedChange <TSender, TValue> This,
     TTarget target,
     Expression <Func <TTarget, TValue> > property)
 {
     Reflection.SetValueToPropertyChain(target, Reflection.ExpressionToPropertyNames(property), This.GetValue());
 }
        /// <summary>
        /// WhenAny allows you to observe whenever one or more properties on an
        /// object have changed, providing an initial value when the Observable
        /// is set up, unlike ObservableForProperty(). Use this method in
        /// constructors to set up bindings between properties that also need an
        /// initial setup.
        /// </summary>
        public static IObservable <TRet> WhenAny <TSender, TRet, T1, T2>(this TSender This,
                                                                         Expression <Func <TSender, T1> > property1,
                                                                         Expression <Func <TSender, T2> > property2,
                                                                         Func <IObservedChange <TSender, T1>, IObservedChange <TSender, T2>, TRet> selector)
        {
            var slot1 = new ObservedChange <TSender, T1>()
            {
                Sender       = This,
                PropertyName = String.Join(".", Reflection.ExpressionToPropertyNames(property1)),
            };
            T1 slot1Value = default(T1); slot1.TryGetValue(out slot1Value); slot1.Value = slot1Value;
            IObservedChange <TSender, T1> islot1 = slot1;
            var slot2 = new ObservedChange <TSender, T2>()
            {
                Sender       = This,
                PropertyName = String.Join(".", Reflection.ExpressionToPropertyNames(property2)),
            };
            T2 slot2Value = default(T2); slot2.TryGetValue(out slot2Value); slot2.Value = slot2Value;
            IObservedChange <TSender, T2> islot2 = slot2;

            return(Observable.Create <TRet>(subject => {
                subject.OnNext(selector(islot1, islot2));

                return Observable.Merge(This.ObservableForProperty(property1).Do(x => { lock (slot1) { islot1 = x.fillInValue(); } }).Select(x => selector(islot1, islot2)), This.ObservableForProperty(property2).Do(x => { lock (slot2) { islot2 = x.fillInValue(); } }).Select(x => selector(islot1, islot2))).Subscribe(subject);
            }));
        }
Esempio n. 4
0
        /// <summary>
        /// Returns the name of a property which has been changed.
        /// </summary>
        /// <typeparam name="TSender">The sender type.</typeparam>
        /// <typeparam name="TValue">The value type.</typeparam>
        /// <param name="item">The observed change.</param>
        /// <returns>
        /// The name of the property which has changed.
        /// </returns>
        public static string GetPropertyName <TSender, TValue>(this IObservedChange <TSender, TValue> item)
        {
            if (item is null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            return(Reflection.ExpressionToPropertyNames(item.Expression));
        }
Esempio n. 5
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. 6
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. 7
0
        /// <summary>
        /// BindTo takes an Observable stream and applies it to a target
        /// property. Conceptually it is similar to "Subscribe(x =&gt;
        /// target.property = x)", but allows you to use child properties
        /// without the null checks.
        /// </summary>
        /// <param name="target">The target object whose property will be set.</param>
        /// <param name="property">An expression representing the target
        /// property to set. This can be a child property (i.e. x.Foo.Bar.Baz).</param>
        /// <returns>An object that when disposed, disconnects the binding.</returns>
        public static IDisposable BindTo <TTarget, TValue>(
            this IObservable <TValue> This,
            TTarget target,
            Expression <Func <TTarget, TValue> > property,
            Func <TValue> fallbackValue = null)
        {
            var pn = Reflection.ExpressionToPropertyNames(property);

            var lastValue = default(TValue);

            return(Observable.Merge(target.WhenAny(property, _ => lastValue).Skip(1), This)
                   .Subscribe(x => {
                lastValue = x;
                Reflection.SetValueToPropertyChain(target, pn, x);
            }));
        }
Esempio n. 8
0
        /// <summary>
        /// ObservableForProperty returns an Observable representing the
        /// property change notifications for a specific property on a
        /// ReactiveObject. This method (unlike other Observables that return
        /// IObservedChange) guarantees that the Value property of
        /// the IObservedChange is set.
        /// </summary>
        /// <param name="property">An Expression representing the property (i.e.
        /// 'x => x.SomeProperty.SomeOtherProperty'</param>
        /// <param name="beforeChange">If True, the Observable will notify
        /// immediately before a property is going to change.</param>
        /// <returns>An Observable representing the property change
        /// notifications for the given property.</returns>
        public static IObservable <IObservedChange <TSender, TValue> > ObservableForProperty <TSender, TValue>(
            this TSender This,
            Expression <Func <TSender, TValue> > property,
            bool beforeChange = false)
        {
            var propertyNames = new LinkedList <string>(Reflection.ExpressionToPropertyNames(property));
            var subscriptions = new LinkedList <IDisposable>(propertyNames.Select(x => (IDisposable)null));
            var ret           = new Subject <IObservedChange <TSender, TValue> >();

            if (This == null)
            {
                throw new ArgumentNullException("Sender");
            }

            /* x => x.Foo.Bar.Baz;
             *
             * Subscribe to This, look for Foo
             * Subscribe to Foo, look for Bar
             * Subscribe to Bar, look for Baz
             * Subscribe to Baz, publish to Subject
             * Return Subject
             *
             * If Bar changes (notification fires on Foo), resubscribe to new Bar
             *  Resubscribe to new Baz, publish to Subject
             *
             * If Baz changes (notification fires on Bar),
             *  Resubscribe to new Baz, publish to Subject
             */

            subscribeToExpressionChain(
                This,
                buildPropPathFromNodePtr(propertyNames.First),
                This,
                propertyNames.First,
                subscriptions.First,
                beforeChange,
                ret);

            return(Observable.Create <IObservedChange <TSender, TValue> >(x => {
                var disp = ret.Subscribe(x);
                return () => {
                    subscriptions.ForEach(y => y.Dispose());
                    disp.Dispose();
                };
            }));
        }
Esempio n. 9
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. 10
0
 /// <summary>
 /// Returns the current value of a property given a notification that
 /// it has changed.
 /// </summary>
 /// <returns>The current value of the property</returns>
 public static string GetPropertyName <TSender, TValue>(this IObservedChange <TSender, TValue> This)
 {
     return(Reflection.ExpressionToPropertyNames(This.Expression));
 }
Esempio n. 11
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);
        }
Esempio n. 12
0
        public IReactiveBinding <TView, TViewModel, TProp> BindCommand <TView, TViewModel, TProp, TControl, TParam>(
            TViewModel viewModel,
            TView view,
            Expression <Func <TViewModel, TProp> > propertyName,
            Expression <Func <TView, TControl> > controlName,
            IObservable <TParam> withParameter,
            string toEvent = null)
            where TViewModel : class
            where TView : class, IViewFor <TViewModel>
            where TProp : ICommand
        {
            var ctlName        = Reflection.SimpleExpressionToPropertyName(controlName);
            var viewPropGetter = Reflection.GetValueFetcherForProperty(typeof(TView), ctlName);

            IObservable <TProp> changed;
            IDisposable         bindingDisposable = bindCommandInternal(viewModel, view, propertyName, viewPropGetter, withParameter, toEvent, out changed);

            return(new ReactiveBinding <TView, TViewModel, TProp>(view, viewModel, new string[] { ctlName }, Reflection.ExpressionToPropertyNames(propertyName),
                                                                  changed, BindingDirection.OneWay, bindingDisposable));
        }