diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index 3371e510e3..c98650fa8e 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -48,6 +48,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri /// The binding mode. /// The binding priority. /// The format string to use. + /// The target property being bound to. /// The null target value. /// /// A final type converter to be run on the produced value. @@ -65,9 +66,10 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri BindingPriority priority = BindingPriority.LocalValue, string? stringFormat = null, object? targetNullValue = null, + AvaloniaProperty? targetProperty = null, TargetTypeConverter? targetTypeConverter = null, UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged) - : base(priority, enableDataValidation) + : base(priority, targetProperty, enableDataValidation) { if (mode == BindingMode.Default) throw new ArgumentException("Binding mode cannot be Default.", nameof(mode)); diff --git a/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs b/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs index 9b6e260f95..1221bee841 100644 --- a/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs +++ b/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs @@ -39,12 +39,16 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, /// /// The default binding priority for the expression. /// + /// The target property being bound to. /// Whether data validation is enabled. public UntypedBindingExpressionBase( BindingPriority defaultPriority, + AvaloniaProperty? targetProperty = null, bool isDataValidationEnabled = false) { Priority = defaultPriority; + TargetProperty = targetProperty; + TargetType = targetProperty?.PropertyType ?? typeof(object); _isDataValidationEnabled = isDataValidationEnabled; } @@ -86,7 +90,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, /// Gets the target type of the binding expression; that is, the type that values produced by /// the expression should be converted to. /// - public Type TargetType { get; private set; } = typeof(object); + public Type TargetType { get; private set; } AvaloniaProperty IValueEntry.Property => TargetProperty ?? throw new Exception(); @@ -262,6 +266,8 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, { if (_sink is not null) throw new InvalidOperationException("BindingExpression was already attached."); + if (TargetProperty is not null && TargetProperty != targetProperty) + throw new InvalidOperationException("BindingExpression was already attached to a different property."); _sink = sink; _frame = frame; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index 357c949d11..d88ba7bda7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -133,6 +133,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions priority: Priority, stringFormat: StringFormat, targetNullValue: TargetNullValue, + targetProperty: targetProperty, targetTypeConverter: TargetTypeConverter.GetDefaultConverter(), updateSourceTrigger: trigger); } diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index e089da6491..d654c7a74a 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -175,6 +175,7 @@ namespace Avalonia.Data mode: mode, priority: Priority, stringFormat: StringFormat, + targetProperty: targetProperty, targetNullValue: TargetNullValue, targetTypeConverter: TargetTypeConverter.GetReflectionConverter(), updateSourceTrigger: trigger); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Obsolete.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Obsolete.cs new file mode 100644 index 0000000000..2c426cbf09 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Obsolete.cs @@ -0,0 +1,51 @@ +using System.Reactive.Linq; +using Avalonia.Data; +using Avalonia.Markup.Xaml.MarkupExtensions; +using Xunit; + +#nullable enable +#pragma warning disable CS0618 // Type or member is obsolete + +namespace Avalonia.Base.UnitTests.Data.Core; + +public abstract partial class BindingExpressionTests +{ + public partial class Reflection + { + [Fact] + public void Obsolete_Initiate_Method_Produces_Observable_With_Correct_Target_Type() + { + // Issue #15081 + var viewModel = new ViewModel { DoubleValue = 42.5 }; + var target = new TargetClass { DataContext = viewModel }; + var binding = new Binding(nameof(viewModel.DoubleValue)); + var instanced = binding.Initiate(target, TargetClass.StringProperty); + + Assert.NotNull(instanced); + + var value = instanced.Observable.First(); + + Assert.Equal("42.5", value); + } + } + + public partial class Compiled + { + [Fact] + public void Obsolete_Initiate_Method_Produces_Observable_With_Correct_Target_Type() + { + // Issue #15081 + var viewModel = new ViewModel { DoubleValue = 42.5 }; + var target = new TargetClass { DataContext = viewModel }; + var path = CompiledBindingPathFromExpressionBuilder.Build(x => x.DoubleValue, true); + var binding = new CompiledBindingExtension(path); + var instanced = binding.Initiate(target, TargetClass.StringProperty); + + Assert.NotNull(instanced); + + var value = instanced.Observable.First(); + + Assert.Equal("42.5", value); + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs index 759337b430..805655d6ab 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs @@ -19,7 +19,7 @@ namespace Avalonia.Base.UnitTests.Data.Core; [InvariantCulture] public abstract partial class BindingExpressionTests { - public class Reflection : BindingExpressionTests + public partial class Reflection : BindingExpressionTests { private protected override (TargetClass, BindingExpression) CreateTargetCore( Expression> expression, @@ -73,7 +73,7 @@ public abstract partial class BindingExpressionTests } } - public class Compiled : BindingExpressionTests + public partial class Compiled : BindingExpressionTests { private protected override (TargetClass, BindingExpression) CreateTargetCore( Expression> expression,