瀏覽代碼

Make setting styled values disposable.

Steven Kirk 5 年之前
父節點
當前提交
2e99fa9a91

+ 7 - 2
src/Avalonia.Base/AvaloniaObject.cs

@@ -311,7 +311,10 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
         /// <param name="priority">The priority of the value.</param>
-        public void SetValue<T>(
+        /// <returns>
+        /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
+        /// </returns>
+        public IDisposable SetValue<T>(
             StyledPropertyBase<T> property,
             T value,
             BindingPriority priority = BindingPriority.LocalValue)
@@ -335,8 +338,10 @@ namespace Avalonia
             }
             else if (!(value is DoNothingType))
             {
-                Values.SetValue(property, value, priority);
+                return Values.SetValue(property, value, priority);
             }
+
+            return null;
         }
 
         /// <summary>

+ 11 - 6
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -458,7 +458,10 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
         /// <param name="priority">The priority of the value.</param>
-        public static void SetValue(
+        /// <returns>
+        /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
+        /// </returns>
+        public static IDisposable SetValue(
             this IAvaloniaObject target,
             AvaloniaProperty property,
             object value,
@@ -467,7 +470,7 @@ namespace Avalonia
             target = target ?? throw new ArgumentNullException(nameof(target));
             property = property ?? throw new ArgumentNullException(nameof(property));
 
-            property.RouteSetValue(target, value, priority);
+            return property.RouteSetValue(target, value, priority);
         }
 
         /// <summary>
@@ -478,7 +481,10 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
         /// <param name="priority">The priority of the value.</param>
-        public static void SetValue<T>(
+        /// <returns>
+        /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
+        /// </returns>
+        public static IDisposable SetValue<T>(
             this IAvaloniaObject target,
             AvaloniaProperty<T> property,
             T value,
@@ -490,11 +496,10 @@ namespace Avalonia
             switch (property)
             {
                 case StyledPropertyBase<T> styled:
-                    target.SetValue(styled, value, priority);
-                    break;
+                    return target.SetValue(styled, value, priority);
                 case DirectPropertyBase<T> direct:
                     target.SetValue(direct, value);
-                    break;
+                    return null;
                 default:
                     throw new NotSupportedException("Unsupported AvaloniaProperty type.");
             }

+ 4 - 1
src/Avalonia.Base/AvaloniaProperty.cs

@@ -496,7 +496,10 @@ namespace Avalonia
         /// <param name="o">The object instance.</param>
         /// <param name="value">The value.</param>
         /// <param name="priority">The priority.</param>
-        internal abstract void RouteSetValue(
+        /// <returns>
+        /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
+        /// </returns>
+        internal abstract IDisposable? RouteSetValue(
             IAvaloniaObject o,
             object value,
             BindingPriority priority);

+ 3 - 1
src/Avalonia.Base/DirectPropertyBase.cs

@@ -114,7 +114,7 @@ namespace Avalonia
         }
 
         /// <inheritdoc/>
-        internal override void RouteSetValue(
+        internal override IDisposable? RouteSetValue(
             IAvaloniaObject o,
             object value,
             BindingPriority priority)
@@ -133,6 +133,8 @@ namespace Avalonia
             {
                 throw v.Error!;
             }
+
+            return null;
         }
 
         /// <inheritdoc/>

+ 1 - 1
src/Avalonia.Base/IAvaloniaObject.cs

@@ -65,7 +65,7 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
         /// <param name="priority">The priority of the value.</param>
-        void SetValue<T>(
+        IDisposable SetValue<T>(
             StyledPropertyBase<T> property,
             T value,
             BindingPriority priority = BindingPriority.LocalValue);

+ 8 - 3
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@@ -10,16 +10,20 @@ namespace Avalonia.PropertyStore
     /// <see cref="PriorityValue{T}"/>.
     /// </summary>
     /// <typeparam name="T">The property type.</typeparam>
-    internal class ConstantValueEntry<T> : IPriorityValueEntry<T>
+    internal class ConstantValueEntry<T> : IPriorityValueEntry<T>, IDisposable
     {
+        private IValueSink _sink;
+
         public ConstantValueEntry(
             StyledPropertyBase<T> property,
             T value,
-            BindingPriority priority)
+            BindingPriority priority,
+            IValueSink sink)
         {
             Property = property;
             Value = value;
             Priority = priority;
+            _sink = sink;
         }
 
         public StyledPropertyBase<T> Property { get; }
@@ -28,6 +32,7 @@ namespace Avalonia.PropertyStore
         Optional<object> IValue.Value => Value.ToObject();
         BindingPriority IValue.ValuePriority => Priority;
 
-        public void Reparent(IValueSink sink) { }
+        public void Dispose() => _sink.Completed(Property, this, Value);
+        public void Reparent(IValueSink sink) => _sink = sink;
     }
 }

+ 7 - 2
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@@ -78,8 +78,10 @@ namespace Avalonia.PropertyStore
 
         public void ClearLocalValue() => UpdateEffectiveValue();
 
-        public void SetValue(T value, BindingPriority priority)
+        public IDisposable? SetValue(T value, BindingPriority priority)
         {
+            IDisposable? result = null;
+
             if (priority == BindingPriority.LocalValue)
             {
                 _localValue = value;
@@ -87,10 +89,13 @@ namespace Avalonia.PropertyStore
             else
             {
                 var insert = FindInsertPoint(priority);
-                _entries.Insert(insert, new ConstantValueEntry<T>(Property, value, priority));
+                var entry = new ConstantValueEntry<T>(Property, value, priority, this);
+                _entries.Insert(insert, entry);
+                result = entry;
             }
 
             UpdateEffectiveValue();
+            return result;
         }
 
         public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPriority priority)

+ 4 - 2
src/Avalonia.Base/StyledPropertyBase.cs

@@ -194,7 +194,7 @@ namespace Avalonia
         }
 
         /// <inheritdoc/>
-        internal override void RouteSetValue(
+        internal override IDisposable RouteSetValue(
             IAvaloniaObject o,
             object value,
             BindingPriority priority)
@@ -203,7 +203,7 @@ namespace Avalonia
 
             if (v.HasValue)
             {
-                o.SetValue<TValue>(this, (TValue)v.Value, priority);
+                return o.SetValue<TValue>(this, (TValue)v.Value, priority);
             }
             else if (v.Type == BindingValueType.UnsetValue)
             {
@@ -213,6 +213,8 @@ namespace Avalonia
             {
                 throw v.Error;
             }
+
+            return null;
         }
 
         /// <inheritdoc/>

+ 17 - 8
src/Avalonia.Base/ValueStore.cs

@@ -70,23 +70,25 @@ namespace Avalonia
             return false;
         }
 
-        public void SetValue<T>(StyledPropertyBase<T> property, T value, BindingPriority priority)
+        public IDisposable? SetValue<T>(StyledPropertyBase<T> property, T value, BindingPriority priority)
         {
             if (property.ValidateValue?.Invoke(value) == false)
             {
                 throw new ArgumentException($"{value} is not a valid value for '{property.Name}.");
             }
 
+            IDisposable? result = null;
+
             if (_values.TryGetValue(property, out var slot))
             {
-                SetExisting(slot, property, value, priority);
+                result = SetExisting(slot, property, value, priority);
             }
             else if (property.HasCoercion)
             {
                 // If the property has any coercion callbacks then always create a PriorityValue.
                 var entry = new PriorityValue<T>(_owner, property, this);
                 _values.AddValue(property, entry);
-                entry.SetValue(value, priority);
+                result = entry.SetValue(value, priority);
             }
             else if (priority == BindingPriority.LocalValue)
             {
@@ -95,10 +97,13 @@ namespace Avalonia
             }
             else
             {
-                var entry = new ConstantValueEntry<T>(property, value, priority);
+                var entry = new ConstantValueEntry<T>(property, value, priority, this);
                 _values.AddValue(property, entry);
                 _sink.ValueChanged(property, priority, default, value);
+                result = entry;
             }
+
+            return result;
         }
 
         public IDisposable AddBinding<T>(
@@ -205,21 +210,23 @@ namespace Avalonia
             }
         }
 
-        private void SetExisting<T>(
+        private IDisposable? SetExisting<T>(
             object slot,
             StyledPropertyBase<T> property,
             T value,
             BindingPriority priority)
         {
+            IDisposable? result = null;
+
             if (slot is IPriorityValueEntry<T> e)
             {
                 var priorityValue = new PriorityValue<T>(_owner, property, this, e);
                 _values.SetValue(property, priorityValue);
-                priorityValue.SetValue(value, priority);
+                result = priorityValue.SetValue(value, priority);
             }
             else if (slot is PriorityValue<T> p)
             {
-                p.SetValue(value, priority);
+                result = p.SetValue(value, priority);
             }
             else if (slot is LocalValueEntry<T> l)
             {
@@ -232,7 +239,7 @@ namespace Avalonia
                 else
                 {
                     var priorityValue = new PriorityValue<T>(_owner, property, this, l);
-                    priorityValue.SetValue(value, priority);
+                    result = priorityValue.SetValue(value, priority);
                     _values.SetValue(property, priorityValue);
                 }
             }
@@ -240,6 +247,8 @@ namespace Avalonia
             {
                 throw new NotSupportedException("Unrecognised value store slot type.");
             }
+
+            return result;
         }
 
         private IDisposable BindExisting<T>(

+ 35 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs

@@ -284,6 +284,41 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("newvalue", target.GetValue(Class1.FrankProperty));
         }
 
+        [Fact]
+        public void Disposing_Style_SetValue_Reverts_To_DefaultValue()
+        {
+            Class1 target = new Class1();
+
+            var d = target.SetValue(Class1.FooProperty, "foo", BindingPriority.Style);
+            d.Dispose();
+
+            Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
+        }
+
+        [Fact]
+        public void Disposing_Style_SetValue_Reverts_To_Previous_Style_Value()
+        {
+            Class1 target = new Class1();
+
+            target.SetValue(Class1.FooProperty, "foo", BindingPriority.Style);
+            var d = target.SetValue(Class1.FooProperty, "bar", BindingPriority.Style);
+            d.Dispose();
+
+            Assert.Equal("foo", target.GetValue(Class1.FooProperty));
+        }
+
+        [Fact]
+        public void Disposing_Animation_SetValue_Reverts_To_Previous_Local_Value()
+        {
+            Class1 target = new Class1();
+
+            target.SetValue(Class1.FooProperty, "foo", BindingPriority.LocalValue);
+            var d = target.SetValue(Class1.FooProperty, "bar", BindingPriority.Animation);
+            d.Dispose();
+
+            Assert.Equal("foo", target.GetValue(Class1.FooProperty));
+        }
+
         private class Class1 : AvaloniaObject
         {
             public static readonly StyledProperty<string> FooProperty =

+ 1 - 1
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@@ -146,7 +146,7 @@ namespace Avalonia.Base.UnitTests
                 throw new NotImplementedException();
             }
 
-            internal override void RouteSetValue(
+            internal override IDisposable RouteSetValue(
                 IAvaloniaObject o,
                 object value,
                 BindingPriority priority)

+ 5 - 1
tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

@@ -24,7 +24,11 @@ namespace Avalonia.Base.UnitTests
                 Owner,
                 TestProperty,
                 NullSink,
-                new ConstantValueEntry<string>(TestProperty, "1", BindingPriority.StyleTrigger));
+                new ConstantValueEntry<string>(
+                    TestProperty,
+                    "1",
+                    BindingPriority.StyleTrigger,
+                    NullSink));
 
             Assert.Equal("1", target.Value.Value);
             Assert.Equal(BindingPriority.StyleTrigger, target.ValuePriority);