Browse Source

Added untyped SetCurrentValue.

Steven Kirk 2 years ago
parent
commit
0ce9180d7c

+ 34 - 0
src/Avalonia.Base/AvaloniaObject.cs

@@ -355,6 +355,40 @@ namespace Avalonia
             SetDirectValueUnchecked(property, value);
         }
 
+        /// <summary>
+        /// Sets the value of a dependency property without changing its value source.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <param name="value">The value.</param>
+        /// <remarks>
+        /// 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 <see cref="BindingPriority"/>, even if
+        /// that priority is <see cref="BindingPriority.Unset"/> or 
+        /// <see cref="BindingPriority.Inherited"/>.
+        /// </remarks>
+        public void SetCurrentValue(AvaloniaProperty property, object? value) => 
+            property.RouteSetCurrentValue(this, value);
+
+        /// <summary>
+        /// Sets the value of a dependency property without changing its value source.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="value">The value.</param>
+        /// <remarks>
+        /// 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 <see cref="BindingPriority"/>, even if
+        /// that priority is <see cref="BindingPriority.Unset"/> or 
+        /// <see cref="BindingPriority.Inherited"/>.
+        /// </remarks>
         public void SetCurrentValue<T>(StyledProperty<T> property, T value)
         {
             _ = property ?? throw new ArgumentNullException(nameof(property));

+ 7 - 0
src/Avalonia.Base/AvaloniaProperty.cs

@@ -496,6 +496,13 @@ namespace Avalonia
             object? value,
             BindingPriority priority);
 
+        /// <summary>
+        /// Routes an untyped SetCurrentValue call to a typed call.
+        /// </summary>
+        /// <param name="o">The object instance.</param>
+        /// <param name="value">The value.</param>
+        internal abstract void RouteSetCurrentValue(AvaloniaObject o, object? value);
+
         /// <summary>
         /// Routes an untyped Bind call to a typed call.
         /// </summary>

+ 5 - 0
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);
+        }
+
         /// <summary>
         /// Routes an untyped Bind call to a typed call.
         /// </summary>

+ 21 - 0
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<TValue>(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<object?> source,

+ 26 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs

@@ -21,6 +21,19 @@ namespace Avalonia.Base.UnitTests
             Assert.True(IsOverridden(target, Class1.FooProperty));
         }
 
+        [Fact]
+        public void SetCurrentValue_Sets_Unset_Value_Untyped()
+        {
+            var target = new Class1();
+
+            target.SetCurrentValue((AvaloniaProperty)Class1.FooProperty, "newvalue");
+
+            Assert.Equal("newvalue", target.GetValue(Class1.FooProperty));
+            Assert.True(target.IsSet(Class1.FooProperty));
+            Assert.Equal(BindingPriority.Unset, GetPriority(target, Class1.FooProperty));
+            Assert.True(IsOverridden(target, Class1.FooProperty));
+        }
+
         [Theory]
         [InlineData(BindingPriority.LocalValue)]
         [InlineData(BindingPriority.Style)]
@@ -140,6 +153,19 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal(60, target.GetValue(Class1.CoercedProperty));
         }
 
+        [Fact]
+        public void SetCurrentValue_Unset_Clears_CurrentValue()
+        {
+            var target = new Class1();
+
+            target.SetCurrentValue(Class1.FooProperty, "newvalue");
+            target.SetCurrentValue(Class1.FooProperty, AvaloniaProperty.UnsetValue);
+
+            Assert.Equal("foodefault", target.Foo);
+            Assert.False(target.IsSet(Class1.FooProperty));
+            Assert.False(IsOverridden(target, Class1.FooProperty));
+        }
+
         [Theory]
         [InlineData(BindingPriority.LocalValue)]
         [InlineData(BindingPriority.Style)]

+ 5 - 0
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@@ -179,6 +179,11 @@ namespace Avalonia.Base.UnitTests
                 throw new NotImplementedException();
             }
 
+            internal override void RouteSetCurrentValue(AvaloniaObject o, object value)
+            {
+                throw new NotImplementedException();
+            }
+
             internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o)
             {
                 throw new NotImplementedException();