// 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;
}
}
}
}