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,