csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
14 KiB
370 lines
14 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Data.Core;
|
|
using Avalonia.Data.Core.Plugins;
|
|
using Avalonia.Markup.Parsers;
|
|
using Avalonia.Markup.Parsers.Nodes;
|
|
|
|
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
|
|
{
|
|
public class CompiledBindingPath
|
|
{
|
|
private readonly List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
|
|
|
|
public CompiledBindingPath() { }
|
|
|
|
internal CompiledBindingPath(IEnumerable<ICompiledBindingPathElement> bindingPath, object rawSource)
|
|
{
|
|
_elements = new List<ICompiledBindingPathElement>(bindingPath);
|
|
RawSource = rawSource;
|
|
}
|
|
|
|
public ExpressionNode BuildExpression(bool enableValidation)
|
|
{
|
|
ExpressionNode pathRoot = null;
|
|
ExpressionNode path = null;
|
|
foreach (var element in _elements)
|
|
{
|
|
ExpressionNode node = null;
|
|
switch (element)
|
|
{
|
|
case NotExpressionPathElement _:
|
|
node = new LogicalNotNode();
|
|
break;
|
|
case PropertyElement prop:
|
|
node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory));
|
|
break;
|
|
case IMethodAsCommandElement methodAsCommand:
|
|
node = new PropertyAccessorNode(methodAsCommand.ExecuteMethod.Name, enableValidation, new CommandAccessorPlugin(methodAsCommand.CreateAccessor));
|
|
break;
|
|
case MethodAsDelegateElement methodAsDelegate:
|
|
node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType));
|
|
break;
|
|
case ArrayElementPathElement arr:
|
|
node = new PropertyAccessorNode(CommonPropertyNames.IndexerName, enableValidation, new ArrayElementPlugin(arr.Indices, arr.ElementType));
|
|
break;
|
|
case VisualAncestorPathElement visualAncestor:
|
|
node = new FindVisualAncestorNode(visualAncestor.AncestorType, visualAncestor.Level);
|
|
break;
|
|
case AncestorPathElement ancestor:
|
|
node = new FindAncestorNode(ancestor.AncestorType, ancestor.Level);
|
|
break;
|
|
case SelfPathElement _:
|
|
node = new SelfNode();
|
|
break;
|
|
case ElementNameElement name:
|
|
node = new ElementNameNode(name.NameScope, name.Name);
|
|
break;
|
|
case IStronglyTypedStreamElement stream:
|
|
node = new StreamNode(stream.CreatePlugin());
|
|
break;
|
|
case ITypeCastElement typeCast:
|
|
node = new StrongTypeCastNode(typeCast.Type, typeCast.Cast);
|
|
break;
|
|
default:
|
|
throw new InvalidOperationException($"Unknown binding path element type {element.GetType().FullName}");
|
|
}
|
|
|
|
path = pathRoot is null ? (pathRoot = node) : path.Next = node;
|
|
}
|
|
|
|
return pathRoot ?? new EmptyExpressionNode();
|
|
}
|
|
|
|
internal SourceMode SourceMode => _elements.Count > 0 && _elements[0] is IControlSourceBindingPathElement ? SourceMode.Control : SourceMode.Data;
|
|
|
|
internal object RawSource { get; }
|
|
|
|
public override string ToString()
|
|
=> string.Concat(_elements);
|
|
}
|
|
|
|
public class CompiledBindingPathBuilder
|
|
{
|
|
private object _rawSource;
|
|
private List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
|
|
|
|
public CompiledBindingPathBuilder Not()
|
|
{
|
|
_elements.Add(new NotExpressionPathElement());
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
|
|
{
|
|
_elements.Add(new PropertyElement(info, accessorFactory, _elements.Count == 0));
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Method(RuntimeMethodHandle handle, RuntimeTypeHandle delegateType)
|
|
{
|
|
_elements.Add(new MethodAsDelegateElement(handle, delegateType));
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Command(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties)
|
|
{
|
|
_elements.Add(new MethodAsCommandElement(executeMethod, canExecuteMethod, dependsOnProperties));
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder CommandWithParameter<T>(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties)
|
|
{
|
|
_elements.Add(new MethodAsCommandElement<T>(executeMethod, canExecuteMethod, dependsOnProperties));
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder StreamTask<T>()
|
|
{
|
|
_elements.Add(new TaskStreamPathElement<T>());
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder StreamObservable<T>()
|
|
{
|
|
_elements.Add(new ObservableStreamPathElement<T>());
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Self()
|
|
{
|
|
_elements.Add(new SelfPathElement());
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Ancestor(Type ancestorType, int level)
|
|
{
|
|
_elements.Add(new AncestorPathElement(ancestorType, level));
|
|
return this;
|
|
}
|
|
public CompiledBindingPathBuilder VisualAncestor(Type ancestorType, int level)
|
|
{
|
|
_elements.Add(new VisualAncestorPathElement(ancestorType, level));
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder ElementName(INameScope nameScope, string name)
|
|
{
|
|
_elements.Add(new ElementNameElement(nameScope, name));
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder ArrayElement(int[] indices, Type elementType)
|
|
{
|
|
_elements.Add(new ArrayElementPathElement(indices, elementType));
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder TypeCast<T>()
|
|
{
|
|
_elements.Add(new TypeCastPathElement<T>());
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder SetRawSource(object rawSource)
|
|
{
|
|
_rawSource = rawSource;
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPath Build() => new CompiledBindingPath(_elements, _rawSource);
|
|
}
|
|
|
|
public interface ICompiledBindingPathElement
|
|
{
|
|
}
|
|
|
|
internal interface IControlSourceBindingPathElement { }
|
|
|
|
internal class NotExpressionPathElement : ICompiledBindingPathElement
|
|
{
|
|
public static readonly NotExpressionPathElement Instance = new NotExpressionPathElement();
|
|
}
|
|
|
|
internal class PropertyElement : ICompiledBindingPathElement
|
|
{
|
|
private readonly bool _isFirstElement;
|
|
|
|
public PropertyElement(IPropertyInfo property, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory, bool isFirstElement)
|
|
{
|
|
Property = property;
|
|
AccessorFactory = accessorFactory;
|
|
_isFirstElement = isFirstElement;
|
|
}
|
|
|
|
public IPropertyInfo Property { get; }
|
|
|
|
public Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
|
|
|
|
public override string ToString()
|
|
=> _isFirstElement ? Property.Name : $".{Property.Name}";
|
|
}
|
|
|
|
internal class MethodAsDelegateElement : ICompiledBindingPathElement
|
|
{
|
|
public MethodAsDelegateElement(RuntimeMethodHandle method, RuntimeTypeHandle delegateType)
|
|
{
|
|
Method = (MethodInfo)MethodBase.GetMethodFromHandle(method);
|
|
DelegateType = Type.GetTypeFromHandle(delegateType);
|
|
}
|
|
|
|
public MethodInfo Method { get; }
|
|
|
|
public Type DelegateType { get; }
|
|
}
|
|
|
|
internal interface IMethodAsCommandElement
|
|
{
|
|
MethodInfo ExecuteMethod { get; }
|
|
IPropertyAccessor CreateAccessor(WeakReference<object> obj);
|
|
}
|
|
|
|
internal class MethodAsCommandElement : ICompiledBindingPathElement, IMethodAsCommandElement
|
|
{
|
|
public MethodAsCommandElement(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnElements)
|
|
{
|
|
ExecuteMethod = (MethodInfo)MethodBase.GetMethodFromHandle(executeMethod);
|
|
CanExecuteMethod = canExecuteMethod != default ? (MethodInfo)MethodBase.GetMethodFromHandle(canExecuteMethod) : null;
|
|
DependsOnProperties = new HashSet<string>(dependsOnElements);
|
|
}
|
|
|
|
public MethodInfo ExecuteMethod { get; }
|
|
public MethodInfo CanExecuteMethod { get; }
|
|
public HashSet<string> DependsOnProperties { get; }
|
|
|
|
public IPropertyAccessor CreateAccessor(WeakReference<object> obj)
|
|
{
|
|
obj.TryGetTarget(out object target);
|
|
return new CommandAccessorPlugin.CommandWithoutParameterAccessor(obj, DependsOnProperties, (Action)ExecuteMethod.CreateDelegate(typeof(Action), target), (Func<object, bool>)CanExecuteMethod?.CreateDelegate(typeof(Func<object, bool>), target));
|
|
}
|
|
}
|
|
internal class MethodAsCommandElement<T> : ICompiledBindingPathElement, IMethodAsCommandElement
|
|
{
|
|
public MethodAsCommandElement(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnElements)
|
|
{
|
|
ExecuteMethod = (MethodInfo)MethodBase.GetMethodFromHandle(executeMethod);
|
|
CanExecuteMethod = canExecuteMethod != default ? (MethodInfo)MethodBase.GetMethodFromHandle(canExecuteMethod) : null;
|
|
DependsOnProperties = new HashSet<string>(dependsOnElements);
|
|
}
|
|
|
|
public MethodInfo ExecuteMethod { get; }
|
|
public MethodInfo CanExecuteMethod { get; }
|
|
public HashSet<string> DependsOnProperties { get; }
|
|
|
|
public IPropertyAccessor CreateAccessor(WeakReference<object> obj)
|
|
{
|
|
obj.TryGetTarget(out object target);
|
|
return new CommandAccessorPlugin.CommandWithParameterAccessor<T>(obj, DependsOnProperties, (Action<T>)ExecuteMethod.CreateDelegate(typeof(Action<T>), target), (Func<object, bool>)CanExecuteMethod?.CreateDelegate(typeof(Func<object, bool>), target));
|
|
}
|
|
}
|
|
|
|
internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement
|
|
{
|
|
IStreamPlugin CreatePlugin();
|
|
}
|
|
|
|
internal interface ITypeCastElement : ICompiledBindingPathElement
|
|
{
|
|
Type Type { get; }
|
|
|
|
Func<object, object> Cast { get; }
|
|
}
|
|
|
|
internal class TaskStreamPathElement<T> : IStronglyTypedStreamElement
|
|
{
|
|
public static readonly TaskStreamPathElement<T> Instance = new TaskStreamPathElement<T>();
|
|
|
|
public IStreamPlugin CreatePlugin() => new TaskStreamPlugin<T>();
|
|
}
|
|
|
|
internal class ObservableStreamPathElement<T> : IStronglyTypedStreamElement
|
|
{
|
|
public static readonly ObservableStreamPathElement<T> Instance = new ObservableStreamPathElement<T>();
|
|
|
|
public IStreamPlugin CreatePlugin() => new ObservableStreamPlugin<T>();
|
|
}
|
|
|
|
internal class SelfPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
|
|
{
|
|
public static readonly SelfPathElement Instance = new SelfPathElement();
|
|
|
|
public override string ToString()
|
|
=> "$self";
|
|
}
|
|
|
|
internal class AncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
|
|
{
|
|
public AncestorPathElement(Type ancestorType, int level)
|
|
{
|
|
AncestorType = ancestorType;
|
|
Level = level;
|
|
}
|
|
|
|
public Type AncestorType { get; }
|
|
public int Level { get; }
|
|
|
|
public override string ToString()
|
|
=> $"$parent[{AncestorType?.Name},{Level}]";
|
|
}
|
|
|
|
internal class VisualAncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
|
|
{
|
|
public VisualAncestorPathElement(Type ancestorType, int level)
|
|
{
|
|
AncestorType = ancestorType;
|
|
Level = level;
|
|
}
|
|
|
|
public Type AncestorType { get; }
|
|
public int Level { get; }
|
|
}
|
|
|
|
internal class ElementNameElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
|
|
{
|
|
public ElementNameElement(INameScope nameScope, string name)
|
|
{
|
|
NameScope = nameScope;
|
|
Name = name;
|
|
}
|
|
|
|
public INameScope NameScope { get; }
|
|
public string Name { get; }
|
|
|
|
public override string ToString()
|
|
=> $"#{Name}";
|
|
}
|
|
|
|
internal class ArrayElementPathElement : ICompiledBindingPathElement
|
|
{
|
|
public ArrayElementPathElement(int[] indices, Type elementType)
|
|
{
|
|
Indices = indices;
|
|
ElementType = elementType;
|
|
}
|
|
|
|
public int[] Indices { get; }
|
|
public Type ElementType { get; }
|
|
public override string ToString()
|
|
=> $"[{string.Join(",", Indices)}]";
|
|
}
|
|
|
|
internal class TypeCastPathElement<T> : ITypeCastElement
|
|
{
|
|
private static object TryCast(object obj)
|
|
{
|
|
if (obj is T result)
|
|
return result;
|
|
return null;
|
|
}
|
|
|
|
public Type Type => typeof(T);
|
|
|
|
public Func<object, object> Cast => TryCast;
|
|
|
|
public override string ToString()
|
|
=> $"({Type.FullName})";
|
|
}
|
|
}
|
|
|