diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index ac52c148be..74eaee5e07 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/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", diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 3cc5ce74cb..b2c4c33ed5 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -17,9 +17,10 @@ + - + @@ -69,6 +70,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs new file mode 100644 index 0000000000..718c695c45 --- /dev/null +++ b/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() + { + } + } + } + +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index ce3154f349..b45fde7798 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/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 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 accessorFactory) { Property = property; + AccessorFactory = accessorFactory; } - public INotifyingPropertyInfo Property { get; } + public IPropertyInfo Property { get; } + + public Func 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; } + } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/NotifyingPropertyInfoHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/NotifyingPropertyInfoHelpers.cs deleted file mode 100644 index 0af201ade8..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/NotifyingPropertyInfoHelpers.cs +++ /dev/null @@ -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 _changedHandlers = new ConditionalWeakTable(); - - 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( - inpc, - nameof(INotifyPropertyChanged.PropertyChanged), - OnNotifyPropertyChanged); - } - } - - protected override void UnsubscribeToChangesForTarget(object target) - { - if (target is INotifyPropertyChanged) - { - WeakEventHandlerManager.Unsubscribe( - 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( - incc, - nameof(INotifyCollectionChanged.CollectionChanged), - OnNotifyCollectionChanged); - } - } - - protected override void UnsubscribeToChangesForTarget(object target) - { - base.UnsubscribeToChangesForTarget(target); - if (target is INotifyCollectionChanged) - { - WeakEventHandlerManager.Unsubscribe( - 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; - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs new file mode 100644 index 0000000000..a4534a7396 --- /dev/null +++ b/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)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 _reference; + private readonly AvaloniaProperty _property; + private IDisposable _subscription; + + public AvaloniaPropertyAccessor(WeakReference reference, AvaloniaProperty property) + { + Contract.Requires(reference != null); + Contract.Requires(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(reference != null); + Contract.Requires(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( + 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( + 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( + incc, + nameof(INotifyCollectionChanged.CollectionChanged), + OnNotifyCollectionChanged); + } + } + + protected override void UnsubscribeCore() + { + base.UnsubscribeCore(); + if (_reference.Target is INotifyCollectionChanged incc) + { + WeakEventHandlerManager.Unsubscribe( + 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; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs index ac3108e847..368a68f06b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs +++ b/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 _accessorFactory; - public PropertyInfoAccessorPlugin(INotifyingPropertyInfo propertyInfo) + public PropertyInfoAccessorPlugin(IPropertyInfo propertyInfo, Func 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); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs index 6cf69da8b2..7d354f2b81 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs +++ b/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); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs index 26965ffea4..6235ef5d86 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs +++ b/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); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs index d3e7c8034a..df88584e54 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs +++ b/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; } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs index 7e13b8a18a..04bb3fe96f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/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. diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 016130dc17..d29e99f2c3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/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"); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs index 287818e412..b2efa603ec 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/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()) { - 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() + .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() .Emit(context, codeGen, _property); + context.Configuration.GetExtra() + .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 _values; + private readonly string _indexerKey; private readonly bool _isNotifyingCollection; - public XamlIlClrIndexerPathElementNode(IXamlIlProperty property, List values, bool isNotifyingCollection) + public XamlIlClrIndexerPathElementNode(IXamlIlProperty property, List 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() - .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() + .EmitLoadIndexerAccessorFactory(context, codeGen, _values[0]); } else { - codeGen.EmitCall(context.GetAvaloniaTypes() - .NotifyingPropertyInfoHelpers.FindMethod(m => m.Name == "CreateINPCPropertyInfo")); + context.Configuration.GetExtra() + .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 _values; + + public XamlIlArrayIndexerPathElementNode(IXamlIlType arrayType, IList values, IXamlIlLineInfo lineInfo) + { + _arrayType = arrayType; + _values = new List(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 _transformElements; diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs index 43d5a4eba1..d2608df9e9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs +++ b/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 indexerArguments = null) + public IXamlIlType Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen, IXamlIlProperty property, IEnumerable indexerArguments = null, string indexerArgumentsKey = null) { indexerArguments = indexerArguments ?? Enumerable.Empty(); 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)>(); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs new file mode 100644 index 0000000000..257a11914e --- /dev/null +++ b/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 { 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; + } + } +} diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 50288d758e..2882114936 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/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); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 34bb071aef..996b2b7ee9 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/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 + x:DataContextType='local:TestDataContext'> "; var loader = new AvaloniaXamlLoader(); @@ -50,7 +52,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions + x:DataContextType='local:TestDataContext'> "; var loader = new AvaloniaXamlLoader(); @@ -79,7 +81,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions + x:DataContextType='local:TestDataContext'> "; 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 = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("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 = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("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 = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("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 = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("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 TaskProperty { get; set; } public IObservable ObservableProperty { get; set; } + + public ObservableCollection ObservableCollectionProperty { get; set; } = new ObservableCollection(); + + public string[] ArrayProperty { get; set; } + + public List ListProperty { get; set; } = new List(); + + public NonIntegerIndexer NonIntegerIndexerProperty { get; set; } = new NonIntegerIndexer(); + + public class NonIntegerIndexer : NotifyingBase + { + private readonly Dictionary _storage = new Dictionary(); + + public string this[string key] + { + get + { + return _storage[key]; + } + set + { + _storage[key] = value; + RaisePropertyChanged(CommonPropertyNames.IndexerName); + } + } + } } }