예제 #1
0
        /// <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>());
        }
예제 #4
0
        /// <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.");
            }
        }
예제 #5
0
 /// <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)));
 }
예제 #6
0
파일: Binding.cs 프로젝트: sqdavid/Avalonia
        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));
            }
        }
예제 #7
0
 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());
 }
예제 #8
0
파일: Binding.cs 프로젝트: sqdavid/Avalonia
        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);
        }
예제 #10
0
        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));
            }
        }
예제 #11
0
        /// <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.");
            }
        }
예제 #12
0
        /// <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;
            }
        }
예제 #13
0
        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;
        }
예제 #14
0
        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);
            }
        }
예제 #15
0
 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();
 }