Skip to content

Commit

Permalink
Merge pull request AvaloniaUI#8119 from AvaloniaUI/feature/ireflectab…
Browse files Browse the repository at this point in the history
…letype

Added support for IReflectableType in InpcPropertyAccessorPlugin
  • Loading branch information
maxkatz6 authored and danwalmsley committed Jun 1, 2022
1 parent 81698eb commit 10a0f6a
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 7 deletions.
19 changes: 12 additions & 7 deletions src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
new Dictionary<(Type, string), PropertyInfo?>();

/// <inheritdoc/>
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null;

/// <summary>
/// Starts monitoring the value of a property on an object.
Expand All @@ -36,7 +36,7 @@ public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
if (!reference.TryGetTarget(out var instance) || instance is null)
return null;

var p = GetFirstPropertyWithName(instance.GetType(), propertyName);
var p = GetFirstPropertyWithName(instance, propertyName);

if (p != null)
{
Expand All @@ -50,8 +50,16 @@ public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
}
}

private PropertyInfo? GetFirstPropertyWithName(Type type, string propertyName)
private const BindingFlags PropertyBindingFlags =
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;

private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName)
{
if (instance is IReflectableType reflectableType)
return reflectableType.GetTypeInfo().GetProperty(propertyName, PropertyBindingFlags);

var type = instance.GetType();

var key = (type, propertyName);

if (!_propertyLookup.TryGetValue(key, out var propertyInfo))
Expand All @@ -66,10 +74,7 @@ public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{
PropertyInfo? found = null;

const BindingFlags bindingFlags =
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;

var properties = type.GetProperties(bindingFlags);
var properties = type.GetProperties(PropertyBindingFlags);

foreach (PropertyInfo propertyInfo in properties)
{
Expand Down
19 changes: 19 additions & 0 deletions tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,25 @@ public void Combined_OneTime_And_OneWayToSource_Bindings_Should_Release_Subscrip

Assert.Equal(0, source.SubscriberCount);
}

[Fact]
public void Binding_Can_Resolve_Property_From_IReflectableType_Type()
{
var source = new DynamicReflectableType { ["Foo"] = "foo" };
var target = new TwoWayBindingTest { DataContext = source };
var binding = new Binding
{
Path = "Foo",
};

target.Bind(TwoWayBindingTest.TwoWayProperty, binding);

Assert.Equal("foo", target.TwoWay);
source["Foo"] = "bar";
Assert.Equal("bar", target.TwoWay);
target.TwoWay = "baz";
Assert.Equal("baz", source["Foo"]);
}

private class StyledPropertyClass : AvaloniaObject
{
Expand Down
221 changes: 221 additions & 0 deletions tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using Moq;

namespace Avalonia.Markup.UnitTests.Data;

class DynamicReflectableType : IReflectableType, INotifyPropertyChanged, IEnumerable<KeyValuePair<string, object>>
{
private Dictionary<string, object> _dic = new();

public TypeInfo GetTypeInfo()
{
return new FakeTypeInfo();
}

public void Add(string key, object value)
{
_dic.Add(key, value);

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key));
}

public object this[string key]
{
get => _dic[key];
set
{
_dic[key] = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key));
}
}

public event PropertyChangedEventHandler PropertyChanged;
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return _dic.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_dic).GetEnumerator();
}


class FakeTypeInfo : TypeInfo
{
protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types,
ParameterModifier[] modifiers)
{
var propInfo = new Mock<PropertyInfo>();
propInfo.SetupGet(x => x.Name).Returns(name);
propInfo.SetupGet(x => x.PropertyType).Returns(typeof(object));
propInfo.SetupGet(x => x.CanWrite).Returns(true);
propInfo.Setup(x => x.GetValue(It.IsAny<object>(), It.IsAny<object[]>()))
.Returns((object target, object [] _) => ((DynamicReflectableType)target)._dic.GetValueOrDefault(name));
propInfo.Setup(x => x.SetValue(It.IsAny<object>(), It.IsAny<object>(), It.IsAny<object[]>()))
.Callback((object target, object value, object [] _) =>
{
((DynamicReflectableType)target)._dic[name] = value;
});
return propInfo.Object;
}

#region NotSupported


public override object[] GetCustomAttributes(bool inherit)
{
throw new NotSupportedException();
}

public override object[] GetCustomAttributes(Type attributeType, bool inherit)
{
throw new NotSupportedException();
}

public override bool IsDefined(Type attributeType, bool inherit)
{
throw new NotSupportedException();
}

public override Module Module { get; }
public override string Namespace { get; }
public override string Name { get; }
protected override TypeAttributes GetAttributeFlagsImpl()
{
throw new NotSupportedException();
}

protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention,
Type[] types, ParameterModifier[] modifiers)
{
throw new NotSupportedException();
}

public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

public override Type GetElementType()
{
throw new NotSupportedException();
}

public override EventInfo GetEvent(string name, BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

public override EventInfo[] GetEvents(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

public override FieldInfo GetField(string name, BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

public override FieldInfo[] GetFields(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention,
Type[] types, ParameterModifier[] modifiers)
{
throw new NotSupportedException();
}

public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args,
ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters)
{
throw new NotSupportedException();
}

public override Type UnderlyingSystemType { get; }

protected override bool IsArrayImpl()
{
throw new NotSupportedException();
}

protected override bool IsByRefImpl()
{
throw new NotSupportedException();
}

protected override bool IsCOMObjectImpl()
{
throw new NotSupportedException();
}

protected override bool IsPointerImpl()
{
throw new NotSupportedException();
}

protected override bool IsPrimitiveImpl()
{
throw new NotSupportedException();
}

public override Assembly Assembly { get; }
public override string AssemblyQualifiedName { get; }
public override Type BaseType { get; }
public override string FullName { get; }
public override Guid GUID { get; }



protected override bool HasElementTypeImpl()
{
throw new NotSupportedException();
}

public override Type GetNestedType(string name, BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

public override Type[] GetNestedTypes(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}

public override Type GetInterface(string name, bool ignoreCase)
{
throw new NotSupportedException();
}

public override Type[] GetInterfaces()
{
throw new NotSupportedException();
}


#endregion

}
}

0 comments on commit 10a0f6a

Please sign in to comment.