Browse Source

Initial code to enable binding to a method.

pull/1179/head
Jeremy Koritzinsky 9 years ago
parent
commit
12ec059acb
  1. 1
      src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs
  2. 91
      src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs
  3. 65
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs

1
src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs

@ -25,6 +25,7 @@ namespace Avalonia.Markup.Data
new List<IPropertyAccessorPlugin>
{
new AvaloniaPropertyAccessorPlugin(),
new MethodAccessorPlugin(),
new InpcPropertyAccessorPlugin(),
};

91
src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Data;
using System.Reflection;
using System.Linq;
namespace Avalonia.Markup.Data.Plugins
{
class MethodAccessorPlugin : IPropertyAccessorPlugin
{
public bool Match(object obj, string propertyName)
=> obj.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName) != null;
public IPropertyAccessor Start(WeakReference reference, string propertyName)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(propertyName != null);
var instance = reference.Target;
var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName);
if (method != null)
{
return new Accessor(reference, method);
}
else
{
var message = $"Could not find CLR method '{propertyName}' on '{instance}'";
var exception = new MissingMemberException(message);
return new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
}
}
private class Accessor : PropertyAccessorBase
{
public Accessor(WeakReference reference, MethodInfo method)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(method != null);
var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray();
var returnType = method.ReturnType;
// TODO: Throw exception if more than 8 parameters or more than 7 + return type.
// Do this here or in the caller? Here probably
if (returnType == typeof(void))
{
if (paramTypes.Length == 0)
{
PropertyType = typeof(Action);
}
else
{
PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes);
}
}
else
{
var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray();
PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters);
}
// TODO: Is this going to leak?
// TODO: Static methods?
Value = method.CreateDelegate(PropertyType, reference.Target);
}
public override Type PropertyType { get; }
public override object Value { get; }
public override bool SetValue(object value, BindingPriority priority) => false;
protected override void SubscribeCore(IObserver<object> observer)
{
SendCurrentValue();
}
private void SendCurrentValue()
{
try
{
var value = Value;
Observer.OnNext(value);
}
catch { }
}
}
}
}

65
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs

@ -0,0 +1,65 @@
using Avalonia.Data;
using Avalonia.Markup.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Avalonia.Markup.UnitTests.Data
{
public class ExpressionObserverTests_Method
{
private class TestObject
{
public void MethodWithoutReturn() { }
public int MethodWithReturn() => 0;
public int MethodWithReturnAndParameters(int i) => i;
}
[Fact]
public async Task Should_Get_Method()
{
var data = new TestObject();
var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithoutReturn));
var result = await observer.Take(1);
Assert.NotNull(result);
GC.KeepAlive(data);
}
[Theory]
[InlineData(nameof(TestObject.MethodWithoutReturn), typeof(Action))]
[InlineData(nameof(TestObject.MethodWithReturn), typeof(Func<int>))]
[InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func<int, int>))]
public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType)
{
var data = new TestObject();
var observer = new ExpressionObserver(data, methodName);
var result = await observer.Take(1);
Assert.IsType(expectedType, result);
GC.KeepAlive(data);
}
[Fact]
public async Task Can_Call_Method_Returned_From_Observer()
{
var data = new TestObject();
var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithReturnAndParameters));
var result = await observer.Take(1);
var callback = (Func<int, int>)result;
Assert.Equal(1, callback(1));
GC.KeepAlive(data);
}
}
}
Loading…
Cancel
Save