/// <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(IAvaloniaObject target, AvaloniaProperty 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; } }
/// <summary> /// Applies an <see cref="InstancedBinding"/> a property on an <see cref="IAvaloniaObject"/>. /// </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 provide this context. /// </param> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> public static IDisposable Apply( IAvaloniaObject target, AvaloniaProperty 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 .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue) .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(Observable.CombineLatest( binding.Observable, target.GetObservable(property), (_, v) => v) .Subscribe(x => binding.Subject.OnNext(x))); default: throw new ArgumentException("Invalid binding mode."); } }
/// <summary> /// Gets an observable for a <see cref="AvaloniaProperty"/>. /// </summary> /// <param name="o">The object.</param> /// <typeparam name="T">The property type.</typeparam> /// <param name="property">The property.</param> /// <returns> /// An observable which fires immediately with the current value of the property on the /// object and subsequently each time the property value changes. /// </returns> public static IObservable <T> GetObservable <T>(this IAvaloniaObject o, AvaloniaProperty <T> property) { Contract.Requires <ArgumentNullException>(o != null); Contract.Requires <ArgumentNullException>(property != null); return(o.GetObservable((AvaloniaProperty)property).Cast <T>()); }
/// <summary> /// Applies an <see cref="InstancedBinding"/> a property on an <see cref="IAvaloniaObject"/>. /// </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( IAvaloniaObject target, AvaloniaProperty 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 .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue) .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."); } }
/// <summary> /// Gets a subject for a <see cref="AvaloniaProperty"/>. /// </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 IAvaloniaObject o, AvaloniaProperty <T> property, BindingPriority priority = BindingPriority.LocalValue) { return(Subject.Create <T>( Observer.Create <T>(x => o.SetValue(property, x, priority)), o.GetObservable(property))); }
private ExpressionObserver CreateDataContexObserver( IAvaloniaObject 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, EnableValidation); return(result); } else { return(new ExpressionObserver( target.GetObservable(Visual.VisualParentProperty) .OfType <IAvaloniaObject>() .Select(x => x.GetObservable(Control.DataContextProperty)) .Switch(), path, EnableValidation)); } }
private IObservable <object> GetParentDataContext(IAvaloniaObject target) { // The DataContext is based on the visual parent and not the logical parent: this may // seem unintuitive considering the fact that property inheritance works on the logical // tree, but consider a ContentControl with a ContentPresenter. The ContentControl's // Content property is bound to a value which becomes the ContentPresenter's // DataContext - it is from this that the child hosted by the ContentPresenter needs to // inherit its DataContext. return(target.GetObservable(Visual.VisualParentProperty) .Select(x => { return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ?? Observable.Return((object)null); }).Switch()); }
private ExpressionObserver CreateTemplatedParentObserver( IAvaloniaObject 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> /// Gets a subject for a <see cref="AvaloniaProperty"/>. /// </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 IAvaloniaObject o, AvaloniaProperty <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); }
private ExpressionObserver CreateDataContextObserver( IAvaloniaObject target, string path, bool targetIsDataContext, object anchor, bool enableDataValidation) { Contract.Requires <ArgumentNullException>(target != null); if (!(target is IStyledElement)) { target = anchor as IStyledElement; if (target == null) { throw new InvalidOperationException("Cannot find a DataContext to bind to."); } } if (!targetIsDataContext) { var update = target.GetObservable(StyledElement.DataContextProperty) .Skip(1) .Select(_ => Unit.Default); var result = new ExpressionObserver( () => target.GetValue(StyledElement.DataContextProperty), path, update, enableDataValidation); return(result); } else { return(new ExpressionObserver( GetParentDataContext(target), path, enableDataValidation)); } }
/// <summary> /// Applies an <see cref="InstancedBinding"/> a property on an <see cref="IAvaloniaObject"/>. /// </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 provide this context. /// </param> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> public static IDisposable Apply( IAvaloniaObject target, AvaloniaProperty property, InstancedBinding binding, object?anchor) { _ = target ?? throw new ArgumentNullException(nameof(target)); _ = property ?? throw new ArgumentNullException(nameof(property)); _ = binding ?? throw new ArgumentNullException(nameof(binding)); var mode = binding.Mode; if (mode == BindingMode.Default) { mode = property.GetMetadata(target.GetType()).DefaultBindingMode; } switch (mode) { case BindingMode.Default: case BindingMode.OneWay: if (binding.Observable is null) { throw new InvalidOperationException("InstancedBinding does not contain an observable."); } return(target.Bind(property, binding.Observable, binding.Priority)); case BindingMode.TwoWay: if (binding.Subject is null) { throw new InvalidOperationException("InstancedBinding does not contain a subject."); } return(new TwoWayBindingDisposable( 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) { // Perf: Avoid allocating closure in the outer scope. var targetCopy = target; var propertyCopy = property; var bindingCopy = binding; return(source .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue) .Take(1) .Subscribe(x => targetCopy.SetValue( propertyCopy, BindingNotification.ExtractValue(x), bindingCopy.Priority))); } else { target.SetValue(property, binding.Value, binding.Priority); return(Disposable.Empty); } case BindingMode.OneWayToSource: { if (binding.Observable is null) { throw new InvalidOperationException("InstancedBinding does not contain an observable."); } if (binding.Subject is null) { throw new InvalidOperationException("InstancedBinding does not contain a subject."); } // Perf: Avoid allocating closure in the outer scope. var bindingCopy = binding; return(Observable.CombineLatest( binding.Observable, target.GetObservable(property), (_, v) => v) .Subscribe(x => bindingCopy.Subject.OnNext(x))); } default: throw new ArgumentException("Invalid binding mode."); } }
/// <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(IAvaloniaObject target, AvaloniaProperty 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; } }
private ExpressionObserver CreateTemplatedParentObserver( IAvaloniaObject 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( IAvaloniaObject 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, EnableValidation); return result; } else { return new ExpressionObserver( target.GetObservable(Visual.VisualParentProperty) .OfType<IAvaloniaObject>() .Select(x => x.GetObservable(Control.DataContextProperty)) .Switch(), path, EnableValidation); } }
private IObservable<object> GetParentDataContext(IAvaloniaObject target) { // The DataContext is based on the visual parent and not the logical parent: this may // seem unintuitive considering the fact that property inheritance works on the logical // tree, but consider a ContentControl with a ContentPresenter. The ContentControl's // Content property is bound to a value which becomes the ContentPresenter's // DataContext - it is from this that the child hosted by the ContentPresenter needs to // inherit its DataContext. return target.GetObservable(Visual.VisualParentProperty) .Select(x => { return (x as IAvaloniaObject)?.GetObservable(Control.DataContextProperty) ?? Observable.Return((object)null); }).Switch(); }