Browse Source

Update data validation from EffectiveValue.

Requires `ValueEntry`/`BaseValueEntry` to be available to `EffectiveValue<T>`.

Also change the tests to only test values coming back from the model in the style binding tests, as they don't work when setting local values currently. This will be fixed later.
pull/10423/head
Steven Kirk 3 years ago
parent
commit
a899185afb
  1. 1
      src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
  2. 51
      src/Avalonia.Base/PropertyStore/EffectiveValue.cs
  3. 26
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  4. 7
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  5. 9
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs

1
src/Avalonia.Base/PropertyStore/BindingEntryBase.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using Avalonia.Reactive;
using Avalonia.Data;
using Avalonia.Threading;
using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{

51
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@ -11,9 +11,6 @@ namespace Avalonia.PropertyStore
/// </remarks>
internal abstract class EffectiveValue
{
private IValueEntry? _valueEntry;
private IValueEntry? _baseValueEntry;
/// <summary>
/// Gets the current effective value as a boxed value.
/// </summary>
@ -29,6 +26,16 @@ namespace Avalonia.PropertyStore
/// </summary>
public BindingPriority BasePriority { get; protected set; }
/// <summary>
/// Gets the active value entry for the current effective value.
/// </summary>
public IValueEntry? ValueEntry { get; private set; }
/// <summary>
/// Gets the active value entry for the current base value.
/// </summary>
public IValueEntry? BaseValueEntry { get; private set; }
/// <summary>
/// Gets a value indicating whether the <see cref="Value"/> was overridden by a call to
/// <see cref="AvaloniaObject.SetCurrentValue{T}"/>.
@ -63,14 +70,14 @@ namespace Avalonia.PropertyStore
{
if (Priority == BindingPriority.Unset)
{
_valueEntry?.Unsubscribe();
_valueEntry = null;
ValueEntry?.Unsubscribe();
ValueEntry = null;
}
if (BasePriority == BindingPriority.Unset)
{
_baseValueEntry?.Unsubscribe();
_baseValueEntry = null;
BaseValueEntry?.Unsubscribe();
BaseValueEntry = null;
}
}
@ -135,40 +142,34 @@ namespace Avalonia.PropertyStore
// value, then the current entry becomes our base entry.
if (Priority > BindingPriority.LocalValue && Priority < BindingPriority.Inherited)
{
Debug.Assert(_valueEntry is not null);
_baseValueEntry = _valueEntry;
_valueEntry = null;
Debug.Assert(ValueEntry is not null);
BaseValueEntry = ValueEntry;
ValueEntry = null;
}
if (_valueEntry != entry)
if (ValueEntry != entry)
{
_valueEntry?.Unsubscribe();
_valueEntry = entry;
ValueEntry?.Unsubscribe();
ValueEntry = entry;
}
}
else if (Priority <= BindingPriority.Animation)
{
// We've received a non-animation value and have an active animation value, so the
// new entry becomes our base entry.
if (_baseValueEntry != entry)
if (BaseValueEntry != entry)
{
_baseValueEntry?.Unsubscribe();
_baseValueEntry = entry;
BaseValueEntry?.Unsubscribe();
BaseValueEntry = entry;
}
}
else if (_valueEntry != entry)
else if (ValueEntry != entry)
{
// Both the current value and the new value are non-animation values, so the new
// entry replaces the existing entry.
_valueEntry?.Unsubscribe();
_valueEntry = entry;
ValueEntry?.Unsubscribe();
ValueEntry = entry;
}
}
protected void UnsubscribeValueEntries()
{
_valueEntry?.Unsubscribe();
_baseValueEntry?.Unsubscribe();
}
}
}

26
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
@ -61,6 +62,12 @@ namespace Avalonia.PropertyStore
UpdateValueEntry(value, priority);
SetAndRaiseCore(owner, (StyledProperty<T>)value.Property, GetValue(value), priority, false);
if (priority > BindingPriority.LocalValue &&
value.GetDataValidationState(out var state, out var error))
{
owner.Owner.OnUpdateDataValidation(value.Property, state, error);
}
}
public void SetLocalValueAndRaise(
@ -128,12 +135,10 @@ namespace Avalonia.PropertyStore
public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property)
{
UnsubscribeValueEntries();
DisposeAndRaiseUnset(owner, (StyledProperty<T>)property);
}
ValueEntry?.Unsubscribe();
BaseValueEntry?.Unsubscribe();
public void DisposeAndRaiseUnset(ValueStore owner, StyledProperty<T> property)
{
var p = (StyledProperty<T>)property;
BindingPriority priority;
T oldValue;
@ -150,9 +155,16 @@ namespace Avalonia.PropertyStore
if (!EqualityComparer<T>.Default.Equals(oldValue, Value))
{
owner.Owner.RaisePropertyChanged(property, Value, oldValue, priority, true);
owner.Owner.RaisePropertyChanged(p, Value, oldValue, priority, true);
if (property.Inherits)
owner.OnInheritedEffectiveValueDisposed(property, Value);
owner.OnInheritedEffectiveValueDisposed(p, Value);
}
if (ValueEntry?.GetDataValidationState(out _, out _) ??
BaseValueEntry?.GetDataValidationState(out _, out _) ??
false)
{
owner.Owner.OnUpdateDataValidation(p, BindingValueType.UnsetValue, null);
}
}

7
src/Avalonia.Base/PropertyStore/ValueStore.cs

@ -838,8 +838,6 @@ namespace Avalonia.PropertyStore
break;
}
current?.EndReevaluation();
if (current?.Priority == BindingPriority.Unset)
{
if (current.BasePriority == BindingPriority.Unset)
@ -852,6 +850,8 @@ namespace Avalonia.PropertyStore
current.RemoveAnimationAndRaise(this, property);
}
}
current?.EndReevaluation();
}
finally
{
@ -923,7 +923,6 @@ namespace Avalonia.PropertyStore
for (var i = _effectiveValues.Count - 1; i >= 0; --i)
{
_effectiveValues.GetKeyValue(i, out var key, out var e);
e.EndReevaluation();
if (e.Priority == BindingPriority.Unset)
{
@ -933,6 +932,8 @@ namespace Avalonia.PropertyStore
if (i > _effectiveValues.Count)
break;
}
e.EndReevaluation();
}
}
finally

9
tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs

@ -89,9 +89,10 @@ namespace Avalonia.Markup.UnitTests.Data
Mode = BindingMode.TwoWay
};
var model = new IndeiValidatingModel();
var root = new TestRoot
{
DataContext = new IndeiValidatingModel(),
DataContext = model,
Styles =
{
new Style(x => x.Is<DataValidationTestControl>())
@ -109,13 +110,13 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(20, target.GetValue(property));
target.SetValue(property, 200);
model.Value = 200;
Assert.Equal(200, target.GetValue(property));
Assert.IsType<DataValidationException>(target.DataValidationError);
Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
target.SetValue(property, 10);
model.Value = 10;
Assert.Equal(10, target.GetValue(property));
Assert.Null(target.DataValidationError);
@ -166,7 +167,7 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.IsType<DataValidationException>(target.DataValidationError);
Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
target.SetValue(property, 10);
model.Value = 10;
Assert.Equal(10, target.GetValue(property));
Assert.Null(target.DataValidationError);

Loading…
Cancel
Save