// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reactive; using System.Reactive.Linq; using Avalonia.Data.Core.Parsers; using Avalonia.Data.Core.Plugins; using Avalonia.Reactive; namespace Avalonia.Data.Core { /// /// Observes and sets the value of an expression on an object. /// public class ExpressionObserver : LightweightObservableBase, IDescription { /// /// An ordered collection of property accessor plugins that can be used to customize /// the reading and subscription of property values on a type. /// public static readonly IList PropertyAccessors = new List { new AvaloniaPropertyAccessorPlugin(), new MethodAccessorPlugin(), new InpcPropertyAccessorPlugin(), }; /// /// An ordered collection of validation checker plugins that can be used to customize /// the validation of view model and model data. /// public static readonly IList DataValidators = new List { new DataAnnotationsValidationPlugin(), new IndeiValidationPlugin(), new ExceptionValidationPlugin(), }; /// /// An ordered collection of stream plugins that can be used to customize the behavior /// of the '^' stream binding operator. /// public static readonly IList StreamHandlers = new List { new TaskStreamPlugin(), new ObservableStreamPlugin(), }; private static readonly object UninitializedValue = new object(); private readonly ExpressionNode _node; private object _root; private IDisposable _rootSubscription; private WeakReference _value; /// /// Initializes a new instance of the class. /// /// The root object. /// The expression. /// /// A description of the expression. /// public ExpressionObserver( object root, ExpressionNode node, string description = null) { if (root == AvaloniaProperty.UnsetValue) { root = null; } _node = node; Description = description; _root = new WeakReference(root); } /// /// Initializes a new instance of the class. /// /// An observable which provides the root object. /// The expression. /// /// A description of the expression. /// public ExpressionObserver( IObservable rootObservable, ExpressionNode node, string description) { Contract.Requires(rootObservable != null); _node = node; Description = description; _root = rootObservable; } /// /// Initializes a new instance of the class. /// /// A function which gets the root object. /// The expression. /// An observable which triggers a re-read of the getter. /// /// A description of the expression. /// public ExpressionObserver( Func rootGetter, ExpressionNode node, IObservable update, string description) { Contract.Requires(rootGetter != null); Contract.Requires(update != null); Description = description; _node = node; _node.Target = new WeakReference(rootGetter()); _root = update.Select(x => rootGetter()); } /// /// Creates a new instance of the class. /// /// The root object. /// The expression. /// Whether or not to track data validation /// /// A description of the expression. If null, 's string representation will be used. /// public static ExpressionObserver Create( T root, Expression> expression, bool enableDataValidation = false, string description = null) { return new ExpressionObserver(root, Parse(expression, enableDataValidation), description ?? expression.ToString()); } /// /// Creates a new instance of the class. /// /// An observable which provides the root object. /// The expression. /// Whether or not to track data validation /// /// A description of the expression. If null, 's string representation will be used. /// public static ExpressionObserver Create( IObservable rootObservable, Expression> expression, bool enableDataValidation = false, string description = null) { Contract.Requires(rootObservable != null); return new ExpressionObserver( rootObservable.Select(o => (object)o), Parse(expression, enableDataValidation), description ?? expression.ToString()); } /// /// Creates a new instance of the class. /// /// A function which gets the root object. /// The expression. /// An observable which triggers a re-read of the getter. /// Whether or not to track data validation /// /// A description of the expression. If null, 's string representation will be used. /// public static ExpressionObserver Create( Func rootGetter, Expression> expression, IObservable update, bool enableDataValidation = false, string description = null) { Contract.Requires(rootGetter != null); return new ExpressionObserver( () => rootGetter(), Parse(expression, enableDataValidation), update, description ?? expression.ToString()); } /// /// Attempts to set the value of a property expression. /// /// The value to set. /// The binding priority to use. /// /// True if the value could be set; false if the expression does not evaluate to a /// property. Note that the must be subscribed to /// before setting the target value can work, as setting the value requires the /// expression to be evaluated. /// public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue) { if (Leaf is SettableNode settable) { var node = _node; while (node != null) { if (node is ITransformNode transform) { value = transform.Transform(value); if (value is BindingNotification) { return false; } } node = node.Next; } return settable.SetTargetValue(value, priority); } return false; } /// /// Gets a description of the expression being observed. /// public string Description { get; } /// /// Gets the expression being observed. /// public string Expression { get; } /// /// Gets the type of the expression result or null if the expression could not be /// evaluated. /// public Type ResultType => (Leaf as SettableNode)?.PropertyType; /// /// Gets the leaf node. /// private ExpressionNode Leaf { get { var node = _node; while (node.Next != null) node = node.Next; return node; } } protected override void Initialize() { _value = null; _node.Subscribe(ValueChanged); StartRoot(); } protected override void Deinitialize() { _rootSubscription?.Dispose(); _rootSubscription = null; _node.Unsubscribe(); } protected override void Subscribed(IObserver observer, bool first) { if (!first && _value != null && _value.TryGetTarget(out var value)) { observer.OnNext(value); } } private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation) { return ExpressionTreeParser.Parse(expression, enableDataValidation); } private void StartRoot() { if (_root is IObservable observable) { _rootSubscription = observable.Subscribe( x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), x => PublishCompleted(), () => PublishCompleted()); } else { _node.Target = (WeakReference)_root; } } private void ValueChanged(object value) { var broken = BindingNotification.ExtractError(value) as MarkupBindingChainException; broken?.Commit(Description); _value = new WeakReference(value); PublishNext(value); } } }