diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index cab4bf5580..9dcc016cbd 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -1,4 +1,4 @@ - + @@ -7,12 +7,36 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPath + baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + + + CP0001 + T:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPathBuilder + baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + CP0001 T:Avalonia.Media.IGlyphTypeface baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPath + baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + + + CP0001 + T:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPathBuilder + baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + CP0002 M:Avalonia.Media.FontManager.TryGetGlyphTypeface(Avalonia.Media.Typeface,Avalonia.Media.IGlyphTypeface@) @@ -289,6 +313,24 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension.#ctor(Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPath) + baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + + + CP0002 + M:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension.get_Path + baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + + + CP0002 + M:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension.ProvideValue(System.IServiceProvider) + baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + CP0002 M:Avalonia.Media.GlyphMetrics.get_Height @@ -655,6 +697,24 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension.#ctor(Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPath) + baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + + + CP0002 + M:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension.get_Path + baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + + + CP0002 + M:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension.ProvideValue(System.IServiceProvider) + baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + CP0002 M:Avalonia.Media.GlyphMetrics.get_Height @@ -1309,4 +1369,4 @@ baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll - \ No newline at end of file + diff --git a/src/Avalonia.Base/Data/CompiledBinding.cs b/src/Avalonia.Base/Data/CompiledBinding.cs new file mode 100644 index 0000000000..764b04957e --- /dev/null +++ b/src/Avalonia.Base/Data/CompiledBinding.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using Avalonia.Controls; +using Avalonia.Data.Converters; +using Avalonia.Data.Core; +using Avalonia.Data.Core.ExpressionNodes; +using Avalonia.Data.Core.Parsers; + +namespace Avalonia.Data; + +/// +/// A binding which does not use reflection to access members. +/// +public class CompiledBinding : BindingBase +{ + /// + /// Initializes a new instance of the class. + /// + public CompiledBinding() { } + + /// + /// Initializes a new instance of the class. + /// + /// The binding path. + public CompiledBinding(CompiledBindingPath path) => Path = path; + + /// + /// Gets or sets the amount of time, in milliseconds, to wait before updating the binding + /// source after the value on the target changes. + /// + /// + /// There is no delay when the source is updated via + /// or . Nor is there a delay when + /// is active and a new source object is provided. + /// + public int Delay { get; set; } + + /// + /// Gets or sets the to use. + /// + public IValueConverter? Converter { get; set; } + + /// + /// Gets or sets the culture in which to evaluate the converter. + /// + /// The default value is null. + /// + /// If this property is not set then will be used. + /// + [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] + public CultureInfo? ConverterCulture { get; set; } + + /// + /// Gets or sets a parameter to pass to . + /// + public object? ConverterParameter { get; set; } + + /// + /// Gets or sets the value to use when the binding is unable to produce a value. + /// + public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue; + + /// + /// Gets or sets the binding mode. + /// + public BindingMode Mode { get; set; } + + /// + /// Gets or sets the binding path. + /// + public CompiledBindingPath? Path { get; set; } + + /// + /// Gets or sets the binding priority. + /// + public BindingPriority Priority { get; set; } + + /// + /// Gets or sets the source for the binding. + /// + public object? Source { get; set; } = AvaloniaProperty.UnsetValue; + + /// + /// Gets or sets the string format. + /// + public string? StringFormat { get; set; } + + /// + /// Gets or sets the value to use when the binding result is null. + /// + public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue; + + /// + /// Gets or sets a value that determines the timing of binding source updates for + /// and bindings. + /// + public UpdateSourceTrigger UpdateSourceTrigger { get; set; } + + internal WeakReference? DefaultAnchor { get; set; } + internal WeakReference? NameScope { get; set; } + + internal override BindingExpressionBase CreateInstance( + AvaloniaObject target, + AvaloniaProperty? targetProperty, + object? anchor) + { + var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false; + var nodes = new List(); + var isRooted = false; + + Path?.BuildExpression(nodes, out isRooted); + + // If the binding isn't rooted (i.e. doesn't have a Source or start with $parent, $self, + // #elementName etc.) then we need to add a data context source node. + if (Source == AvaloniaProperty.UnsetValue && !isRooted) + nodes.Insert(0, ExpressionNodeFactory.CreateDataContext(targetProperty)); + + // If the first node is an ISourceNode then allow it to select the source; otherwise + // use the binding source if specified, falling back to the target. + var source = nodes?.Count > 0 && nodes[0] is SourceNode sn + ? sn.SelectSource(Source, target, anchor ?? DefaultAnchor?.Target) + : Source != AvaloniaProperty.UnsetValue ? Source : target; + + var (mode, trigger) = ResolveDefaultsFromMetadata(target, targetProperty); + + return new BindingExpression( + source, + nodes, + FallbackValue, + delay: TimeSpan.FromMilliseconds(Delay), + converter: Converter, + converterCulture: ConverterCulture, + converterParameter: ConverterParameter, + enableDataValidation: enableDataValidation, + mode: mode, + priority: Priority, + stringFormat: StringFormat, + targetNullValue: TargetNullValue, + targetProperty: targetProperty, + targetTypeConverter: TargetTypeConverter.GetDefaultConverter(), + updateSourceTrigger: trigger); + } + + private (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata( + AvaloniaObject target, + AvaloniaProperty? targetProperty) + { + var mode = Mode; + var trigger = UpdateSourceTrigger == UpdateSourceTrigger.Default ? + UpdateSourceTrigger.PropertyChanged : UpdateSourceTrigger; + + if (mode == BindingMode.Default) + { + if (targetProperty?.GetMetadata(target) is { } metadata) + mode = metadata.DefaultBindingMode; + else + mode = BindingMode.OneWay; + } + + return (mode, trigger); + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Avalonia.Base/Data/CompiledBindingPath.cs similarity index 92% rename from src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs rename to src/Avalonia.Base/Data/CompiledBindingPath.cs index 20e9c6e886..aea320702e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Avalonia.Base/Data/CompiledBindingPath.cs @@ -4,10 +4,10 @@ using System.Reflection; using Avalonia.Controls; using Avalonia.Data.Core; using Avalonia.Data.Core.ExpressionNodes; +using Avalonia.Data.Core.Parsers; using Avalonia.Data.Core.Plugins; -using Avalonia.Markup.Parsers; -namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings +namespace Avalonia.Data { public class CompiledBindingPath { @@ -96,21 +96,17 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings /// public override string ToString() - => string.Concat((IEnumerable) _elements); + => string.Concat((IEnumerable)_elements); } public class CompiledBindingPathBuilder { - private readonly int _apiVersion; private readonly List _elements = new(); public CompiledBindingPathBuilder() { } - // TODO12: Remove this constructor. apiVersion is only needed for compatibility with - // versions of Avalonia which used $self.Property() for building TemplatedParent bindings. - public CompiledBindingPathBuilder(int apiVersion) => _apiVersion = apiVersion; public CompiledBindingPathBuilder Not() { @@ -120,22 +116,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public CompiledBindingPathBuilder Property(IPropertyInfo info, Func, IPropertyInfo, IPropertyAccessor> accessorFactory) { - // Older versions of Avalonia used $self.Property() for building TemplatedParent bindings. - // Try to detect this and upgrade to using a TemplatedParentPathElement so that logging works - // correctly. - if (_apiVersion == 0 && - info.Name == "TemplatedParent" && - _elements.Count >= 1 && - _elements[_elements.Count - 1] is SelfPathElement) - { - _elements.Add(new TemplatedParentPathElement()); - } - else - { - return Property(info, accessorFactory, acceptsNull: false); - } - - return this; + return Property(info, accessorFactory, acceptsNull: false); } public CompiledBindingPathBuilder Property( @@ -285,7 +266,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public MethodInfo Method { get; } public Type DelegateType { get; } - + public bool AcceptsNull { get; } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs index 88d708335e..a11dea95e4 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs @@ -142,7 +142,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers IXamlType? itemsCollectionType = null; if (context.GetAvaloniaTypes().BindingBase.IsAssignableFrom(parentItemsValue.Type.GetClrType())) { - if (parentItemsValue.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension) + if (parentItemsValue.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBinding) && parentItemsValue is XamlMarkupExtensionNode ext && ext.Value is XamlAstConstructableObjectNode parentItemsBinding) { var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType().FirstOrDefault(); @@ -176,7 +176,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode obj) { var bindingType = context.GetAvaloniaTypes().BindingBase; - if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension)) + if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && + !(obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension) || + obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))) { return new AvaloniaXamlIlDataContextTypeMetadataNode(on, obj.Type.GetClrType()); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 2b7a835fd8..8659eb1299 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -51,6 +51,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ClrPropertyInfo { get; } public IXamlType IPropertyAccessor { get; } public IXamlType PropertyInfoAccessorFactory { get; } + public IXamlType CompiledBinding { get; } public IXamlType CompiledBindingPathBuilder { get; } public IXamlType CompiledBindingPath { get; } public IXamlType CompiledBindingExtension { get; } @@ -242,8 +243,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo"); 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"); + CompiledBinding = cfg.TypeSystem.GetType("Avalonia.Data.CompiledBinding"); + CompiledBindingPathBuilder = cfg.TypeSystem.GetType("Avalonia.Data.CompiledBindingPathBuilder"); + CompiledBindingPath = cfg.TypeSystem.GetType("Avalonia.Data.CompiledBindingPath"); CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension"); ResolveByNameExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ResolveByNameExtension"); DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate"); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index 68dd595ea2..96af9a3a18 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -1013,11 +1013,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions var intType = context.Configuration.TypeSystem.GetType("System.Int32"); var types = context.GetAvaloniaTypes(); - // We're calling the CompiledBindingPathBuilder(int apiVersion) with an apiVersion - // of 1 to indicate that we don't want TemplatedParent compatibility hacks enabled. - codeGen - .Ldc_I4(1) - .Newobj(types.CompiledBindingPathBuilder.GetConstructor(new() { intType })); + codeGen.Newobj(types.CompiledBindingPathBuilder.GetConstructor()); foreach (var transform in _transformElements) { diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 7a52f94a9f..e6186bbea6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -20,7 +20,6 @@ - diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index 0913903ff3..22606bdd93 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -7,15 +7,13 @@ using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.Parsers; -using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; namespace Avalonia.Markup.Xaml.MarkupExtensions { - public sealed class CompiledBindingExtension : BindingBase + public sealed class CompiledBindingExtension : CompiledBinding { public CompiledBindingExtension() { - Path = new CompiledBindingPath(); } public CompiledBindingExtension(CompiledBindingPath path) @@ -23,9 +21,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions Path = path; } - public CompiledBindingExtension ProvideValue(IServiceProvider provider) + public CompiledBinding ProvideValue(IServiceProvider? provider) { - return new CompiledBindingExtension + return new CompiledBinding { Path = Path, Delay = Delay, @@ -38,184 +36,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions Priority = Priority, StringFormat = StringFormat, Source = Source, - DefaultAnchor = new WeakReference(provider.GetDefaultAnchor()), + DefaultAnchor = new WeakReference(provider?.GetDefaultAnchor()), UpdateSourceTrigger = UpdateSourceTrigger, }; } - /// - /// Gets or sets the amount of time, in milliseconds, to wait before updating the binding - /// source after the value on the target changes. - /// - /// - /// There is no delay when the source is updated via - /// or . Nor is there a delay when - /// is active and a new source object is provided. - /// - public int Delay { get; set; } - - /// - /// Gets or sets the to use. - /// - public IValueConverter? Converter { get; set; } - - /// - /// Gets or sets the culture in which to evaluate the converter. - /// - /// The default value is null. - /// - /// If this property is not set then will be used. - /// - [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] - public CultureInfo? ConverterCulture { get; set; } - - /// - /// Gets or sets a parameter to pass to . - /// - public object? ConverterParameter { get; set; } - public Type? DataType { get; set; } - - /// - /// Gets or sets the value to use when the binding is unable to produce a value. - /// - public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue; - - /// - /// Gets or sets the binding mode. - /// - public BindingMode Mode { get; set; } - - [ConstructorArgument("path")] - public CompiledBindingPath Path { get; set; } - - /// - /// Gets or sets the binding priority. - /// - public BindingPriority Priority { get; set; } - - /// - /// Gets or sets the source for the binding. - /// - public object? Source { get; set; } = AvaloniaProperty.UnsetValue; - - /// - /// Gets or sets the string format. - /// - public string? StringFormat { get; set; } - - /// - /// Gets or sets the value to use when the binding result is null. - /// - public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue; - - /// - /// Gets or sets a value that determines the timing of binding source updates for - /// and bindings. - /// - public UpdateSourceTrigger UpdateSourceTrigger { get; set; } - - internal WeakReference? DefaultAnchor { get; set; } - - internal override BindingExpressionBase CreateInstance( - AvaloniaObject target, - AvaloniaProperty? targetProperty, - object? anchor) - { - var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false; - return InstanceCore(target, targetProperty, anchor, enableDataValidation); - } - - /// - /// Hack for TreeDataTemplate to create a binding expression for an item. - /// - /// The item. - /// - /// Ideally we'd do this in a more generic way but didn't have time to refactor - /// ITreeDataTemplate in time for 11.0. We should revisit this in 12.0. - /// - // TODO12: Refactor - internal BindingExpression CreateObservableForTreeDataTemplate(object source) - { - if (Source != AvaloniaProperty.UnsetValue) - throw new NotSupportedException("Source bindings are not supported in this context."); - - var nodes = new List(); - - Path.BuildExpression(nodes, out var isRooted); - - if (isRooted) - throw new NotSupportedException("Rooted binding paths are not supported in this context."); - - return new BindingExpression( - source, - nodes, - FallbackValue, - delay: TimeSpan.FromMilliseconds(Delay), - converter: Converter, - converterParameter: ConverterParameter, - targetNullValue: TargetNullValue); - } - - private BindingExpression InstanceCore( - AvaloniaObject target, - AvaloniaProperty? targetProperty, - object? anchor, - bool enableDataValidation) - { - var nodes = new List(); - - // Build the expression nodes from the binding path. - Path.BuildExpression(nodes, out var isRooted); - - // If the binding isn't rooted (i.e. doesn't have a Source or start with $parent, $self, - // #elementName etc.) then we need to add a data context source node. - if (Source == AvaloniaProperty.UnsetValue && !isRooted) - nodes.Insert(0, ExpressionNodeFactory.CreateDataContext(targetProperty)); - - // If the first node is an ISourceNode then allow it to select the source; otherwise - // use the binding source if specified, falling back to the target. - var source = nodes.Count > 0 && nodes[0] is SourceNode sn - ? sn.SelectSource(Source, target, anchor ?? DefaultAnchor?.Target) - : Source != AvaloniaProperty.UnsetValue ? Source : target; - - var (mode, trigger) = ResolveDefaultsFromMetadata(target, targetProperty); - - return new BindingExpression( - source, - nodes, - FallbackValue, - delay: TimeSpan.FromMilliseconds(Delay), - converter: Converter, - converterCulture: ConverterCulture, - converterParameter: ConverterParameter, - enableDataValidation: enableDataValidation, - mode: mode, - priority: Priority, - stringFormat: StringFormat, - targetNullValue: TargetNullValue, - targetProperty: targetProperty, - targetTypeConverter: TargetTypeConverter.GetDefaultConverter(), - updateSourceTrigger: trigger); - } - - private (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata( - AvaloniaObject target, - AvaloniaProperty? targetProperty) - { - var mode = Mode; - var trigger = UpdateSourceTrigger == UpdateSourceTrigger.Default ? - UpdateSourceTrigger.PropertyChanged : UpdateSourceTrigger; - - if (mode == BindingMode.Default) - { - if (targetProperty?.GetMetadata(target) is { } metadata) - mode = metadata.DefaultBindingMode; - else - mode = BindingMode.OneWay; - } - - return (mode, trigger); - } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs index da4d7374d4..513b18c7a7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs @@ -33,7 +33,7 @@ namespace Avalonia.Markup.Xaml Type Resolve (string qualifiedTypeName); } - + // TODO12: Move to Avalonia.Base [AttributeUsage(AttributeTargets.Property)] public sealed class ConstructorArgumentAttribute : Attribute { diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/CompiledBindingPathFromExpressionBuilder.cs b/tests/Avalonia.Base.UnitTests/Data/Core/CompiledBindingPathFromExpressionBuilder.cs index 0662fd94b0..d3623fde80 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/CompiledBindingPathFromExpressionBuilder.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/CompiledBindingPathFromExpressionBuilder.cs @@ -1,10 +1,9 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; +using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 051129e12c..727b3ddc45 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -639,8 +639,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions target.ApplyTemplate(); // Assert DataGridLikeColumn.Binding data type. - var compiledPath = ((CompiledBindingExtension)column.Binding!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var compiledPath = ((CompiledBinding)column.Binding!).Path; + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(string), node.Property.PropertyType); Assert.Equal(nameof(TestData.StringProperty), node.Property.Name); @@ -682,8 +682,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions target.ApplyTemplate(); // Assert DataGridLikeColumn.Binding data type. - var compiledPath = ((CompiledBindingExtension)column.Binding!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var compiledPath = ((CompiledBinding)column.Binding!).Path; + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(int), node.Property.PropertyType); // Assert DataGridLikeColumn.Template data type by evaluating the template. @@ -727,8 +727,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions target.ApplyTemplate(); // Assert DataGridLikeColumn.Binding data type. - var compiledPath = ((CompiledBindingExtension)column.Binding!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var compiledPath = ((CompiledBinding)column.Binding!).Path; + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(int), node.Property.PropertyType); // Assert DataGridLikeColumn.Template data type by evaluating the template. @@ -2060,9 +2060,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions x:DataType='local:TestDataContext' X='{CompiledBinding StringProperty}' />"; var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml); - var compiledPath = ((CompiledBindingExtension)control.X!).Path; + var compiledPath = ((CompiledBinding)control.X!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(string), node.Property.PropertyType); } } @@ -2078,9 +2078,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' X='{CompiledBinding StringProperty, DataType=local:TestDataContext}' />"; var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml); - var compiledPath = ((CompiledBindingExtension)control.X!).Path; + var compiledPath = ((CompiledBinding)control.X!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(string), node.Property.PropertyType); } } @@ -2097,9 +2097,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions X='{CompiledBinding StringProperty, DataType=local:TestDataContext}' />"; var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration { UseCompiledBindingsByDefault = true }); - var compiledPath = ((CompiledBindingExtension)control.X!).Path; + var compiledPath = ((CompiledBinding)control.X!).Path; - var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + var node = Assert.IsType(Assert.Single(compiledPath!.Elements)); Assert.Equal(typeof(string), node.Property.PropertyType); } }