Browse Source

Make setting styled values disposable.

pull/3636/head
Steven Kirk 6 years ago
parent
commit
2e99fa9a91
  1. 9
      src/Avalonia.Base/AvaloniaObject.cs
  2. 17
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  3. 5
      src/Avalonia.Base/AvaloniaProperty.cs
  4. 4
      src/Avalonia.Base/DirectPropertyBase.cs
  5. 2
      src/Avalonia.Base/IAvaloniaObject.cs
  6. 11
      src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
  7. 9
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  8. 6
      src/Avalonia.Base/StyledPropertyBase.cs
  9. 25
      src/Avalonia.Base/ValueStore.cs
  10. 35
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
  11. 2
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  12. 6
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

9
src/Avalonia.Base/AvaloniaObject.cs

@ -311,7 +311,10 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="priority">The priority of 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, StyledPropertyBase<T> property,
T value, T value,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
@ -335,8 +338,10 @@ namespace Avalonia
} }
else if (!(value is DoNothingType)) else if (!(value is DoNothingType))
{ {
Values.SetValue(property, value, priority); return Values.SetValue(property, value, priority);
} }
return null;
} }
/// <summary> /// <summary>

17
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -458,7 +458,10 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="priority">The priority of 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, this IAvaloniaObject target,
AvaloniaProperty property, AvaloniaProperty property,
object value, object value,
@ -467,7 +470,7 @@ namespace Avalonia
target = target ?? throw new ArgumentNullException(nameof(target)); target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
property.RouteSetValue(target, value, priority); return property.RouteSetValue(target, value, priority);
} }
/// <summary> /// <summary>
@ -478,7 +481,10 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="priority">The priority of 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, this IAvaloniaObject target,
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
T value, T value,
@ -490,11 +496,10 @@ namespace Avalonia
switch (property) switch (property)
{ {
case StyledPropertyBase<T> styled: case StyledPropertyBase<T> styled:
target.SetValue(styled, value, priority); return target.SetValue(styled, value, priority);
break;
case DirectPropertyBase<T> direct: case DirectPropertyBase<T> direct:
target.SetValue(direct, value); target.SetValue(direct, value);
break; return null;
default: default:
throw new NotSupportedException("Unsupported AvaloniaProperty type."); throw new NotSupportedException("Unsupported AvaloniaProperty type.");
} }

5
src/Avalonia.Base/AvaloniaProperty.cs

@ -496,7 +496,10 @@ namespace Avalonia
/// <param name="o">The object instance.</param> /// <param name="o">The object instance.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="priority">The priority.</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, IAvaloniaObject o,
object value, object value,
BindingPriority priority); BindingPriority priority);

4
src/Avalonia.Base/DirectPropertyBase.cs

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

2
src/Avalonia.Base/IAvaloniaObject.cs

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

11
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

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

9
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@ -78,8 +78,10 @@ namespace Avalonia.PropertyStore
public void ClearLocalValue() => UpdateEffectiveValue(); 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) if (priority == BindingPriority.LocalValue)
{ {
_localValue = value; _localValue = value;
@ -87,10 +89,13 @@ namespace Avalonia.PropertyStore
else else
{ {
var insert = FindInsertPoint(priority); 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(); UpdateEffectiveValue();
return result;
} }
public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPriority priority) public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPriority priority)

6
src/Avalonia.Base/StyledPropertyBase.cs

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

25
src/Avalonia.Base/ValueStore.cs

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

35
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs

@ -284,6 +284,41 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("newvalue", target.GetValue(Class1.FrankProperty)); 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 private class Class1 : AvaloniaObject
{ {
public static readonly StyledProperty<string> FooProperty = public static readonly StyledProperty<string> FooProperty =

2
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

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

6
tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

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

Loading…
Cancel
Save