/// <summary> /// Applies a binding subject to a property on an instance. /// </summary> /// <param name="target">The target instance.</param> /// <param name="property">The target property.</param> /// <param name="subject">The binding subject.</param> internal void Bind(IPerspexObject target, PerspexProperty property, ISubject <object> subject) { var mode = Mode == BindingMode.Default ? property.GetMetadata(target.GetType()).DefaultBindingMode : Mode; switch (mode) { case BindingMode.Default: case BindingMode.OneWay: target.Bind(property, subject, Priority); break; case BindingMode.TwoWay: throw new NotSupportedException("TwoWay MultiBinding not currently supported."); case BindingMode.OneTime: target.GetObservable(Control.DataContextProperty).Subscribe(dataContext => { subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority)); }); break; case BindingMode.OneWayToSource: target.GetObservable(property).Subscribe(subject); break; } }
/// <inheritdoc/> public InstancedBinding Initiate( IPerspexObject target, PerspexProperty targetProperty, object anchor = null) { if (Name == "Red") { } var host = (target as IControl) ?? (anchor as IControl); var style = anchor as IStyle; var resource = PerspexProperty.UnsetValue; if (host != null) { resource = host.FindStyleResource(Name); } else if (style != null) { resource = style.FindResource(Name); } if (resource != PerspexProperty.UnsetValue) { return(new InstancedBinding(resource, Priority)); } else { return(null); } }
/// <summary> /// Gets an observable for a <see cref="PerspexProperty"/>. /// </summary> /// <param name="o">The object.</param> /// <typeparam name="T">The type of the property.</typeparam> /// <param name="property">The property.</param> /// <returns> /// An observable which when subscribed pushes the old and new values of the property each /// time it is changed. /// </returns> public static IObservable <Tuple <T, T> > GetObservableWithHistory <T>( this IPerspexObject o, PerspexProperty <T> property) { Contract.Requires <ArgumentNullException>(o != null); Contract.Requires <ArgumentNullException>(property != null); return(new PerspexObservable <Tuple <T, T> >( observer => { EventHandler <PerspexPropertyChangedEventArgs> handler = (s, e) => { if (e.Property == property) { observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue)); } }; o.PropertyChanged += handler; return Disposable.Create(() => { o.PropertyChanged -= handler; }); }, GetDescription(o, property))); }
/// <summary> /// Gets an observable for a <see cref="PerspexProperty"/>. /// </summary> /// <param name="o">The object.</param> /// <param name="property">The property.</param> /// <returns>An observable.</returns> public static IObservable <object> GetObservable(this IPerspexObject o, PerspexProperty property) { Contract.Requires <ArgumentNullException>(o != null); Contract.Requires <ArgumentNullException>(property != null); return(new PerspexObservable <object>( observer => { EventHandler <PerspexPropertyChangedEventArgs> handler = (s, e) => { if (e.Property == property) { observer.OnNext(e.NewValue); } }; observer.OnNext(o.GetValue(property)); o.PropertyChanged += handler; return Disposable.Create(() => { o.PropertyChanged -= handler; }); }, GetDescription(o, property))); }
/// <summary> /// Gets an observable for a <see cref="PerspexProperty"/>. /// </summary> /// <param name="o">The object.</param> /// <typeparam name="T">The property type.</typeparam> /// <param name="property">The property.</param> /// <returns>An observable.</returns> public static IObservable <T> GetObservable <T>(this IPerspexObject o, PerspexProperty <T> property) { Contract.Requires <ArgumentNullException>(o != null); Contract.Requires <ArgumentNullException>(property != null); return(o.GetObservable((PerspexProperty)property).Cast <T>()); }
/// <summary> /// Binds a property to a subject according to a <see cref="BindingMode"/>. /// </summary> /// <param name="o">The object.</param> /// <param name="property">The property to bind.</param> /// <param name="source">The binding source.</param> /// <param name="mode">The binding mode.</param> /// <param name="priority">The binding priority.</param> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> public static IDisposable Bind( this IPerspexObject o, PerspexProperty property, ISubject <object> source, BindingMode mode, BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires <ArgumentNullException>(o != null); Contract.Requires <ArgumentNullException>(property != null); Contract.Requires <ArgumentNullException>(source != null); switch (mode) { case BindingMode.Default: case BindingMode.OneWay: return(o.Bind(property, source, priority)); case BindingMode.TwoWay: return(new CompositeDisposable( o.Bind(property, source, priority), o.GetObservable(property).Subscribe(source))); case BindingMode.OneTime: return(source.Take(1).Subscribe(x => o.SetValue(property, x, priority))); case BindingMode.OneWayToSource: return(o.GetObservable(property).Subscribe(source)); default: throw new ArgumentException("Invalid binding mode."); } }
private ExpressionObserver CreateDataContexObserver( IPerspexObject target, string path, bool targetIsDataContext) { Contract.Requires <ArgumentNullException>(target != null); if (!targetIsDataContext) { var update = target.GetObservable(Control.DataContextProperty) .Skip(1) .Select(_ => Unit.Default); var result = new ExpressionObserver( () => target.GetValue(Control.DataContextProperty), path, update); return(result); } else { return(new ExpressionObserver( target.GetObservable(Visual.VisualParentProperty) .OfType <IPerspexObject>() .Select(x => x.GetObservable(Control.DataContextProperty)) .Switch(), path)); } }
/// <summary> /// Applies the binding to a property on an instance. /// </summary> /// <param name="instance">The target instance.</param> /// <param name="property">The target property.</param> public void Bind(IPerspexObject instance, PerspexProperty property) { var subject = CreateSubject(instance, property); if (subject != null) { Bind(instance, property, subject); } }
/// <summary> /// Applies the binding to a property on an instance. /// </summary> /// <param name="instance">The target instance.</param> /// <param name="property">The target property.</param> public void Bind(IPerspexObject instance, PerspexProperty property) { var subject = CreateSubject(instance, property); if (subject != null) { Bind(instance, property, subject); } }
/// <inheritdoc/> void IDirectPropertyAccessor.SetValue(IPerspexObject instance, object value) { if (Setter == null) { throw new ArgumentException($"The property {Name} is readonly."); } Setter((TOwner)instance, (TValue)value); }
/// <summary> /// Gets a weak observable for a <see cref="PerspexProperty"/>. /// </summary> /// <param name="o">The object.</param> /// <param name="property">The property.</param> /// <returns>An observable.</returns> public static IObservable <object> GetWeakObservable(this IPerspexObject o, PerspexProperty property) { Contract.Requires <ArgumentNullException>(o != null); Contract.Requires <ArgumentNullException>(property != null); return(new WeakPropertyChangedObservable( new WeakReference <IPerspexObject>(o), property, GetDescription(o, property))); }
/// <summary> /// Animates a <see cref="PerspexProperty"/>. /// </summary> /// <typeparam name="T">The property type.</typeparam> /// <param name="target">The target object.</param> /// <param name="property">The target property.</param> /// <param name="start">The value of the property at the start of the animation.</param> /// <param name="finish">The value of the property at the end of the animation.</param> /// <param name="easing">The easing function to use.</param> /// <param name="duration">The duration of the animation.</param> /// <returns>An <see cref="Animation"/> that can be used to track or stop the animation.</returns> public static Animation <T> Property <T>( IPerspexObject target, PerspexProperty <T> property, T start, T finish, IEasing <T> easing, TimeSpan duration) { var o = GetTimer(duration).Select(progress => easing.Ease(progress, start, finish)); return(new Animation <T>(o, target.Bind(property, o, BindingPriority.Animation))); }
/// <inheritdoc/> public InstancedBinding Initiate( IPerspexObject target, PerspexProperty targetProperty, object anchor = null) { Contract.Requires <ArgumentNullException>(target != null); var pathInfo = ParsePath(Path); ValidateState(pathInfo); ExpressionObserver observer; if (pathInfo.ElementName != null || ElementName != null) { observer = CreateElementObserver( (target as IControl) ?? (anchor as IControl), pathInfo.ElementName ?? ElementName, pathInfo.Path); } else if (Source != null) { observer = CreateSourceObserver(Source, pathInfo.Path); } else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext) { observer = CreateDataContexObserver( target, pathInfo.Path, targetProperty == Control.DataContextProperty, anchor); } else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent) { observer = CreateTemplatedParentObserver(target, pathInfo.Path); } else { throw new NotSupportedException(); } var subject = new ExpressionSubject( observer, targetProperty?.PropertyType ?? typeof(object), Converter ?? DefaultValueConverter.Instance, ConverterParameter, FallbackValue, Priority); return(new InstancedBinding(subject, Mode, Priority)); }
/// <summary> /// Creates a subject that can be used to get and set the value of the binding. /// </summary> /// <param name="target">The target instance.</param> /// <param name="targetProperty">The target property.</param> /// <returns>An <see cref="ISubject{Object}"/>.</returns> public ISubject<object> CreateSubject(IPerspexObject target, PerspexProperty targetProperty) { if (Converter == null) { throw new NotSupportedException("MultiBinding without Converter not currently supported."); } var targetType = targetProperty?.PropertyType ?? typeof(object); var result = new BehaviorSubject<object>(PerspexProperty.UnsetValue); var children = Bindings.Select(x => x.CreateSubject(target, null)); var input = children.CombineLatest().Select(x => ConvertValue(x, targetType)); input.Subscribe(result); return result; }
/// <summary> /// Called when the <see cref="DataContext"/> property begins and ends being notified. /// </summary> /// <param name="o">The object on which the DataContext is changing.</param> /// <param name="notifying">Whether the notifcation is beginning or ending.</param> private static void DataContextNotifying(IPerspexObject o, bool notifying) { var control = o as Control; if (control != null) { control.IsDataContextChanging = notifying; if (!notifying) { control.OnDataContextChanged(); } } }
/// <summary> /// Creates a subject that can be used to get and set the value of the binding. /// </summary> /// <param name="target">The target instance.</param> /// <param name="targetProperty">The target property.</param> /// <returns>An <see cref="ISubject{Object}"/>.</returns> public ISubject <object> CreateSubject(IPerspexObject target, PerspexProperty targetProperty) { if (Converter == null) { throw new NotSupportedException("MultiBinding without Converter not currently supported."); } var targetType = targetProperty?.PropertyType ?? typeof(object); var result = new BehaviorSubject <object>(PerspexProperty.UnsetValue); var children = Bindings.Select(x => x.CreateSubject(target, null)); var input = children.CombineLatest().Select(x => ConvertValue(x, targetType)); input.Subscribe(result); return(result); }
/// <summary> /// Applies an <see cref="InstancedBinding"/> a property on an <see cref="IPerspexObject"/>. /// </summary> /// <param name="target">The target object.</param> /// <param name="property">The property to bind.</param> /// <param name="binding">The instanced binding.</param> /// <param name="anchor"> /// An optional anchor from which to locate required context. When binding to objects that /// are not in the logical tree, certain types of binding need an anchor into the tree in /// order to locate named controls or resources. The <paramref name="anchor"/> parameter /// can be used to provice this context. /// </param> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> public static IDisposable Apply( IPerspexObject target, PerspexProperty property, InstancedBinding binding, object anchor) { Contract.Requires <ArgumentNullException>(target != null); Contract.Requires <ArgumentNullException>(property != null); Contract.Requires <ArgumentNullException>(binding != null); var mode = binding.Mode; if (mode == BindingMode.Default) { mode = property.GetMetadata(target.GetType()).DefaultBindingMode; } switch (mode) { case BindingMode.Default: case BindingMode.OneWay: return(target.Bind(property, binding.Observable ?? binding.Subject, binding.Priority)); case BindingMode.TwoWay: return(new CompositeDisposable( target.Bind(property, binding.Subject, binding.Priority), target.GetObservable(property).Subscribe(binding.Subject))); case BindingMode.OneTime: var source = binding.Subject ?? binding.Observable; if (source != null) { return(source.Take(1).Subscribe(x => target.SetValue(property, x, binding.Priority))); } else { target.SetValue(property, binding.Value, binding.Priority); return(Disposable.Empty); } case BindingMode.OneWayToSource: return(target.GetObservable(property).Subscribe(binding.Subject)); default: throw new ArgumentException("Invalid binding mode."); } }
private ExpressionObserver CreateTemplatedParentObserver( IPerspexObject target, string path) { Contract.Requires <ArgumentNullException>(target != null); var update = target.GetObservable(Control.TemplatedParentProperty) .Skip(1) .Select(_ => Unit.Default); var result = new ExpressionObserver( () => target.GetValue(Control.TemplatedParentProperty), path, update); return(result); }
/// <summary> /// Creates a subject that can be used to get and set the value of the binding. /// </summary> /// <param name="target">The target instance.</param> /// <param name="targetProperty">The target property. May be null.</param> /// <returns>An <see cref="ISubject{Object}"/>.</returns> public ISubject<object> CreateSubject( IPerspexObject target, PerspexProperty targetProperty) { Contract.Requires<ArgumentNullException>(target != null); var pathInfo = ParsePath(Path); ValidateState(pathInfo); ExpressionObserver observer; if (pathInfo.ElementName != null || ElementName != null) { observer = CreateElementObserver( (IControl)target, pathInfo.ElementName ?? ElementName, pathInfo.Path); } else if (Source != null) { observer = CreateSourceObserver(Source, pathInfo.Path); } else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext) { observer = CreateDataContexObserver( target, pathInfo.Path, targetProperty == Control.DataContextProperty); } else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent) { observer = CreateTemplatedParentObserver(target, pathInfo.Path); } else { throw new NotSupportedException(); } return new ExpressionSubject( observer, targetProperty?.PropertyType ?? typeof(object), Converter ?? DefaultValueConverter.Instance, ConverterParameter, FallbackValue); }
/// <summary> /// Gets a subject for a <see cref="PerspexProperty"/>. /// </summary> /// <typeparam name="T">The property type.</typeparam> /// <param name="o">The object.</param> /// <param name="property">The property.</param> /// <param name="priority"> /// The priority with which binding values are written to the object. /// </param> /// <returns> /// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the /// property. /// </returns> public static ISubject <T> GetSubject <T>( this IPerspexObject o, PerspexProperty <T> property, BindingPriority priority = BindingPriority.LocalValue) { // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the // AnonymousSubject classes from this file and use Subject.Create<T>. var output = new Subject <T>(); var result = new AnonymousSubject <T>( Observer.Create <T>( x => output.OnNext(x), e => output.OnError(e), () => output.OnCompleted()), o.GetObservable(property)); o.Bind(property, output, priority); return(result); }
/// <summary> /// Binds a property on an <see cref="IPerspexObject"/> to an <see cref="IBinding"/>. /// </summary> /// <param name="target">The object.</param> /// <param name="property">The property to bind.</param> /// <param name="binding">The binding.</param> /// <param name="anchor"> /// An optional anchor from which to locate required context. When binding to objects that /// are not in the logical tree, certain types of binding need an anchor into the tree in /// order to locate named controls or resources. The <paramref name="anchor"/> parameter /// can be used to provice this context. /// </param> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> public static IDisposable Bind( this IPerspexObject target, PerspexProperty property, IBinding binding, object anchor = null) { Contract.Requires <ArgumentNullException>(target != null); Contract.Requires <ArgumentNullException>(property != null); Contract.Requires <ArgumentNullException>(binding != null); var result = binding.Initiate(target, property, anchor); if (result != null) { return(BindingOperations.Apply(target, property, result, anchor)); } else { return(Disposable.Empty); } }
private ExpressionObserver CreateDataContexObserver( IPerspexObject target, string path, bool targetIsDataContext, object anchor) { Contract.Requires <ArgumentNullException>(target != null); if (!(target is IControl)) { target = anchor as IControl; if (target == null) { throw new InvalidOperationException("Cannot find a DataContext to bind to."); } } if (!targetIsDataContext) { var update = target.GetObservable(Control.DataContextProperty) .Skip(1) .Select(_ => Unit.Default); var result = new ExpressionObserver( () => target.GetValue(Control.DataContextProperty), path, update); return(result); } else { return(new ExpressionObserver( target.GetObservable(Visual.VisualParentProperty) .OfType <IPerspexObject>() .Select(x => x.GetObservable(Control.DataContextProperty)) .Switch(), path)); } }
/// <summary> /// Binds a property on an <see cref="IPerspexObject"/> to an <see cref="IBinding"/>. /// </summary> /// <param name="o">The object.</param> /// <param name="property">The property to bind.</param> /// <param name="binding">The binding.</param> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> public static IDisposable Bind( this IPerspexObject o, PerspexProperty property, IBinding binding) { Contract.Requires <ArgumentNullException>(o != null); Contract.Requires <ArgumentNullException>(property != null); Contract.Requires <ArgumentNullException>(binding != null); var mode = binding.Mode; if (mode == BindingMode.Default) { mode = property.GetMetadata(o.GetType()).DefaultBindingMode; } return(o.Bind( property, binding.CreateSubject(o, property), mode, binding.Priority)); }
/// <inheritdoc/> object IDirectPropertyAccessor.GetValue(IPerspexObject instance) { return(Getter((TOwner)instance)); }
private ExpressionObserver CreateTemplatedParentObserver( IPerspexObject target, string path) { Contract.Requires<ArgumentNullException>(target != null); var update = target.GetObservable(Control.TemplatedParentProperty) .Skip(1) .Select(_ => Unit.Default); var result = new ExpressionObserver( () => target.GetValue(Control.TemplatedParentProperty), path, update); return result; }
private ExpressionObserver CreateDataContexObserver( IPerspexObject target, string path, bool targetIsDataContext) { Contract.Requires<ArgumentNullException>(target != null); if (!targetIsDataContext) { var update = target.GetObservable(Control.DataContextProperty) .Skip(1) .Select(_ => Unit.Default); var result = new ExpressionObserver( () => target.GetValue(Control.DataContextProperty), path, update); return result; } else { return new ExpressionObserver( target.GetObservable(Visual.VisualParentProperty) .OfType<IPerspexObject>() .Select(x => x.GetObservable(Control.DataContextProperty)) .Switch(), path); } }
/// <summary> /// Applies a binding subject to a property on an instance. /// </summary> /// <param name="target">The target instance.</param> /// <param name="property">The target property.</param> /// <param name="subject">The binding subject.</param> internal void Bind(IPerspexObject target, PerspexProperty property, ISubject<object> subject) { var mode = Mode == BindingMode.Default ? property.DefaultBindingMode : Mode; switch (mode) { case BindingMode.Default: case BindingMode.OneWay: target.Bind(property, subject, Priority); break; case BindingMode.TwoWay: throw new NotSupportedException("TwoWay MultiBinding not currently supported."); case BindingMode.OneTime: target.GetObservable(Control.DataContextProperty).Subscribe(dataContext => { subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority)); }); break; case BindingMode.OneWayToSource: target.GetObservable(property).Subscribe(subject); break; } }
/// <summary> /// Gets a description of a property that van be used in observables. /// </summary> /// <param name="o">The object.</param> /// <param name="property">The property</param> /// <returns>The description.</returns> private static string GetDescription(IPerspexObject o, PerspexProperty property) { return($"{o.GetType().Name}.{property.Name}"); }