Browse Source

Implement support for method->command direct binding (not hooked up yet)

pull/7246/head
Jeremy Koritzinsky 4 years ago
parent
commit
2647c5cc73
No known key found for this signature in database GPG Key ID: 25A7D1C8126B7A16
  1. 10692
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json
  2. 74
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  3. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  4. 197
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs
  5. 60
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs

10692
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json

File diff suppressed because it is too large

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

@ -595,6 +595,80 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
class XamlIlClrMethodAsCommandPathElementNode : IXamlIlBindingPathElementNode
{
private readonly IXamlMethod _executeMethod;
private readonly IXamlMethod _canExecuteMethod;
private readonly IReadOnlyList<string> _dependsOnProperties;
public XamlIlClrMethodAsCommandPathElementNode(IXamlType iCommandType, IXamlMethod executeMethod, IXamlMethod canExecuteMethod, IReadOnlyList<string> dependsOnProperties)
{
Type = iCommandType;
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_dependsOnProperties = dependsOnProperties;
}
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen
.Ldtoken(_executeMethod);
if (_canExecuteMethod is not null)
{
codeGen.Ldtoken(_canExecuteMethod);
}
else
{
using var canExecuteMethodHandle = codeGen.LocalsPool.GetLocal(context.Configuration.TypeSystem.GetType("System.Reflection.RuntimeMethodHandle"));
codeGen
.Ldloca(canExecuteMethodHandle.Local)
.Emit(OpCodes.Initobj)
.Ldloc(canExecuteMethodHandle.Local);
}
if (_dependsOnProperties is { Count:> 0 })
{
using var dependsOnProperties = codeGen.LocalsPool.GetLocal(context.Configuration.WellKnownTypes.String.MakeArrayType(1));
codeGen
.Ldc_I4(_dependsOnProperties.Count)
.Newarr(context.Configuration.WellKnownTypes.String)
.Stloc(dependsOnProperties.Local);
for (var i = 0; i < _dependsOnProperties.Count; i++)
{
var prop = _dependsOnProperties[i];
codeGen
.Ldloc(dependsOnProperties.Local)
.Ldc_I4(i)
.Ldstr(prop)
.Stelem_ref();
}
codeGen.Ldloc(dependsOnProperties.Local);
}
else
{
codeGen.Ldnull();
}
if (_executeMethod.Parameters.Count != 0)
{
codeGen.EmitCall(
context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "CommandWithParameter"));
}
else
{
codeGen.EmitCall(
context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Command"));
}
}
}
class XamlIlClrIndexerPathElementNode : IXamlIlBindingPathElementNode
{
private readonly IXamlProperty _property;

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -18,6 +18,7 @@
<Compile Include="MarkupExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindingExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\ArrayElementPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\CommandAccessorPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\CompiledBindingPath.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\FindVisualAncestorNode.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\MethodAccessorPlugin.cs" />

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

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
using Avalonia.Utilities;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
internal class CommandAccessorPlugin : IPropertyAccessorPlugin
{
private readonly Func<WeakReference<object>, IPropertyAccessor> _commandAccessorFactory;
public CommandAccessorPlugin(Func<WeakReference<object>, IPropertyAccessor> commandAccessorFactory)
{
_commandAccessorFactory = commandAccessorFactory;
}
public bool Match(object obj, string propertyName)
{
throw new InvalidOperationException("The CommandAccessorPlugin does not support dynamic matching");
}
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
{
return _commandAccessorFactory(reference);
}
internal abstract class CommandAccessorBase : PropertyAccessorBase
{
private readonly WeakReference<object> _reference;
private readonly ISet<string> _dependsOnProperties;
public CommandAccessorBase(WeakReference<object> reference, ISet<string> dependsOnProperties)
{
Contract.Requires<ArgumentNullException>(reference != null);
_reference = reference;
_dependsOnProperties = dependsOnProperties;
}
public override Type PropertyType => typeof(ICommand);
protected abstract void RaiseCanExecuteChanged();
public override bool SetValue(object value, BindingPriority priority)
{
return false;
}
void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName))
{
SendCurrentValue();
}
}
protected override void SubscribeCore()
{
SendCurrentValue();
SubscribeToChanges();
}
protected override void UnsubscribeCore()
{
if (_dependsOnProperties is { Count: > 0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
{
WeakEventHandlerManager.Unsubscribe<PropertyChangedEventArgs, InpcPropertyAccessor>(
inpc,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
}
}
private void SendCurrentValue()
{
try
{
var value = Value;
PublishValue(value);
}
catch { }
}
private void SubscribeToChanges()
{
if (_dependsOnProperties is { Count:>0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
{
WeakEventHandlerManager.Subscribe<INotifyPropertyChanged, PropertyChangedEventArgs, InpcPropertyAccessor>(
inpc,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
}
}
}
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();
}
}
}
}
}

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

@ -37,6 +37,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
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;
@ -102,6 +105,18 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
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>());
@ -201,6 +216,51 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
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();

Loading…
Cancel
Save