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.
395 lines
14 KiB
395 lines
14 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Data.Core;
|
|
using Avalonia.Data.Core.ExpressionNodes;
|
|
using Avalonia.Data.Core.Parsers;
|
|
using Avalonia.Data.Core.Plugins;
|
|
|
|
namespace Avalonia.Data
|
|
{
|
|
public class CompiledBindingPath
|
|
{
|
|
private readonly ICompiledBindingPathElement[] _elements;
|
|
|
|
public CompiledBindingPath()
|
|
=> _elements = Array.Empty<ICompiledBindingPathElement>();
|
|
|
|
internal CompiledBindingPath(ICompiledBindingPathElement[] elements)
|
|
=> _elements = elements;
|
|
|
|
internal void BuildExpression(List<ExpressionNode> result, out bool isRooted)
|
|
{
|
|
var negated = 0;
|
|
|
|
isRooted = false;
|
|
|
|
foreach (var element in _elements)
|
|
{
|
|
ExpressionNode? node;
|
|
switch (element)
|
|
{
|
|
case NotExpressionPathElement:
|
|
++negated;
|
|
node = null;
|
|
break;
|
|
case PropertyElement prop:
|
|
node = new PropertyAccessorNode(prop.Property.Name, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory), prop.AcceptsNull);
|
|
break;
|
|
case MethodAsCommandElement methodAsCommand:
|
|
node = new MethodCommandNode(
|
|
methodAsCommand.MethodName,
|
|
methodAsCommand.ExecuteMethod,
|
|
methodAsCommand.CanExecuteMethod,
|
|
methodAsCommand.DependsOnProperties);
|
|
break;
|
|
case MethodAsDelegateElement methodAsDelegate:
|
|
node = new PropertyAccessorNode(
|
|
methodAsDelegate.Method.Name,
|
|
new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType),
|
|
methodAsDelegate.AcceptsNull);
|
|
break;
|
|
case ArrayElementPathElement arr:
|
|
node = new ArrayIndexerNode(arr.Indices);
|
|
break;
|
|
case VisualAncestorPathElement visualAncestor:
|
|
node = new VisualAncestorElementNode(visualAncestor.AncestorType, visualAncestor.Level);
|
|
isRooted = true;
|
|
break;
|
|
case AncestorPathElement ancestor:
|
|
node = new LogicalAncestorElementNode(ancestor.AncestorType, ancestor.Level);
|
|
isRooted = true;
|
|
break;
|
|
case SelfPathElement:
|
|
node = null;
|
|
isRooted = true;
|
|
break;
|
|
case ElementNameElement name:
|
|
node = new NamedElementNode(name.NameScope, name.Name);
|
|
isRooted = true;
|
|
break;
|
|
case IStronglyTypedStreamElement stream:
|
|
node = new StreamNode(stream.CreatePlugin());
|
|
break;
|
|
case ITypeCastElement typeCast:
|
|
node = new FuncTransformNode(typeCast.Cast);
|
|
break;
|
|
case TemplatedParentPathElement:
|
|
node = new TemplatedParentNode();
|
|
isRooted = true;
|
|
break;
|
|
default:
|
|
throw new InvalidOperationException($"Unknown binding path element type {element.GetType().FullName}");
|
|
}
|
|
|
|
if (node is not null)
|
|
result.Add(node);
|
|
}
|
|
|
|
|
|
for (var i = 0; i < negated; ++i)
|
|
result.Add(new LogicalNotNode());
|
|
}
|
|
|
|
internal IEnumerable<ICompiledBindingPathElement> Elements => _elements;
|
|
|
|
/// <inheritdoc />
|
|
public override string ToString()
|
|
=> string.Concat((IEnumerable<ICompiledBindingPathElement>)_elements);
|
|
}
|
|
|
|
public class CompiledBindingPathBuilder
|
|
{
|
|
private readonly List<ICompiledBindingPathElement> _elements = new();
|
|
|
|
public CompiledBindingPathBuilder()
|
|
{
|
|
}
|
|
|
|
|
|
public CompiledBindingPathBuilder Not()
|
|
{
|
|
_elements.Add(new NotExpressionPathElement());
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> accessorFactory)
|
|
{
|
|
return Property(info, accessorFactory, acceptsNull: false);
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Property(
|
|
IPropertyInfo info,
|
|
Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> accessorFactory,
|
|
bool acceptsNull)
|
|
{
|
|
_elements.Add(new PropertyElement(info, accessorFactory, _elements.Count == 0, acceptsNull));
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Method(RuntimeMethodHandle handle, RuntimeTypeHandle delegateType)
|
|
{
|
|
Method(handle, delegateType, acceptsNull: false);
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Method(
|
|
RuntimeMethodHandle handle,
|
|
RuntimeTypeHandle delegateType,
|
|
bool acceptsNull)
|
|
{
|
|
_elements.Add(new MethodAsDelegateElement(handle, delegateType, acceptsNull));
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPathBuilder Command(string methodName, Action<object, object?> executeHelper, Func<object, object?, bool>? canExecuteHelper, string[]? dependsOnProperties)
|
|
{
|
|
_elements.Add(new MethodAsCommandElement(methodName, executeHelper, canExecuteHelper, dependsOnProperties ?? Array.Empty<string>()));
|
|
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 TemplatedParent()
|
|
{
|
|
_elements.Add(new TemplatedParentPathElement());
|
|
return this;
|
|
}
|
|
|
|
public CompiledBindingPath Build() => new CompiledBindingPath(_elements.ToArray());
|
|
}
|
|
|
|
internal 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,
|
|
bool acceptsNull)
|
|
{
|
|
Property = property;
|
|
AccessorFactory = accessorFactory;
|
|
_isFirstElement = isFirstElement;
|
|
AcceptsNull = acceptsNull;
|
|
}
|
|
|
|
public IPropertyInfo Property { get; }
|
|
|
|
public Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
|
|
|
|
public bool AcceptsNull { get; }
|
|
|
|
public override string ToString()
|
|
=> _isFirstElement ? Property.Name : $".{Property.Name}";
|
|
}
|
|
|
|
internal class MethodAsDelegateElement : ICompiledBindingPathElement
|
|
{
|
|
public MethodAsDelegateElement(
|
|
RuntimeMethodHandle method,
|
|
RuntimeTypeHandle delegateType,
|
|
bool acceptsNull)
|
|
{
|
|
Method = MethodBase.GetMethodFromHandle(method) as MethodInfo
|
|
?? throw new ArgumentException("Invalid method handle", nameof(method));
|
|
DelegateType = Type.GetTypeFromHandle(delegateType)
|
|
?? throw new ArgumentException("Unexpected null returned from Type.GetTypeFromHandle in MethodAsDelegateElement");
|
|
AcceptsNull = acceptsNull;
|
|
}
|
|
|
|
public MethodInfo Method { get; }
|
|
|
|
public Type DelegateType { get; }
|
|
|
|
public bool AcceptsNull { get; }
|
|
}
|
|
|
|
internal class MethodAsCommandElement : ICompiledBindingPathElement
|
|
{
|
|
public MethodAsCommandElement(string methodName, Action<object, object?> executeHelper, Func<object, object?, bool>? canExecuteHelper, string[] dependsOnElements)
|
|
{
|
|
MethodName = methodName;
|
|
ExecuteMethod = executeHelper;
|
|
CanExecuteMethod = canExecuteHelper;
|
|
DependsOnProperties = new HashSet<string>(dependsOnElements);
|
|
}
|
|
|
|
public string MethodName { get; }
|
|
public Action<object, object?> ExecuteMethod { get; }
|
|
public Func<object, object?, bool>? CanExecuteMethod { get; }
|
|
public HashSet<string> DependsOnProperties { get; }
|
|
}
|
|
|
|
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()
|
|
=> FormattableString.Invariant($"$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 TemplatedParentPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
|
|
{
|
|
public override string ToString()
|
|
=> $"$templatedParent";
|
|
}
|
|
|
|
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()
|
|
=> FormattableString.Invariant($"[{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})";
|
|
}
|
|
}
|
|
|