diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 93bbee12b8..5a5827d0aa 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -355,6 +355,40 @@ namespace Avalonia
SetDirectValueUnchecked(property, value);
}
+ ///
+ /// Sets the value of a dependency property without changing its value source.
+ ///
+ /// The property.
+ /// The value.
+ ///
+ /// This method is used by a component that programmatically sets the value of one of its
+ /// own properties without disabling an application's declared use of the property. The
+ /// method changes the effective value of the property, but existing data bindings and
+ /// styles will continue to work.
+ ///
+ /// The new value will have the property's current , even if
+ /// that priority is or
+ /// .
+ ///
+ public void SetCurrentValue(AvaloniaProperty property, object? value) =>
+ property.RouteSetCurrentValue(this, value);
+
+ ///
+ /// Sets the value of a dependency property without changing its value source.
+ ///
+ /// The type of the property.
+ /// The property.
+ /// The value.
+ ///
+ /// This method is used by a component that programmatically sets the value of one of its
+ /// own properties without disabling an application's declared use of the property. The
+ /// method changes the effective value of the property, but existing data bindings and
+ /// styles will continue to work.
+ ///
+ /// The new value will have the property's current , even if
+ /// that priority is or
+ /// .
+ ///
public void SetCurrentValue(StyledProperty property, T value)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 5db4d81f03..1c1d09c3f5 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -496,6 +496,13 @@ namespace Avalonia
object? value,
BindingPriority priority);
+ ///
+ /// Routes an untyped SetCurrentValue call to a typed call.
+ ///
+ /// The object instance.
+ /// The value.
+ internal abstract void RouteSetCurrentValue(AvaloniaObject o, object? value);
+
///
/// Routes an untyped Bind call to a typed call.
///
diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs
index 9ee1eee0fa..94dfaaab01 100644
--- a/src/Avalonia.Base/DirectPropertyBase.cs
+++ b/src/Avalonia.Base/DirectPropertyBase.cs
@@ -152,6 +152,11 @@ namespace Avalonia
return null;
}
+ internal override void RouteSetCurrentValue(AvaloniaObject o, object? value)
+ {
+ RouteSetValue(o, value, BindingPriority.LocalValue);
+ }
+
///
/// Routes an untyped Bind call to a typed call.
///
diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs
index 79d1b9202d..8e0ecf5544 100644
--- a/src/Avalonia.Base/StyledProperty.cs
+++ b/src/Avalonia.Base/StyledProperty.cs
@@ -220,6 +220,27 @@ namespace Avalonia
}
}
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
+ internal override void RouteSetCurrentValue(AvaloniaObject target, object? value)
+ {
+ if (value == BindingOperations.DoNothing)
+ return;
+
+ if (value == UnsetValue)
+ {
+ target.ClearValue(this);
+ }
+ else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted))
+ {
+ target.SetCurrentValue(this, (TValue)converted!);
+ }
+ else
+ {
+ var type = value?.GetType().FullName ?? "(null)";
+ throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})");
+ }
+ }
+
internal override IDisposable RouteBind(
AvaloniaObject target,
IObservable