/// <summary> /// Attempts to return the current value of a property given a /// notification that it has changed. If any property in the /// property expression is null, false is returned. /// </summary> /// <param name="This"> /// The <see cref="IObservedChange{TSender, TValue}"/> instance to get the value of /// </param> /// <param name="changeValue"> /// The value of the property expression. /// </param> /// <returns> /// True if the entire expression was able to be followed, false otherwise. /// </returns> internal static bool TryGetValue <TSender, TValue>(this IObservedChange <TSender, TValue> This, out TValue changeValue) { if (!Equals(This.Value, default(TValue))) { changeValue = This.Value; return(true); } return(Reflection.TryGetValueForPropertyChain(out changeValue, This.Sender, This.Expression.GetExpressionChain())); }
/// <summary> /// Attempts to return the current value of a property given a /// notification that it has changed. If any property in the /// property expression is null, false is returned. /// </summary> /// <param name="changeValue">The value of the property /// expression.</param> /// <returns>True if the entire expression was able to be followed, /// false otherwise</returns> public static bool TryGetValue <TSender, TValue>(this IObservedChange <TSender, TValue> This, out TValue changeValue) { if (!Equals(This.Value, default(TValue))) { changeValue = This.Value; return(true); } object current = This.Sender; string fullPropName = This.PropertyName; return(Reflection.TryGetValueForPropertyChain(out changeValue, current, fullPropName.Split('.'))); }
/// <summary> /// Attempts to return the current value of a property given a /// notification that it has changed. If any property in the /// property expression is null, false is returned. /// </summary> /// <typeparam name="TSender">The sender type.</typeparam> /// <typeparam name="TValue">The value type.</typeparam> /// <param name="item"> /// The <see cref="IObservedChange{TSender, TValue}"/> instance to get the value of. /// </param> /// <param name="changeValue"> /// The value of the property expression. /// </param> /// <returns> /// True if the entire expression was able to be followed, false otherwise. /// </returns> internal static bool TryGetValue <TSender, TValue>(this IObservedChange <TSender, TValue> item, out TValue changeValue) { if (Equals(item.Sender, null)) { throw new ArgumentNullException(nameof(item), "Sender of the item is null"); } if (!Equals(item.Value, default(TValue))) { changeValue = item.Value; return(true); } return(Reflection.TryGetValueForPropertyChain(out changeValue, item.Sender, item.Expression.GetExpressionChain())); }
static IObservedChange <object, object> observedChangeFor(Expression expression, IObservedChange <object, object> sourceChange) { var propertyName = expression.GetMemberInfo().Name; if (sourceChange.Value == null) { return(new ObservedChange <object, object>(sourceChange.Value, expression));; } else { object value; // expression is always a simple expression Reflection.TryGetValueForPropertyChain(out value, sourceChange.Value, new[] { expression }); return(new ObservedChange <object, object>(sourceChange.Value, expression, value)); } }
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); }