Browse Source
However we still need to split the observers into two classes because otherwise we can't implement both `IObservable<T>` and `IObservable<object?>` on the same object.pull/10423/head
3 changed files with 55 additions and 123 deletions
@ -1,126 +1,25 @@ |
|||||
using System; |
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
using Avalonia.Data; |
using Avalonia.Data; |
||||
using Avalonia.Threading; |
|
||||
|
|
||||
namespace Avalonia.PropertyStore |
namespace Avalonia.PropertyStore |
||||
{ |
{ |
||||
internal class LocalValueBindingObserver<T> : IObserver<T>, |
internal class LocalValueBindingObserver<T> : LocalValueBindingObserverBase<T>, |
||||
IObserver<BindingValue<T>>, |
IObserver<object?> |
||||
IDisposable |
|
||||
{ |
{ |
||||
private readonly ValueStore _owner; |
|
||||
private readonly bool _hasDataValidation; |
|
||||
private IDisposable? _subscription; |
|
||||
private T? _defaultValue; |
|
||||
private bool _isDefaultValueInitialized; |
|
||||
|
|
||||
public LocalValueBindingObserver(ValueStore owner, StyledProperty<T> property) |
public LocalValueBindingObserver(ValueStore owner, StyledProperty<T> property) |
||||
|
: base(owner, property) |
||||
{ |
{ |
||||
_owner = owner; |
|
||||
Property = property; |
|
||||
_hasDataValidation = property.GetMetadata(owner.Owner.GetType()).EnableDataValidation ?? false; |
|
||||
} |
|
||||
|
|
||||
public StyledProperty<T> Property { get;} |
|
||||
|
|
||||
public void Start(IObservable<T> source) |
|
||||
{ |
|
||||
_subscription = source.Subscribe(this); |
|
||||
} |
|
||||
|
|
||||
public void Start(IObservable<BindingValue<T>> source) |
|
||||
{ |
|
||||
_subscription = source.Subscribe(this); |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
_subscription?.Dispose(); |
|
||||
_subscription = null; |
|
||||
_owner.OnLocalValueBindingCompleted(Property, this); |
|
||||
} |
} |
||||
|
|
||||
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); |
public void Start(IObservable<object?> source) => _subscription = source.Subscribe(this); |
||||
public void OnError(Exception error) => OnCompleted(); |
|
||||
|
|
||||
public void OnNext(T value) |
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] |
||||
|
public void OnNext(object? value) |
||||
{ |
{ |
||||
static void Execute(LocalValueBindingObserver<T> instance, T value) |
if (value == BindingOperations.DoNothing) |
||||
{ |
|
||||
var owner = instance._owner; |
|
||||
var property = instance.Property; |
|
||||
|
|
||||
if (property.ValidateValue?.Invoke(value) == false) |
|
||||
value = instance.GetCachedDefaultValue(); |
|
||||
|
|
||||
owner.SetLocalValue(property, value); |
|
||||
|
|
||||
if (instance._hasDataValidation) |
|
||||
owner.Owner.OnUpdateDataValidation(property, BindingValueType.Value, null); |
|
||||
} |
|
||||
|
|
||||
if (Dispatcher.UIThread.CheckAccess()) |
|
||||
{ |
|
||||
Execute(this, value); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
// To avoid allocating closure in the outer scope we need to capture variables
|
|
||||
// locally. This allows us to skip most of the allocations when on UI thread.
|
|
||||
var instance = this; |
|
||||
var newValue = value; |
|
||||
Dispatcher.UIThread.Post(() => Execute(instance, newValue)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void OnNext(BindingValue<T> value) |
|
||||
{ |
|
||||
static void Execute(LocalValueBindingObserver<T> instance, BindingValue<T> value) |
|
||||
{ |
|
||||
var owner = instance._owner; |
|
||||
var property = instance.Property; |
|
||||
var originalType = value.Type; |
|
||||
|
|
||||
LoggingUtils.LogIfNecessary(owner.Owner, property, value); |
|
||||
|
|
||||
// Revert to the default value if the binding value fails validation, or if
|
|
||||
// there was no value (though not if there was a data validation error).
|
|
||||
if ((value.HasValue && property.ValidateValue?.Invoke(value.Value) == false) || |
|
||||
(!value.HasValue && value.Type != BindingValueType.DataValidationError)) |
|
||||
value = value.WithValue(instance.GetCachedDefaultValue()); |
|
||||
|
|
||||
if (value.HasValue) |
|
||||
owner.SetLocalValue(property, value.Value); |
|
||||
if (instance._hasDataValidation) |
|
||||
owner.Owner.OnUpdateDataValidation(property, originalType, value.Error); |
|
||||
} |
|
||||
|
|
||||
if (value.Type is BindingValueType.DoNothing) |
|
||||
return; |
return; |
||||
|
base.OnNext(BindingValue<T>.FromUntyped(value, Property.PropertyType)); |
||||
if (Dispatcher.UIThread.CheckAccess()) |
|
||||
{ |
|
||||
Execute(this, value); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
// To avoid allocating closure in the outer scope we need to capture variables
|
|
||||
// locally. This allows us to skip most of the allocations when on UI thread.
|
|
||||
var instance = this; |
|
||||
var newValue = value; |
|
||||
Dispatcher.UIThread.Post(() => Execute(instance, newValue)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private T GetCachedDefaultValue() |
|
||||
{ |
|
||||
if (!_isDefaultValueInitialized) |
|
||||
{ |
|
||||
_defaultValue = Property.GetDefaultValue(_owner.Owner.GetType()); |
|
||||
_isDefaultValueInitialized = true; |
|
||||
} |
|
||||
|
|
||||
return _defaultValue!; |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue