Skip to content

Instantly share code, notes, and snippets.

@toeb
Last active May 8, 2018 23:35
Show Gist options
  • Save toeb/f2842322af1efbadafe7757fb10d9146 to your computer and use it in GitHub Desktop.
Save toeb/f2842322af1efbadafe7757fb10d9146 to your computer and use it in GitHub Desktop.
using System;
using System.Windows.Markup;
using System.Windows.Data;
using System.Globalization;
using System.Collections.Generic;
using System.Linq;
using Core.Common.Injection;
using System.Xml;
using System.Diagnostics;
namespace Core.Common.Wpf.Export
{
/// <summary> A base class for Evaluating Extensions - Extensions which can contain recursive bound elements </summary>
///
/// <remarks> Toeb, 2018-05-09. </remarks>
public abstract class EvaluatingExtensionBase : MarkupExtension, IMultiValueConverter
{
public EvaluatingExtensionBase(object[] arguments) { this.MultiArguments = arguments; }
protected abstract object Evaluate(object[] values);
public object[] MultiArguments { get; private set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return _ast.Evaluate(new Queue<object>(values));
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private class AstNode
{
public override string ToString()
{
return $"Ast Input {InputObject} Value {Value}";
}
public object Value { get; internal set; }
public MarkupExtension Provider
{
get; set;
}
public object InputObject
{
get; set;
}
public AstNode[] Children
{
get; set;
}
public IEnumerable<AstNode> Descendants
{
get
{
return SelfAndDescendents.Skip(1);
}
}
public IEnumerable<AstNode> SelfAndDescendents
{
get
{
yield return this;
foreach (var child in Children.SelectMany(c => c.SelfAndDescendents))
{
yield return child;
}
}
}
public static AstNode Parse(object current)
{
var result = new AstNode();
result.InputObject = current;
result.Children = Array.Empty<AstNode>();
if (current is MultiBinding mb)
{
if (mb.ConverterParameter is EvaluatingExtensionBase evaluatingExtensionBase)
{
result.Children = evaluatingExtensionBase.MultiArguments.Select(a => Parse(a)).ToArray();
}
else
{
throw new InvalidOperationException($"only multibindings of type {nameof(EvaluatingExtensionBase)} are supported");
}
}
if (current is MarkupExtension extension)
{
result.Provider = extension;
}
return result;
}
public object Evaluate(Queue<object> values)
{
if (Provider is MultiBinding multi)
{
if (multi.Converter is EvaluatingExtensionBase evb)
{
var items = Children.Select(child => child.Evaluate(values)).ToArray();
return evb.Evaluate(items);
}
}
if (Provider != null)
{
if (values.Count == 0)
{
return null;
}
return values.Dequeue();
}
return InputObject;
}
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Provider == null)
{
Value = InputObject;
}
else
{
Value = Provider.ProvideValue(serviceProvider);
}
return Value;
}
}
private MultiBinding _binding;
private AstNode _ast;
public object TargetObject { get; private set; }
public object TargetProperty { get; private set; }
public IServiceProvider ServiceProvider { get; private set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
TargetObject = serviceProvider.GetService<IProvideValueTarget>()?.TargetObject;
TargetProperty = serviceProvider.GetService<IProvideValueTarget>()?.TargetProperty;
ServiceProvider = serviceProvider;
OnProvideValue(serviceProvider);
if (_binding != null)
{
throw new InvalidOperationException("provide value may only be called once");
}
_binding = new MultiBinding() { Converter = this };
_binding.ConverterParameter = this;
_ast = AstNode.Parse(_binding);
var provided = _ast
.Descendants
.Select(n => n.ProvideValue(serviceProvider))
.Where(v => v != null)
.ToArray();
var bindingExpressions = provided.OfType<BindingExpression>().ToArray();
foreach (var binding in bindingExpressions)
{
_binding.Bindings.Add(binding.ParentBindingBase);
}
return _binding.ProvideValue(serviceProvider);
}
protected virtual void OnProvideValue(IServiceProvider provider)
{
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment