Browse Source

Change property notification management to directly create property accessors. Fix indexer bindings for arrays.

PR Feedback.
pull/3001/head
Jeremy Koritzinsky 7 years ago
parent
commit
da00a14149
  1. 8
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  2. 4
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  3. 67
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs
  4. 34
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  5. 255
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/NotifyingPropertyInfoHelpers.cs
  6. 230
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs
  7. 91
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs
  8. 6
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  9. 7
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
  10. 6
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
  11. 17
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  12. 6
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  13. 101
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs
  14. 17
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs
  15. 103
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs
  16. 2
      src/Markup/Avalonia.Markup/Data/Binding.cs
  17. 149
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

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

@ -53,14 +53,18 @@ namespace Avalonia.Build.Tasks
var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(clrPropertiesDef);
var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(indexerAccessorClosure);
var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)));
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)));
var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext",

4
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -17,9 +17,10 @@
<Compile Include="Extensions.cs" />
<Compile Include="MarkupExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindingExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\ArrayElementPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\CompiledBindingPath.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\NotifyingPropertyInfoHelpers.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\ObservableStreamPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\PropertyInfoAccessorFactory.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\PropertyInfoAccessorPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\TaskStreamPlugin.cs" />
<Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
@ -69,6 +70,7 @@
<Compile Include="XamlIl\CompilerExtensions\Transformers\XNameTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\XamlIlBindingPathHelper.cs" />
<Compile Include="XamlIl\CompilerExtensions\XamlIlClrPropertyInfoHelper.cs" />
<Compile Include="XamlIl\CompilerExtensions\XamlIlPropertyInfoAccessorFactoryEmitter.cs" />
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlParentStackProvider.cs" />
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
<Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />

67
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
class ArrayElementPlugin : IPropertyAccessorPlugin
{
private readonly int[] _indices;
private readonly Type _elementType;
public ArrayElementPlugin(int[] indices, Type elementType)
{
_indices = indices;
_elementType = elementType;
}
public bool Match(object obj, string propertyName)
{
throw new InvalidOperationException("The ArrayElementPlugin does not support dynamic matching");
}
public IPropertyAccessor Start(WeakReference reference, string propertyName)
{
return new Accessor(reference, _indices, _elementType);
}
class Accessor : PropertyAccessorBase
{
private readonly int[] _indices;
private readonly WeakReference _reference;
public Accessor(WeakReference reference, int[] indices, Type elementType)
{
_reference = reference;
_indices = indices;
PropertyType = elementType;
}
public override Type PropertyType { get; }
public override object Value => _reference.Target is Array arr ? arr.GetValue(_indices) : null;
public override bool SetValue(object value, BindingPriority priority)
{
if (_reference.Target is Array arr)
{
arr.SetValue(value, _indices);
return true;
}
return false;
}
protected override void SubscribeCore()
{
PublishValue(Value);
}
protected override void UnsubscribeCore()
{
}
}
}
}

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

@ -32,7 +32,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
node = new LogicalNotNode();
break;
case PropertyElement prop:
node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property));
node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory));
break;
case ArrayElementPathElement arr:
node = new PropertyAccessorNode(CommonPropertyNames.IndexerName, enableValidation, new ArrayElementPlugin(arr.Indices, arr.ElementType));
break;
case AncestorPathElement ancestor:
node = new FindAncestorNode(ancestor.AncestorType, ancestor.Level);
@ -69,9 +72,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this;
}
public CompiledBindingPathBuilder Property(INotifyingPropertyInfo info)
public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference, IPropertyInfo, IPropertyAccessor> accessorFactory)
{
_elements.Add(new PropertyElement(info));
_elements.Add(new PropertyElement(info, accessorFactory));
return this;
}
@ -105,6 +108,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this;
}
public CompiledBindingPathBuilder ArrayElement(int[] indices, Type elementType)
{
_elements.Add(new ArrayElementPathElement(indices, elementType));
return this;
}
public CompiledBindingPath Build() => new CompiledBindingPath(_elements);
}
@ -121,12 +130,15 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class PropertyElement : ICompiledBindingPathElement
{
public PropertyElement(INotifyingPropertyInfo property)
public PropertyElement(IPropertyInfo property, Func<WeakReference, IPropertyInfo, IPropertyAccessor> accessorFactory)
{
Property = property;
AccessorFactory = accessorFactory;
}
public INotifyingPropertyInfo Property { get; }
public IPropertyInfo Property { get; }
public Func<WeakReference, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
}
internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement
@ -176,4 +188,16 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public INameScope NameScope { get; }
public string Name { get; }
}
internal class ArrayElementPathElement : ICompiledBindingPathElement
{
public ArrayElementPathElement(int[] indices, Type elementType)
{
Indices = indices;
ElementType = elementType;
}
public int[] Indices { get; }
public Type ElementType { get; }
}
}

255
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/NotifyingPropertyInfoHelpers.cs

@ -1,255 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using Avalonia.Data.Core;
using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
public static class NotifyingPropertyInfoHelpers
{
public static INotifyingPropertyInfo CreateINPCPropertyInfo(IPropertyInfo basePropertyInfo)
=> new INPCPropertyInfo(basePropertyInfo);
public static INotifyingPropertyInfo CreateAvaloniaPropertyInfo(AvaloniaProperty property)
=> new AvaloniaPropertyInfo(property);
public static INotifyingPropertyInfo CreateIndexerPropertyInfo(IPropertyInfo basePropertyInfo, int argument)
=> new IndexerInfo(basePropertyInfo, argument);
}
public interface INotifyingPropertyInfo : IPropertyInfo
{
void OnPropertyChanged(object target, EventHandler handler);
void RemoveListener(object target, EventHandler handler);
}
internal abstract class NotifyingPropertyInfoBase : INotifyingPropertyInfo
{
private readonly IPropertyInfo _base;
protected readonly ConditionalWeakTable<object, EventHandler> _changedHandlers = new ConditionalWeakTable<object, EventHandler>();
public NotifyingPropertyInfoBase(IPropertyInfo baseProperty)
{
_base = baseProperty;
}
public string Name => _base.Name;
public bool CanSet => _base.CanSet;
public bool CanGet => _base.CanGet;
public Type PropertyType => _base.PropertyType;
public object Get(object target)
{
return _base.Get(target);
}
public void Set(object target, object value)
{
_base.Set(target, value);
}
public void OnPropertyChanged(object target, EventHandler handler)
{
if (ValidateTargetType(target))
{
return;
}
if (_changedHandlers.TryGetValue(target, out var value))
{
_changedHandlers.Remove(target);
_changedHandlers.Add(target, (EventHandler)Delegate.Combine(value, handler));
}
else
{
_changedHandlers.Add(target, handler);
SubscribeToChangesForNewTarget(target);
}
}
protected abstract bool ValidateTargetType(object target);
protected abstract void SubscribeToChangesForNewTarget(object target);
protected abstract void UnsubscribeToChangesForTarget(object target);
protected bool TryGetHandlersForTarget(object target, out EventHandler handlers)
=> _changedHandlers.TryGetValue(target, out handlers);
public void RemoveListener(object target, EventHandler handler)
{
if (!ValidateTargetType(target))
{
return;
}
if (_changedHandlers.TryGetValue(target, out var value))
{
_changedHandlers.Remove(target);
EventHandler modified = (EventHandler)Delegate.Remove(value, handler);
if (modified != null)
{
_changedHandlers.Add(target, modified);
}
else
{
UnsubscribeToChangesForTarget(target);
}
}
}
}
internal class INPCPropertyInfo : NotifyingPropertyInfoBase
{
public INPCPropertyInfo(IPropertyInfo baseProperty)
:base(baseProperty)
{
}
void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (Name == e.PropertyName && TryGetHandlersForTarget(sender, out var handlers))
{
handlers(sender, EventArgs.Empty);
}
}
protected override bool ValidateTargetType(object target)
{
return target is INotifyPropertyChanged;
}
protected override void SubscribeToChangesForNewTarget(object target)
{
if (target is INotifyPropertyChanged inpc)
{
WeakEventHandlerManager.Subscribe<INotifyPropertyChanged, PropertyChangedEventArgs, INPCPropertyInfo>(
inpc,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
}
}
protected override void UnsubscribeToChangesForTarget(object target)
{
if (target is INotifyPropertyChanged)
{
WeakEventHandlerManager.Unsubscribe<PropertyChangedEventArgs, INPCPropertyInfo>(
target,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
}
}
}
internal class AvaloniaPropertyInfo : NotifyingPropertyInfoBase
{
private readonly AvaloniaProperty _base;
public AvaloniaPropertyInfo(AvaloniaProperty baseProperty)
:base(baseProperty)
{
_base = baseProperty;
}
protected override void SubscribeToChangesForNewTarget(object target)
{
IAvaloniaObject obj = (IAvaloniaObject)target;
obj.PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (_base == e.Property && TryGetHandlersForTarget(sender, out var handlers))
{
handlers(sender, EventArgs.Empty);
}
}
protected override void UnsubscribeToChangesForTarget(object target)
{
((IAvaloniaObject)target).PropertyChanged -= OnPropertyChanged;
}
protected override bool ValidateTargetType(object target)
{
return target is IAvaloniaObject;
}
}
internal class IndexerInfo : INPCPropertyInfo
{
private int _index;
public IndexerInfo(IPropertyInfo baseProperty, int indexerArgument) : base(baseProperty)
{
_index = indexerArgument;
}
protected override void SubscribeToChangesForNewTarget(object target)
{
base.SubscribeToChangesForNewTarget(target);
if (target is INotifyCollectionChanged incc)
{
WeakEventHandlerManager.Subscribe<INotifyCollectionChanged, NotifyCollectionChangedEventArgs, IndexerInfo>(
incc,
nameof(INotifyCollectionChanged.CollectionChanged),
OnNotifyCollectionChanged);
}
}
protected override void UnsubscribeToChangesForTarget(object target)
{
base.UnsubscribeToChangesForTarget(target);
if (target is INotifyCollectionChanged)
{
WeakEventHandlerManager.Unsubscribe<NotifyCollectionChangedEventArgs, IndexerInfo>(
target,
nameof(INotifyCollectionChanged.CollectionChanged),
OnNotifyCollectionChanged);
}
}
void OnNotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (ShouldNotifyListeners(args) && TryGetHandlersForTarget(sender, out var handlers))
{
handlers(sender, EventArgs.Empty);
}
}
bool ShouldNotifyListeners(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
return _index >= e.NewStartingIndex;
case NotifyCollectionChangedAction.Remove:
return _index >= e.OldStartingIndex;
case NotifyCollectionChangedAction.Replace:
return _index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems.Count;
case NotifyCollectionChangedAction.Move:
return (_index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems.Count) ||
(_index >= e.OldStartingIndex &&
_index < e.OldStartingIndex + e.OldItems.Count);
case NotifyCollectionChangedAction.Reset:
return true;
}
return false;
}
protected override bool ValidateTargetType(object target)
=> base.ValidateTargetType(target) || target is INotifyCollectionChanged;
}
}

230
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs

@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Text;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Utilities;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
public static class PropertyInfoAccessorFactory
{
public static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference target, IPropertyInfo property)
=> new InpcPropertyAccessor(target, property);
public static IPropertyAccessor CreateAvaloniaPropertyAccessor(WeakReference target, IPropertyInfo property)
=> new AvaloniaPropertyAccessor(new WeakReference<AvaloniaObject>((AvaloniaObject)target.Target), (AvaloniaProperty)property);
public static IPropertyAccessor CreateIndexerPropertyAccessor(WeakReference target, IPropertyInfo property, int argument)
=> new IndexerAccessor(target, property, argument);
}
internal class AvaloniaPropertyAccessor : PropertyAccessorBase
{
private readonly WeakReference<AvaloniaObject> _reference;
private readonly AvaloniaProperty _property;
private IDisposable _subscription;
public AvaloniaPropertyAccessor(WeakReference<AvaloniaObject> reference, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(property != null);
_reference = reference;
_property = property;
}
public AvaloniaObject Instance
{
get
{
_reference.TryGetTarget(out var result);
return result;
}
}
public override Type PropertyType => _property.PropertyType;
public override object Value => Instance?.GetValue(_property);
public override bool SetValue(object value, BindingPriority priority)
{
if (!_property.IsReadOnly)
{
Instance.SetValue(_property, value, priority);
return true;
}
return false;
}
protected override void SubscribeCore()
{
_subscription = Instance?.GetObservable(_property).Subscribe(PublishValue);
}
protected override void UnsubscribeCore()
{
_subscription?.Dispose();
_subscription = null;
}
}
internal class InpcPropertyAccessor : PropertyAccessorBase
{
protected readonly WeakReference _reference;
private readonly IPropertyInfo _property;
public InpcPropertyAccessor(WeakReference reference, IPropertyInfo property)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(property != null);
_reference = reference;
_property = property;
}
public override Type PropertyType => _property.PropertyType;
public override object Value
{
get
{
var o = _reference.Target;
return (o != null) ? _property.Get(o) : null;
}
}
public override bool SetValue(object value, BindingPriority priority)
{
if (_property.CanSet && _reference.IsAlive)
{
_property.Set(_reference.Target, value);
SendCurrentValue();
return true;
}
return false;
}
void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
{
SendCurrentValue();
}
}
protected override void SubscribeCore()
{
SendCurrentValue();
SubscribeToChanges();
}
protected override void UnsubscribeCore()
{
var inpc = _reference.Target as INotifyPropertyChanged;
if (inpc != null)
{
WeakEventHandlerManager.Unsubscribe<PropertyChangedEventArgs, InpcPropertyAccessor>(
inpc,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
}
}
protected void SendCurrentValue()
{
try
{
var value = Value;
PublishValue(value);
}
catch { }
}
private void SubscribeToChanges()
{
var inpc = _reference.Target as INotifyPropertyChanged;
if (inpc != null)
{
WeakEventHandlerManager.Subscribe<INotifyPropertyChanged, PropertyChangedEventArgs, InpcPropertyAccessor>(
inpc,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
}
}
}
internal class IndexerAccessor : InpcPropertyAccessor
{
private int _index;
public IndexerAccessor(WeakReference target, IPropertyInfo basePropertyInfo, int argument)
:base(target, basePropertyInfo)
{
_index = argument;
}
protected override void SubscribeCore()
{
base.SubscribeCore();
if (_reference.Target is INotifyCollectionChanged incc)
{
WeakEventHandlerManager.Subscribe<INotifyCollectionChanged, NotifyCollectionChangedEventArgs, IndexerAccessor>(
incc,
nameof(INotifyCollectionChanged.CollectionChanged),
OnNotifyCollectionChanged);
}
}
protected override void UnsubscribeCore()
{
base.UnsubscribeCore();
if (_reference.Target is INotifyCollectionChanged incc)
{
WeakEventHandlerManager.Unsubscribe<NotifyCollectionChangedEventArgs, IndexerAccessor>(
incc,
nameof(INotifyCollectionChanged.CollectionChanged),
OnNotifyCollectionChanged);
}
}
void OnNotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (ShouldNotifyListeners(args))
{
SendCurrentValue();
}
}
bool ShouldNotifyListeners(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
return _index >= e.NewStartingIndex;
case NotifyCollectionChangedAction.Remove:
return _index >= e.OldStartingIndex;
case NotifyCollectionChangedAction.Replace:
return _index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems.Count;
case NotifyCollectionChangedAction.Move:
return (_index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems.Count) ||
(_index >= e.OldStartingIndex &&
_index < e.OldStartingIndex + e.OldItems.Count);
case NotifyCollectionChangedAction.Reset:
return true;
}
return false;
}
}
}

91
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs

@ -3,17 +3,20 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
class PropertyInfoAccessorPlugin : IPropertyAccessorPlugin
{
private readonly INotifyingPropertyInfo _propertyInfo;
private readonly IPropertyInfo _propertyInfo;
private readonly Func<WeakReference, IPropertyInfo, IPropertyAccessor> _accessorFactory;
public PropertyInfoAccessorPlugin(INotifyingPropertyInfo propertyInfo)
public PropertyInfoAccessorPlugin(IPropertyInfo propertyInfo, Func<WeakReference, IPropertyInfo, IPropertyAccessor> accessorFactory)
{
_propertyInfo = propertyInfo;
_accessorFactory = accessorFactory;
}
public bool Match(object obj, string propertyName)
@ -24,89 +27,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public IPropertyAccessor Start(WeakReference reference, string propertyName)
{
Debug.Assert(_propertyInfo.Name == propertyName);
return new Accessor(reference, _propertyInfo);
}
class Accessor : PropertyAccessorBase
{
private WeakReference _reference;
private INotifyingPropertyInfo _propertyInfo;
private bool _eventRaised;
public Accessor(WeakReference reference, INotifyingPropertyInfo propertyInfo)
{
_reference = reference;
_propertyInfo = propertyInfo;
}
public override Type PropertyType => _propertyInfo.PropertyType;
public override object Value
{
get
{
var o = _reference.Target;
return (o != null) ? _propertyInfo.Get(o) : null;
}
}
public override bool SetValue(object value, BindingPriority priority)
{
if (_propertyInfo.CanSet)
{
_eventRaised = false;
_propertyInfo.Set(_reference.Target, value);
if (!_eventRaised)
{
SendCurrentValue();
}
return true;
}
return false;
}
void OnChanged(object sender, EventArgs e)
{
_eventRaised = true;
SendCurrentValue();
}
protected override void SubscribeCore()
{
SendCurrentValue();
SubscribeToChanges();
}
protected override void UnsubscribeCore()
{
var target = _reference.Target;
if (target != null)
{
_propertyInfo.RemoveListener(target, OnChanged);
}
}
private void SendCurrentValue()
{
try
{
var value = Value;
PublishValue(value);
}
catch { }
}
private void SubscribeToChanges()
{
var target = _reference.Target;
if (target != null)
{
_propertyInfo.OnPropertyChanged(target, OnChanged);
}
}
return _accessorFactory(reference, _propertyInfo);
}
}
}

6
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs

@ -117,10 +117,12 @@ namespace Avalonia.Markup.Xaml.XamlIl
var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N"));
var indexerClosureType = tb.DefineNestedType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder))),
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType))),
_sreContextType);

7
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs

@ -6,16 +6,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class AvaloniaXamlIlCompilerConfiguration : XamlIlTransformerConfiguration
{
public XamlIlClrPropertyInfoEmitter ClrPropertyEmitter { get; }
public XamlIlPropertyInfoAccessorFactoryEmitter AccessorFactoryEmitter { get; }
public AvaloniaXamlIlCompilerConfiguration(IXamlIlTypeSystem typeSystem,
IXamlIlAssembly defaultAssembly,
XamlIlLanguageTypeMappings typeMappings,
XamlIlXmlnsMappings xmlnsMappings,
XamlIlValueConverter customValueConverter,
XamlIlClrPropertyInfoEmitter clrPropertyEmitter) : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter)
XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter)
: base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter)
{
ClrPropertyEmitter = clrPropertyEmitter;
AccessorFactoryEmitter = accessorFactoryEmitter;
AddExtra(ClrPropertyEmitter);
AddExtra(AccessorFactoryEmitter);
}
}
}

6
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs

@ -72,12 +72,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
private readonly AvaloniaXamlIlWellKnownTypes _types;
public IXamlIlAstValueNode Name { get; set; }
public IXamlIlType ControlType { get; }
public IXamlIlType TargetType { get; }
public AvaloniaNameScopeRegistrationXamlIlNode(IXamlIlAstValueNode name, AvaloniaXamlIlWellKnownTypes types, IXamlIlType controlType) : base(name)
public AvaloniaNameScopeRegistrationXamlIlNode(IXamlIlAstValueNode name, AvaloniaXamlIlWellKnownTypes types, IXamlIlType targetType) : base(name)
{
_types = types;
ControlType = controlType;
TargetType = targetType;
Name = name;
}

17
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -8,6 +8,7 @@ using Avalonia.Utilities;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
@ -20,17 +21,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (node is XamlIlAstObjectNode on)
{
AvaloniaXamlIlDataContextTypeMetadataNode calculatedDataContextTypeNode = null;
foreach (var child in on.Children)
AvaloniaXamlIlDataContextTypeMetadataNode directiveDataContextTypeNode = null;
for (int i = 0; i < on.Children.Count; ++i)
{
var child = on.Children[i];
if (child is XamlIlAstXmlDirective directive)
{
if (directive.Namespace == XamlNamespaces.Xaml2006
&& directive.Name == "DataContextType"
&& directive.Values.Count == 1
&& directive.Values[0] is XamlIlTypeExtensionNode dataContextType)
&& directive.Values[0] is XamlIlAstTextNode text)
{
on.Children.Remove(child);
return new AvaloniaXamlIlDataContextTypeMetadataNode(on, dataContextType.Value.GetClrType());
i--;
directiveDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on,
XamlIlTypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type);
}
}
else if (child is XamlIlAstXamlPropertyValueNode pv
@ -62,10 +68,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
}
if (!(calculatedDataContextTypeNode is null))
{
return calculatedDataContextTypeNode;
}
return directiveDataContextTypeNode ?? calculatedDataContextTypeNode ?? node;
}
// TODO: Add node for DataTemplate scope.

6
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -30,7 +30,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlIlType ClrPropertyInfo { get; }
public IXamlIlType PropertyPath { get; }
public IXamlIlType PropertyPathBuilder { get; }
public IXamlIlType NotifyingPropertyInfoHelpers { get; }
public IXamlIlType IPropertyAccessor { get; }
public IXamlIlType PropertyInfoAccessorFactory { get; }
public IXamlIlType CompiledBindingPathBuilder { get; }
public IXamlIlType CompiledBindingPath { get; }
public IXamlIlType CompiledBindingExtension { get; }
@ -79,7 +80,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo");
PropertyPath = cfg.TypeSystem.GetType("Avalonia.Data.Core.PropertyPath");
PropertyPathBuilder = cfg.TypeSystem.GetType("Avalonia.Data.Core.PropertyPathBuilder");
NotifyingPropertyInfoHelpers = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.NotifyingPropertyInfoHelpers");
IPropertyAccessor = cfg.TypeSystem.GetType("Avalonia.Data.Core.Plugins.IPropertyAccessor");
PropertyInfoAccessorFactory = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.PropertyInfoAccessorFactory");
CompiledBindingPathBuilder = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPathBuilder");
CompiledBindingPath = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPath");
CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");

101
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs

@ -128,16 +128,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
break;
case BindingExpressionGrammar.IndexerNode indexer:
{
if (targetType.IsArray)
{
nodes.Add(new XamlIlArrayIndexerPathElementNode(targetType, indexer.Arguments, lineInfo));
break;
}
IXamlIlProperty property = null;
for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
{
var defaultMemberAttribute = currentType.CustomAttributes.FirstOrDefault(x => x.Type.GetFullName() == "System.Reflection.DefaultMemberAttribute");
var defaultMemberAttribute = currentType.CustomAttributes.FirstOrDefault(x => x.Type.Namespace == "System.Reflection" && x.Type.Name == "DefaultMemberAttribute");
if (defaultMemberAttribute != null)
{
property = targetType.GetAllProperties().FirstOrDefault(x => x.Name == (string)defaultMemberAttribute.Parameters[0]);
break;
}
};
if (property is null)
{
@ -150,7 +155,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
int currentParamIndex = 0;
foreach (var param in parameters)
{
var textNode = new XamlIlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex]);
var textNode = new XamlIlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
param, out var converted))
throw new XamlIlParseException(
@ -163,7 +168,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
bool isNotifyingCollection = targetType.GetAllInterfaces().Any(i => i.FullName == "System.Collections.Specialized.INotifyCollectionChanged");
nodes.Add(new XamlIlClrIndexerPathElementNode(property, values, isNotifyingCollection));
nodes.Add(new XamlIlClrIndexerPathElementNode(property, values, string.Join(",", indexer.Arguments), isNotifyingCollection));
break;
}
case BindingExpressionGrammar.AttachedPropertyNameNode attachedProp:
@ -183,7 +188,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
IXamlIlType elementType = null;
foreach (var deferredContent in context.ParentNodes().OfType<NestedScopeMetadataNode>())
{
elementType = ScopeRegistrationFinder.GetControlType(deferredContent, elementName.Name);
elementType = ScopeRegistrationFinder.GetTargetType(deferredContent, elementName.Name);
if (!(elementType is null))
{
break;
@ -191,7 +196,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
if (elementType is null)
{
elementType = ScopeRegistrationFinder.GetControlType(context.RootObject, elementName.Name);
elementType = ScopeRegistrationFinder.GetTargetType(context.RootObject, elementName.Name);
}
if (elementType is null)
@ -224,13 +229,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
string Name { get; }
IXamlIlType ControlType { get; set; }
IXamlIlType TargetType { get; set; }
public static IXamlIlType GetControlType(IXamlIlAstNode namescopeRoot, string name)
public static IXamlIlType GetTargetType(IXamlIlAstNode namescopeRoot, string name)
{
var finder = new ScopeRegistrationFinder(name);
namescopeRoot.Visit(finder);
return finder.ControlType;
return finder.TargetType;
}
void IXamlIlAstVisitor.Pop()
@ -257,7 +262,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
if (registration.Name is XamlIlAstTextNode text && text.Text == Name)
{
ControlType = registration.ControlType;
TargetType = registration.TargetType;
}
}
return node;
@ -387,12 +392,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
=> codeGen
.Ldsfld(_field)
.EmitCall(context.GetAvaloniaTypes()
.NotifyingPropertyInfoHelpers.FindMethod(m => m.Name == "CreateAvaloniaPropertyInfo"))
.EmitCall(context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
{
codeGen.Ldsfld(_field);
context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
.EmitLoadInpcPropertyAccessorFactory(context, codeGen);
codeGen.EmitCall(context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
}
public IXamlIlType Type { get; }
}
@ -411,9 +417,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
.Emit(context, codeGen, _property);
context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
.EmitLoadInpcPropertyAccessorFactory(context, codeGen);
codeGen
.EmitCall(context.GetAvaloniaTypes()
.NotifyingPropertyInfoHelpers.FindMethod(m => m.Name == "CreateINPCPropertyInfo"))
.EmitCall(context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
}
@ -425,12 +432,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
private readonly IXamlIlProperty _property;
private readonly List<IXamlIlAstValueNode> _values;
private readonly string _indexerKey;
private readonly bool _isNotifyingCollection;
public XamlIlClrIndexerPathElementNode(IXamlIlProperty property, List<IXamlIlAstValueNode> values, bool isNotifyingCollection)
public XamlIlClrIndexerPathElementNode(IXamlIlProperty property, List<IXamlIlAstValueNode> values, string indexerKey, bool isNotifyingCollection)
{
_property = property;
_values = values;
_indexerKey = indexerKey;
_isNotifyingCollection = isNotifyingCollection;
}
@ -438,21 +447,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
var intType = context.Configuration.TypeSystem.GetType("System.Int32");
context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
.Emit(context, codeGen, _property, _values);
.Emit(context, codeGen, _property, _values, _indexerKey);
if (_isNotifyingCollection
&&
_values.Count == 1
&& _values[0].Type.GetClrType().Equals(intType))
{
context.Emit(_values[0], codeGen, intType);
codeGen.EmitCall(context.GetAvaloniaTypes()
.NotifyingPropertyInfoHelpers.FindMethod(m => m.Name == "CreateIndexerPropertyInfo"));
context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
.EmitLoadIndexerAccessorFactory(context, codeGen, _values[0]);
}
else
{
codeGen.EmitCall(context.GetAvaloniaTypes()
.NotifyingPropertyInfoHelpers.FindMethod(m => m.Name == "CreateINPCPropertyInfo"));
context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
.EmitLoadInpcPropertyAccessorFactory(context, codeGen);
}
codeGen.EmitCall(context.GetAvaloniaTypes()
@ -462,6 +470,49 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public IXamlIlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
}
class XamlIlArrayIndexerPathElementNode : IXamlIlBindingPathElementNode
{
private readonly IXamlIlType _arrayType;
private readonly List<int> _values;
public XamlIlArrayIndexerPathElementNode(IXamlIlType arrayType, IList<string> values, IXamlIlLineInfo lineInfo)
{
_arrayType = arrayType;
_values = new List<int>(values.Count);
foreach (var item in values)
{
if (!int.TryParse(item, out var index))
{
throw new XamlIlParseException($"Unable to convert '{item}' to an integer.", lineInfo.Line, lineInfo.Position);
}
_values.Add(index);
}
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
var intType = context.Configuration.TypeSystem.GetType("System.Int32");
var indices = codeGen.DefineLocal(context.Configuration.WellKnownTypes.Object.MakeArrayType(1));
codeGen.Ldc_I4(_values.Count)
.Newarr(intType)
.Stloc(indices);
for (int i = 0; i < _values.Count; i++)
{
codeGen.Ldloc(indices)
.Ldc_I4(i)
.Ldc_I4(_values[i])
.Stelem_ref();
}
codeGen.Ldloc(indices)
.Ldtype(Type)
.EmitCall(context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "ArrayElement"));
}
public IXamlIlType Type => _arrayType.ArrayElementType;
}
class XamlIlBindingPathNode : XamlIlAstNode, IXamlIlBindingPathNode, IXamlIlAstEmitableNode
{
private readonly List<IXamlIlBindingPathElementNode> _transformElements;

17
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs

@ -20,16 +20,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
_builder = builder;
}
static string GetKey(IXamlIlProperty property)
=> property.Getter.DeclaringType.GetFullName() + "." + property.Name;
static string GetKey(IXamlIlProperty property, string indexerArgumentsKey)
{
var baseKey = property.Getter.DeclaringType.GetFullName() + "." + property.Name;
if (indexerArgumentsKey is null)
{
return baseKey;
}
return baseKey + $"[{indexerArgumentsKey}]";
}
public IXamlIlType Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen, IXamlIlProperty property, IEnumerable<IXamlIlAstValueNode> indexerArguments = null)
public IXamlIlType Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen, IXamlIlProperty property, IEnumerable<IXamlIlAstValueNode> indexerArguments = null, string indexerArgumentsKey = null)
{
indexerArguments = indexerArguments ?? Enumerable.Empty<IXamlIlAstValueNode>();
var types = context.GetAvaloniaTypes();
IXamlIlMethod Get()
{
var key = GetKey(property);
var key = GetKey(property, indexerArgumentsKey);
if (!_fields.TryGetValue(key, out var lst))
_fields[key] = lst = new List<(IXamlIlProperty prop, IXamlIlMethod get)>();

103
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class XamlIlPropertyInfoAccessorFactoryEmitter
{
private bool _indexerClosureTypeInitialized = false;
private readonly IXamlIlTypeBuilder _indexerClosureType;
public XamlIlPropertyInfoAccessorFactoryEmitter(IXamlIlTypeBuilder indexerClosureType)
{
_indexerClosureType = indexerClosureType;
}
public IXamlIlType EmitLoadInpcPropertyAccessorFactory(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
codeGen.Ldnull();
EmitLoadPropertyAccessorFactory(context, codeGen, context.GetAvaloniaTypes().PropertyInfoAccessorFactory, "CreateInpcPropertyAccessor");
return EmitCreateAccessorFactoryDelegate(context, codeGen);
}
public IXamlIlType EmitLoadAvaloniaPropertyAccessorFactory(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
codeGen.Ldnull();
EmitLoadPropertyAccessorFactory(context, codeGen, context.GetAvaloniaTypes().PropertyInfoAccessorFactory, "CreateAvaloniaPropertyAccessor");
return EmitCreateAccessorFactoryDelegate(context, codeGen);
}
private void EmitLoadPropertyAccessorFactory(XamlIlEmitContext context, IXamlIlEmitter codeGen, IXamlIlType type, string accessorFactoryName)
{
var types = context.GetAvaloniaTypes();
var weakReferenceType = context.Configuration.TypeSystem.GetType("System.WeakReference");
FindMethodMethodSignature accessorFactorySignature = new FindMethodMethodSignature(accessorFactoryName, types.IPropertyAccessor, weakReferenceType, types.IPropertyInfo)
{
IsStatic = true
};
codeGen.Ldftn(type.GetMethod(accessorFactorySignature));
}
public IXamlIlType EmitLoadIndexerAccessorFactory(XamlIlEmitContext context, IXamlIlEmitter codeGen, IXamlIlAstValueNode value)
{
const string indexerClosureFactoryMethodName = "CreateAccessor";
var types = context.GetAvaloniaTypes();
var intType = context.Configuration.TypeSystem.GetType("System.Int32");
var weakReferenceType = context.Configuration.TypeSystem.GetType("System.WeakReference");
if (!_indexerClosureTypeInitialized)
{
var indexAccessorFactoryMethod = context.GetAvaloniaTypes().PropertyInfoAccessorFactory.GetMethod(
new FindMethodMethodSignature(
"CreateIndexerPropertyAccessor",
types.IPropertyAccessor,
weakReferenceType,
types.IPropertyInfo,
intType)
{
IsStatic = true
});
var indexField = _indexerClosureType.DefineField(intType, "_index", false, false);
var ctor = _indexerClosureType.DefineConstructor(false, intType);
ctor.Generator
.Ldarg_0()
.Stfld(indexField);
_indexerClosureType.DefineMethod(
types.IPropertyAccessor,
new[] { weakReferenceType, types.IPropertyInfo },
indexerClosureFactoryMethodName,
isPublic: false,
isStatic: false,
isInterfaceImpl: false)
.Generator
.Ldarg_0()
.Ldarg(1)
.Ldfld(indexField)
.EmitCall(indexAccessorFactoryMethod);
}
context.Emit(value, codeGen, intType);
codeGen.Newobj(_indexerClosureType.FindConstructor(new List<IXamlIlType> { intType }));
EmitLoadPropertyAccessorFactory(context, codeGen, _indexerClosureType, indexerClosureFactoryMethodName);
return EmitCreateAccessorFactoryDelegate(context, codeGen);
}
private IXamlIlType EmitCreateAccessorFactoryDelegate(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
var types = context.GetAvaloniaTypes();
var weakReferenceType = context.Configuration.TypeSystem.GetType("System.WeakReference");
var funcType = context.Configuration.TypeSystem.GetType("System.Func`3").MakeGenericType(
weakReferenceType,
types.IPropertyInfo,
types.IPropertyAccessor);
codeGen.Newobj(funcType.Constructors.First(c =>
c.Parameters.Count == 2 &&
c.Parameters[0].Equals(context.Configuration.WellKnownTypes.Object)));
return funcType;
}
}
}

2
src/Markup/Avalonia.Markup/Data/Binding.cs

@ -71,7 +71,7 @@ namespace Avalonia.Data
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
INameScope nameScope;
INameScope nameScope = null;
NameScope?.TryGetTarget(out nameScope);
var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope);

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

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Data.Core;
using Avalonia.Markup.Data;
using Avalonia.UnitTests;
using Xunit;
@ -21,7 +23,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataContextType='{x:Type local:TestDataContext}'>
x:DataContextType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
</Window>";
var loader = new AvaloniaXamlLoader();
@ -50,7 +52,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataContextType='{x:Type local:TestDataContext}'>
x:DataContextType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding TaskProperty^}' Name='textBlock' />
</Window>";
var loader = new AvaloniaXamlLoader();
@ -79,7 +81,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataContextType='{x:Type local:TestDataContext}'>
x:DataContextType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding ObservableProperty^}' Name='textBlock' />
</Window>";
var loader = new AvaloniaXamlLoader();
@ -101,6 +103,121 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal("foobar", textBlock.Text);
}
}
[Fact]
public void ResolvesIndexerBindingCorrectly()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataContextType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding ListProperty[3]}' Name='textBlock' />
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var dataContext = new TestDataContext
{
ListProperty = { "A", "B", "C", "D", "E" }
};
window.DataContext = dataContext;
Assert.Equal(dataContext.ListProperty[3], textBlock.Text);
}
}
[Fact]
public void ResolvesArrayIndexerBindingCorrectly()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataContextType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding ArrayProperty[3]}' Name='textBlock' />
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var dataContext = new TestDataContext
{
ArrayProperty = new[] { "A", "B", "C", "D", "E" }
};
window.DataContext = dataContext;
Assert.Equal(dataContext.ArrayProperty[3], textBlock.Text);
}
}
[Fact]
public void ResolvesObservableIndexerBindingCorrectly()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataContextType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding ObservableCollectionProperty[3]}' Name='textBlock' />
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var dataContext = new TestDataContext
{
ObservableCollectionProperty = { "A", "B", "C", "D", "E" }
};
window.DataContext = dataContext;
Assert.Equal(dataContext.ObservableCollectionProperty[3], textBlock.Text);
dataContext.ObservableCollectionProperty[3] = "New Value";
Assert.Equal(dataContext.ObservableCollectionProperty[3], textBlock.Text);
}
}
[Fact]
public void ResolvesNonIntegerIndexerBindingCorrectly()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataContextType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding NonIntegerIndexerProperty[Test]}' Name='textBlock' />
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var dataContext = new TestDataContext();
dataContext.NonIntegerIndexerProperty["Test"] = "Initial Value";
window.DataContext = dataContext;
Assert.Equal(dataContext.NonIntegerIndexerProperty["Test"], textBlock.Text);
dataContext.NonIntegerIndexerProperty["Test"] = "New Value";
Assert.Equal(dataContext.NonIntegerIndexerProperty["Test"], textBlock.Text);
}
}
}
public class TestDataContext
@ -110,5 +227,31 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public Task<string> TaskProperty { get; set; }
public IObservable<string> ObservableProperty { get; set; }
public ObservableCollection<string> ObservableCollectionProperty { get; set; } = new ObservableCollection<string>();
public string[] ArrayProperty { get; set; }
public List<string> ListProperty { get; set; } = new List<string>();
public NonIntegerIndexer NonIntegerIndexerProperty { get; set; } = new NonIntegerIndexer();
public class NonIntegerIndexer : NotifyingBase
{
private readonly Dictionary<string, string> _storage = new Dictionary<string, string>();
public string this[string key]
{
get
{
return _storage[key];
}
set
{
_storage[key] = value;
RaisePropertyChanged(CommonPropertyNames.IndexerName);
}
}
}
}
}

Loading…
Cancel
Save