// 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.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Markup.Data.Plugins; namespace Avalonia.Markup.Data { /// /// Observes and sets the value of an expression on an object. /// public class ExpressionObserver : ObservableBase, 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 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 IndeiValidationPlugin(), new ExceptionValidationPlugin(), }; /// /// An ordered collection of value handlers that can be used to customize the handling /// of certain values. /// public static readonly IList ValueHandlers = new List { new TaskValuePlugin(), new ObservableValuePlugin(), }; private static readonly object UninitializedValue = new object(); private readonly ExpressionNode _node; private readonly Subject _finished; private readonly object _root; private IObservable _result; /// /// Initializes a new instance of the class. /// /// The root object. /// The expression. /// Whether data validation should be enabled. public ExpressionObserver(object root, string expression, bool enableDataValidation = false) { Contract.Requires(expression != null); Expression = expression; _node = Parse(expression, enableDataValidation); _root = new WeakReference(root); } /// /// Initializes a new instance of the class. /// /// An observable which provides the root object. /// The expression. /// Whether data validation should be enabled. public ExpressionObserver( IObservable rootObservable, string expression, bool enableDataValidation = false) { Contract.Requires(rootObservable != null); Contract.Requires(expression != null); Expression = expression; _node = Parse(expression, enableDataValidation); _finished = new Subject(); _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. /// Whether data validation should be enabled. public ExpressionObserver( Func rootGetter, string expression, IObservable update, bool enableDataValidation = false) { Contract.Requires(rootGetter != null); Contract.Requires(expression != null); Contract.Requires(update != null); Expression = expression; _node = Parse(expression, enableDataValidation); _finished = new Subject(); _node.Target = new WeakReference(rootGetter()); _root = update.Select(x => rootGetter()); } /// /// 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) { return (Leaf as PropertyAccessorNode)?.SetTargetValue(value, priority) ?? false; } /// /// 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 PropertyAccessorNode)?.PropertyType; /// string IDescription.Description => Expression; /// /// Gets the leaf node. /// private ExpressionNode Leaf { get { var node = _node; while (node.Next != null) node = node.Next; return node; } } /// protected override IDisposable SubscribeCore(IObserver observer) { if (_result == null) { var source = (IObservable)_node; if (_finished != null) { source = source.TakeUntil(_finished); } _result = Observable.Using(StartRoot, _ => source) .Publish(UninitializedValue) .RefCount() .Where(x => x != UninitializedValue); } return _result.Subscribe(observer); } private static ExpressionNode Parse(string expression, bool enableDataValidation) { if (!string.IsNullOrWhiteSpace(expression)) { return ExpressionNodeBuilder.Build(expression, enableDataValidation); } else { return new EmptyExpressionNode(); } } private IDisposable StartRoot() { var observable = _root as IObservable; if (observable != null) { return observable.Subscribe( x => _node.Target = new WeakReference(x), _ => _finished.OnNext(Unit.Default), () => _finished.OnNext(Unit.Default)); } else { _node.Target = (WeakReference)_root; return Disposable.Empty; } } } }