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="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>

17
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.");
}

5
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);

4
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/>

2
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);

11
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;
}
}

9
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)

6
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/>

25
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
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 =

2
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)

6
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);

Loading…
Cancel
Save