Browse Source

Use a dynamically-emitted trampoline method so we can handle the "execute method has a return type" case cleanly.

This also provides a location for us to add any other trampolines that we need to emit.
pull/7246/head
Jeremy Koritzinsky 4 years ago
parent
commit
1d5e262487
No known key found for this signature in database GPG Key ID: 25A7D1C8126B7A16
  1. 5
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  2. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  3. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
  4. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  5. 58
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  6. 117
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs
  7. 169
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs
  8. 59
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  9. 4
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

5
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -124,6 +124,8 @@ namespace Avalonia.Build.Tasks
var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure", var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(indexerAccessorClosure); asm.MainModule.Types.Add(indexerAccessorClosure);
var trampolineBuilder = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlTrampolines",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem); var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem, var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
@ -133,6 +135,7 @@ namespace Avalonia.Build.Tasks
AvaloniaXamlIlLanguage.CustomValueConverter, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)), new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)), new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)),
new DeterministicIdGenerator()); new DeterministicIdGenerator());
@ -256,7 +259,7 @@ namespace Avalonia.Build.Tasks
(closureName, closureBaseType) => (closureName, closureBaseType) =>
populateBuilder.DefineSubType(closureBaseType, closureName, false), populateBuilder.DefineSubType(closureBaseType, closureName, false),
(closureName, returnType, parameterTypes) => (closureName, returnType, parameterTypes) =>
populateBuilder.CreateDelegateSubType(closureName, false, returnType, parameterTypes), populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes),
res.Uri, res res.Uri, res
); );

5
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

@ -177,11 +177,13 @@ namespace Avalonia.Markup.Xaml.XamlIl
var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri); var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N")); var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N"));
var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N")); var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N"));
var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm, var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter, _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)), new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType))), new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))),
_sreEmitMappings, _sreEmitMappings,
_sreContextType) { EnableIlVerification = true }; _sreContextType) { EnableIlVerification = true };
@ -196,6 +198,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType); compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType);
var created = tb.CreateTypeInfo(); var created = tb.CreateTypeInfo();
clrPropertyBuilder.CreateTypeInfo(); clrPropertyBuilder.CreateTypeInfo();
trampolineBuilder.CreateTypeInfo();
return LoadOrPopulate(created, rootInstance); return LoadOrPopulate(created, rootInstance);
} }

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs

@ -7,6 +7,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{ {
public XamlIlClrPropertyInfoEmitter ClrPropertyEmitter { get; } public XamlIlClrPropertyInfoEmitter ClrPropertyEmitter { get; }
public XamlIlPropertyInfoAccessorFactoryEmitter AccessorFactoryEmitter { get; } public XamlIlPropertyInfoAccessorFactoryEmitter AccessorFactoryEmitter { get; }
public XamlIlTrampolineBuilder TrampolineBuilder { get; }
public AvaloniaXamlIlCompilerConfiguration(IXamlTypeSystem typeSystem, public AvaloniaXamlIlCompilerConfiguration(IXamlTypeSystem typeSystem,
IXamlAssembly defaultAssembly, IXamlAssembly defaultAssembly,
@ -15,13 +16,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XamlValueConverter customValueConverter, XamlValueConverter customValueConverter,
XamlIlClrPropertyInfoEmitter clrPropertyEmitter, XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter, XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter,
XamlIlTrampolineBuilder trampolineBuilder,
IXamlIdentifierGenerator identifierGenerator = null) IXamlIdentifierGenerator identifierGenerator = null)
: base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator) : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator)
{ {
ClrPropertyEmitter = clrPropertyEmitter; ClrPropertyEmitter = clrPropertyEmitter;
AccessorFactoryEmitter = accessorFactoryEmitter; AccessorFactoryEmitter = accessorFactoryEmitter;
TrampolineBuilder = trampolineBuilder;
AddExtra(ClrPropertyEmitter); AddExtra(ClrPropertyEmitter);
AddExtra(AccessorFactoryEmitter); AddExtra(AccessorFactoryEmitter);
AddExtra(TrampolineBuilder);
} }
} }
} }

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -86,6 +86,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IBrush { get; } public IXamlType IBrush { get; }
public IXamlType ImmutableSolidColorBrush { get; } public IXamlType ImmutableSolidColorBrush { get; }
public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; } public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; }
public IXamlType TypeUtilities { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{ {
@ -189,6 +190,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush"); IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush");
ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush"); ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush");
ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List<IXamlType> { UInt }); ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List<IXamlType> { UInt });
TypeUtilities = cfg.TypeSystem.GetType("Avalonia.Utilities.TypeUtilities");
} }
} }

58
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs

@ -14,7 +14,7 @@ using XamlX.Emit;
using XamlX.IL; using XamlX.IL;
using Avalonia.Utilities; using Avalonia.Utilities;
using XamlIlEmitContext = XamlX.Emit.XamlEmitContext<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>; using XamlIlEmitContext = XamlX.Emit.XamlEmitContextWithLocals<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{ {
@ -652,58 +652,56 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen) public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{ {
var trampolineBuilder = context.Configuration.GetExtra<XamlIlTrampolineBuilder>();
var objectType = context.Configuration.WellKnownTypes.Object;
codeGen codeGen
.Ldtoken(_executeMethod); .Ldstr(_executeMethod.Name)
.Ldnull()
if (_canExecuteMethod is not null) .Ldftn(trampolineBuilder.EmitCommandExecuteTrampoline(context, _executeMethod))
.Newobj(context.Configuration.TypeSystem.GetType("System.Action`2")
.MakeGenericType(objectType, objectType)
.GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") }));
if (_canExecuteMethod is null)
{ {
codeGen.Ldtoken(_canExecuteMethod); codeGen.Ldnull();
} }
else else
{ {
using var canExecuteMethodHandle = codeGen.LocalsPool.GetLocal(context.Configuration.TypeSystem.GetType("System.RuntimeMethodHandle"));
codeGen codeGen
.Ldloca(canExecuteMethodHandle.Local) .Ldnull()
.Emit(OpCodes.Initobj) .Ldftn(trampolineBuilder.EmitCommandCanExecuteTrampoline(context, _canExecuteMethod))
.Ldloc(canExecuteMethodHandle.Local); .Newobj(context.Configuration.TypeSystem.GetType("System.Func`3")
.MakeGenericType(objectType, objectType, context.Configuration.WellKnownTypes.Boolean)
.GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") }));
} }
if (_dependsOnProperties is { Count:> 0 }) if (_dependsOnProperties is { Count:> 1 })
{ {
using var dependsOnProperties = codeGen.LocalsPool.GetLocal(context.Configuration.WellKnownTypes.String.MakeArrayType(1)); using var dependsOnPropertiesArray = context.GetLocalOfType(context.Configuration.WellKnownTypes.String.MakeArrayType(1));
codeGen codeGen
.Ldc_I4(_dependsOnProperties.Count) .Ldc_I4(_dependsOnProperties.Count)
.Newarr(context.Configuration.WellKnownTypes.String) .Newarr(context.Configuration.WellKnownTypes.String)
.Stloc(dependsOnProperties.Local); .Stloc(dependsOnPropertiesArray.Local);
for (var i = 0; i < _dependsOnProperties.Count; i++) for (int i = 0; i < _dependsOnProperties.Count; i++)
{ {
var prop = _dependsOnProperties[i];
codeGen codeGen
.Ldloc(dependsOnProperties.Local) .Ldloc(dependsOnPropertiesArray.Local)
.Ldc_I4(i) .Ldc_I4(i)
.Ldstr(prop) .Ldstr(_dependsOnProperties[i])
.Stelem_ref(); .Stelem_ref();
} }
codeGen.Ldloc(dependsOnProperties.Local); codeGen.Ldloc(dependsOnPropertiesArray.Local);
} }
else else
{ {
codeGen.Ldnull(); codeGen.Ldnull();
} }
if (_executeMethod.Parameters.Count != 0) codeGen
{ .EmitCall(context.GetAvaloniaTypes()
codeGen.EmitCall( .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Command"));
context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "CommandWithParameter"));
}
else
{
codeGen.EmitCall(
context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Command"));
}
} }
} }
@ -829,7 +827,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
} }
} }
class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult> class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstLocalsEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{ {
private readonly List<IXamlIlBindingPathElementNode> _transformElements; private readonly List<IXamlIlBindingPathElementNode> _transformElements;

117
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using XamlX.Emit;
using XamlX.IL;
using XamlX.TypeSystem;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using System.Reflection.Emit;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
internal class XamlIlTrampolineBuilder
{
private IXamlTypeBuilder<IXamlILEmitter> _builder;
private Dictionary<string, IXamlMethod> _trampolines = new();
public XamlIlTrampolineBuilder(IXamlTypeBuilder<IXamlILEmitter> builder)
{
_builder = builder;
}
public IXamlMethod EmitCommandExecuteTrampoline(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlMethod executeMethod)
{
Debug.Assert(!executeMethod.IsStatic);
string methodName = $"{executeMethod.DeclaringType.GetFqn()}+{executeMethod.Name}_{executeMethod.Parameters.Count}!CommandExecuteTrampoline";
if (_trampolines.TryGetValue(methodName, out var method))
{
return method;
}
var trampoline = _builder.DefineMethod(
context.Configuration.WellKnownTypes.Void,
new[] { context.Configuration.WellKnownTypes.Object, context.Configuration.WellKnownTypes.Object },
methodName,
true,
true,
false);
var gen = trampoline.Generator;
if (executeMethod.DeclaringType.IsValueType)
{
gen.Ldarg_0()
.Unbox(executeMethod.DeclaringType);
}
else
{
gen.Ldarg_0()
.Castclass(executeMethod.DeclaringType);
}
if (executeMethod.Parameters.Count != 0)
{
Debug.Assert(executeMethod.Parameters.Count == 1);
if (executeMethod.Parameters[0] != context.Configuration.WellKnownTypes.Object)
{
var convertedValue = gen.DefineLocal(context.Configuration.WellKnownTypes.Object);
gen.Ldtype(executeMethod.Parameters[0])
.Ldarg(1)
.EmitCall(context.Configuration.WellKnownTypes.CultureInfo.FindMethod(m => m.Name == "get_CurrentCulture"))
.Ldloca(convertedValue)
.EmitCall(
context.GetAvaloniaTypes().TypeUtilities.FindMethod(m => m.Name == "TryConvert"),
swallowResult: true)
.Ldloc(convertedValue)
.Unbox_Any(executeMethod.Parameters[0]);
}
else
{
gen.Ldarg(1);
}
}
gen.Emit(OpCodes.Tailcall);
gen.EmitCall(executeMethod, swallowResult: true);
gen.Ret();
_trampolines.Add(methodName, trampoline);
return trampoline;
}
public IXamlMethod EmitCommandCanExecuteTrampoline(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlMethod canExecuteMethod)
{
Debug.Assert(!canExecuteMethod.IsStatic);
Debug.Assert(canExecuteMethod.Parameters.Count == 1);
Debug.Assert(canExecuteMethod.ReturnType == context.Configuration.WellKnownTypes.Boolean);
string methodName = $"{canExecuteMethod.DeclaringType.GetFqn()}+{canExecuteMethod.Name}!CommandCanExecuteTrampoline";
if (_trampolines.TryGetValue(methodName, out var method))
{
return method;
}
var trampoline = _builder.DefineMethod(
context.Configuration.WellKnownTypes.Boolean,
new[] { context.Configuration.WellKnownTypes.Object, context.Configuration.WellKnownTypes.Object },
methodName,
true,
true,
false);
if (canExecuteMethod.DeclaringType.IsValueType)
{
trampoline.Generator
.Ldarg_0()
.Unbox(canExecuteMethod.DeclaringType);
}
else
{
trampoline.Generator
.Ldarg_0()
.Castclass(canExecuteMethod.DeclaringType);
}
trampoline.Generator
.Ldarg(1)
.Emit(OpCodes.Tailcall)
.EmitCall(canExecuteMethod)
.Ret();
_trampolines.Add(methodName, trampoline);
return trampoline;
}
}
}

169
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs

@ -12,11 +12,15 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
internal class CommandAccessorPlugin : IPropertyAccessorPlugin internal class CommandAccessorPlugin : IPropertyAccessorPlugin
{ {
private readonly Func<WeakReference<object>, IPropertyAccessor> _commandAccessorFactory; private readonly Action<object, object> _execute;
private readonly Func<object, object, bool> _canExecute;
private readonly ISet<string> _dependsOnProperties;
public CommandAccessorPlugin(Func<WeakReference<object>, IPropertyAccessor> commandAccessorFactory) public CommandAccessorPlugin(Action<object, object> execute, Func<object, object, bool> canExecute, ISet<string> dependsOnProperties)
{ {
_commandAccessorFactory = commandAccessorFactory; _execute = execute;
_canExecute = canExecute;
_dependsOnProperties = dependsOnProperties;
} }
public bool Match(object obj, string propertyName) public bool Match(object obj, string propertyName)
@ -26,25 +30,76 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName) public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
{ {
return _commandAccessorFactory(reference); return new CommandAccessor(reference, _execute, _canExecute, _dependsOnProperties);
} }
internal abstract class CommandAccessorBase : PropertyAccessorBase private sealed class CommandAccessor : PropertyAccessorBase
{ {
private readonly WeakReference<object> _reference; private readonly WeakReference<object> _reference;
private Command _command;
private readonly ISet<string> _dependsOnProperties; private readonly ISet<string> _dependsOnProperties;
public CommandAccessorBase(WeakReference<object> reference, ISet<string> dependsOnProperties) public CommandAccessor(WeakReference<object> reference, Action<object, object> execute, Func<object, object, bool> canExecute, ISet<string> dependsOnProperties)
{ {
Contract.Requires<ArgumentNullException>(reference != null); Contract.Requires<ArgumentNullException>(reference != null);
_reference = reference; _reference = reference;
_dependsOnProperties = dependsOnProperties; _dependsOnProperties = dependsOnProperties;
_command = new Command(reference, execute, canExecute);
} }
public override Type PropertyType => typeof(ICommand); public override object Value => _reference.TryGetTarget(out var _) ? _command : null;
private void RaiseCanExecuteChanged()
{
_command.RaiseCanExecuteChanged();
}
private sealed class Command : ICommand
{
private readonly WeakReference<object> _target;
private readonly Action<object, object> _execute;
private readonly Func<object, object, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public Command(WeakReference<object> target, Action<object, object> execute, Func<object, object, bool> canExecute)
{
_target = target;
_execute = execute;
_canExecute = canExecute;
}
public void RaiseCanExecuteChanged()
{
Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty)
, Threading.DispatcherPriority.Input);
}
public bool CanExecute(object parameter)
{
if (_target.TryGetTarget(out var target))
{
if (_canExecute == null)
{
return true;
}
return _canExecute(target, parameter);
}
return false;
}
protected abstract void RaiseCanExecuteChanged(); public void Execute(object parameter)
{
if (_target.TryGetTarget(out var target))
{
_execute(target, parameter);
}
}
}
public override Type PropertyType => typeof(ICommand);
public override bool SetValue(object value, BindingPriority priority) public override bool SetValue(object value, BindingPriority priority)
{ {
@ -55,7 +110,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName)) if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName))
{ {
SendCurrentValue(); RaiseCanExecuteChanged();
} }
} }
@ -97,101 +152,5 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
} }
} }
} }
internal sealed class CommandWithParameterAccessor<T> : CommandAccessorBase
{
private Command _command;
public CommandWithParameterAccessor(WeakReference<object> target, ISet<string> dependsOnProperties, Action<T> execute, Func<object, bool> canExecute)
: base(target, dependsOnProperties)
{
_command = new Command(execute, canExecute);
}
public override object Value => _command;
protected override void RaiseCanExecuteChanged()
{
_command.RaiseCanExecuteChanged();
}
private sealed class Command : ICommand
{
private readonly Action<T> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public Command(Action<T> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public void RaiseCanExecuteChanged()
{
Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty)
, Threading.DispatcherPriority.Input);
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
}
}
internal sealed class CommandWithoutParameterAccessor : CommandAccessorBase
{
private Command _command;
public CommandWithoutParameterAccessor(WeakReference<object> target, ISet<string> dependsOnProperties, Action execute, Func<object, bool> canExecute)
: base(target, dependsOnProperties)
{
_command = new Command(execute, canExecute);
}
public override object Value => _command;
protected override void RaiseCanExecuteChanged()
{
_command.RaiseCanExecuteChanged();
}
private sealed class Command : ICommand
{
private readonly Action _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public Command(Action execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public void RaiseCanExecuteChanged()
{
Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty)
, Threading.DispatcherPriority.Input);
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute();
}
}
}
} }
} }

59
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs

@ -37,8 +37,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
case PropertyElement prop: case PropertyElement prop:
node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory)); node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory));
break; break;
case IMethodAsCommandElement methodAsCommand: case MethodAsCommandElement methodAsCommand:
node = new PropertyAccessorNode(methodAsCommand.ExecuteMethod.Name, enableValidation, new CommandAccessorPlugin(methodAsCommand.CreateAccessor)); node = new PropertyAccessorNode(methodAsCommand.MethodName, enableValidation, new CommandAccessorPlugin(methodAsCommand.ExecuteMethod, methodAsCommand.CanExecuteMethod, methodAsCommand.DependsOnProperties));
break; break;
case MethodAsDelegateElement methodAsDelegate: case MethodAsDelegateElement methodAsDelegate:
node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType)); node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType));
@ -105,15 +105,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this; return this;
} }
public CompiledBindingPathBuilder Command(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties) public CompiledBindingPathBuilder Command(string methodName, Action<object, object> executeHelper, Func<object, object, bool> canExecuteHelper, string[] dependsOnProperties)
{ {
_elements.Add(new MethodAsCommandElement(executeMethod, canExecuteMethod, dependsOnProperties)); _elements.Add(new MethodAsCommandElement(methodName, executeHelper, canExecuteHelper, dependsOnProperties ?? Array.Empty<string>()));
return this;
}
public CompiledBindingPathBuilder CommandWithParameter<T>(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties)
{
_elements.Add(new MethodAsCommandElement<T>(executeMethod, canExecuteMethod, dependsOnProperties));
return this; return this;
} }
@ -216,49 +210,20 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public Type DelegateType { get; } public Type DelegateType { get; }
} }
internal interface IMethodAsCommandElement internal class MethodAsCommandElement : ICompiledBindingPathElement
{
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) public MethodAsCommandElement(string methodName, Action<object, object> executeHelper, Func<object, object, bool> canExecuteHelper, string[] dependsOnElements)
{ {
ExecuteMethod = (MethodInfo)MethodBase.GetMethodFromHandle(executeMethod); MethodName = methodName;
CanExecuteMethod = canExecuteMethod != default ? (MethodInfo)MethodBase.GetMethodFromHandle(canExecuteMethod) : null; ExecuteMethod = executeHelper;
CanExecuteMethod = canExecuteHelper;
DependsOnProperties = new HashSet<string>(dependsOnElements); DependsOnProperties = new HashSet<string>(dependsOnElements);
} }
public MethodInfo ExecuteMethod { get; } public string MethodName { get; }
public MethodInfo CanExecuteMethod { get; } public Action<object, object> ExecuteMethod { get; }
public Func<object, object, bool> CanExecuteMethod { get; }
public HashSet<string> DependsOnProperties { 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 internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement

4
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -1181,7 +1181,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:MethodAsCommandDataContext'> x:DataType='local:MethodAsCommandDataContext'>
<Button Name='button' Command='{Binding Do}' CommandParameter='{CompiledBinding Parameter, Mode=OneTime}'/> <Button Name='button' Command='{CompiledBinding Do}' CommandParameter='{CompiledBinding Parameter, Mode=OneTime}'/>
</Window>"; </Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button"); var button = window.FindControl<Button>("button");
@ -1209,7 +1209,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:MethodAsCommandDataContext'> x:DataType='local:MethodAsCommandDataContext'>
<Button Name='button' Command='{Binding Do}' CommandParameter='{CompiledBinding Parameter, Mode=OneWay}'/> <Button Name='button' Command='{CompiledBinding Do}' CommandParameter='{CompiledBinding Parameter, Mode=OneWay}'/>
</Window>"; </Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button"); var button = window.FindControl<Button>("button");

Loading…
Cancel
Save