Browse Source

Store direct property bindings in ValueStore.

We already have a place to store them in the `_localValueBindings` list, so use that rather than having a separate `_directBindings` list in `AvaloniaObject`.
refactor/style-priorities
Steven Kirk 4 years ago
parent
commit
8f98c2ae76
  1. 131
      src/Avalonia.Base/AvaloniaObject.cs
  2. 18
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  3. 11
      src/Avalonia.Base/AvaloniaProperty.cs
  4. 20
      src/Avalonia.Base/Data/BindingNotification.cs
  5. 116
      src/Avalonia.Base/Data/BindingValue.cs
  6. 14
      src/Avalonia.Base/DirectPropertyBase.cs
  7. 76
      src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs
  8. 55
      src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs
  9. 34
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  10. 59
      src/Avalonia.Base/Reactive/BindingValueAdapter.cs
  11. 33
      src/Avalonia.Base/Reactive/BindingValueExtensions.cs
  12. 62
      src/Avalonia.Base/Reactive/TypedBindingAdapter.cs
  13. 55
      src/Avalonia.Base/Reactive/UntypedBindingAdapter.cs
  14. 10
      src/Avalonia.Base/StyledPropertyBase.cs
  15. 8
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

131
src/Avalonia.Base/AvaloniaObject.cs

@ -18,7 +18,6 @@ namespace Avalonia
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
{
private AvaloniaObject? _inheritanceParent;
private List<IDisposable>? _directBindings;
private PropertyChangedEventHandler? _inpcChanged;
private EventHandler<AvaloniaPropertyChangedEventArgs>? _propertyChanged;
private List<AvaloniaObject>? _inheritanceChildren;
@ -336,6 +335,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
LogPropertySet(property, value, BindingPriority.LocalValue);
SetDirectValueUnchecked(property, value);
}
@ -343,7 +343,6 @@ namespace Avalonia
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
@ -355,7 +354,6 @@ namespace Avalonia
IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue) => property.RouteBind(this, source, priority);
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
@ -433,10 +431,9 @@ namespace Avalonia
/// </returns>
public IDisposable Bind<T>(
DirectPropertyBase<T> property,
IObservable<BindingValue<T>> source)
IObservable<object?> source)
{
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
VerifyAccess();
property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
@ -446,15 +443,59 @@ namespace Avalonia
throw new ArgumentException($"The property {property.Name} is readonly.");
}
Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log(
this,
"Bound {Property} to {Binding} with priority LocalValue",
property,
GetDescription(source));
return _values.AddBinding(property, source);
}
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
DirectPropertyBase<T> property,
IObservable<T> source)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
_directBindings ??= new List<IDisposable>();
if (property.IsReadOnly)
{
throw new ArgumentException($"The property {property.Name} is readonly.");
}
return new DirectBindingSubscription<T>(this, property, source);
return _values.AddBinding(property, source);
}
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
DirectPropertyBase<T> property,
IObservable<BindingValue<T>> source)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
if (property.IsReadOnly)
{
throw new ArgumentException($"The property {property.Name} is readonly.");
}
return _values.AddBinding(property, source);
}
/// <summary>
@ -562,7 +603,7 @@ namespace Avalonia
{
}
// <summary>
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event for a direct property.
/// </summary>
/// <param name="property">The property that has changed.</param>
@ -644,17 +685,15 @@ namespace Avalonia
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
private void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, T value)
internal void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, T value)
{
var p = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
if (value is UnsetValueType)
{
p.InvokeSetter(this, p.GetUnsetValue(GetType()));
property.InvokeSetter(this, property.GetUnsetValue(GetType()));
}
else if (!(value is DoNothingType))
{
p.InvokeSetter(this, value);
property.InvokeSetter(this, value);
}
}
@ -663,15 +702,8 @@ namespace Avalonia
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
private void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, BindingValue<T> value)
internal void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, BindingValue<T> value)
{
var p = AvaloniaPropertyRegistry.Instance.FindRegisteredDirect(this, property);
if (p == null)
{
throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}");
}
LoggingUtils.LogIfNecessary(this, property, value);
switch (value.Type)
@ -691,7 +723,7 @@ namespace Avalonia
break;
}
var metadata = p.GetMetadata(GetType());
var metadata = property.GetMetadata(GetType());
if (metadata.EnableDataValidation == true)
{
@ -725,50 +757,5 @@ namespace Avalonia
value,
priority);
}
private class DirectBindingSubscription<T> : IObserver<BindingValue<T>>, IDisposable
{
private readonly AvaloniaObject _owner;
private readonly DirectPropertyBase<T> _property;
private readonly IDisposable _subscription;
public DirectBindingSubscription(
AvaloniaObject owner,
DirectPropertyBase<T> property,
IObservable<BindingValue<T>> source)
{
_owner = owner;
_property = property;
_owner._directBindings!.Add(this);
_subscription = source.Subscribe(this);
}
public void Dispose()
{
// _subscription can be null, if Subscribe failed with an exception.
_subscription?.Dispose();
_owner._directBindings!.Remove(this);
}
public void OnCompleted() => Dispose();
public void OnError(Exception error) => Dispose();
public void OnNext(BindingValue<T> value)
{
if (Dispatcher.UIThread.CheckAccess())
{
_owner.SetDirectValueUnchecked(_property, 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 = _owner;
var property = _property;
var newValue = value;
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
}
}
}
}
}

18
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -261,7 +261,6 @@ namespace Avalonia
}
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
}
/// <summary>
@ -280,14 +279,17 @@ namespace Avalonia
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
if (target is AvaloniaObject ao)
{
return property switch
{
StyledPropertyBase<T> styled => ao.Bind(styled, source, priority),
DirectPropertyBase<T> direct => ao.Bind(direct, source),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
}
return target.Bind(
property,
source.ToBindingValue(),
priority);
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
}
/// <summary>

11
src/Avalonia.Base/AvaloniaProperty.cs

@ -505,17 +505,6 @@ namespace Avalonia
IObservable<object?> source,
BindingPriority priority);
/// <summary>
/// Routes an untyped Bind call to a typed call.
/// </summary>
/// <param name="o">The object instance.</param>
/// <param name="source">The binding source.</param>
/// <param name="priority">The priority.</param>
internal abstract IDisposable RouteBind(
AvaloniaObject o,
IObservable<BindingValue<object?>> source,
BindingPriority priority);
/// <summary>
/// Overrides the metadata for the property on the specified type.
/// </summary>

20
src/Avalonia.Base/Data/BindingNotification.cs

@ -241,26 +241,6 @@ namespace Avalonia.Data
_value = value;
}
public BindingValue<object?> ToBindingValue()
{
if (ErrorType == BindingErrorType.None)
{
return HasValue ? new BindingValue<object?>(Value) : BindingValue<object?>.Unset;
}
else if (ErrorType == BindingErrorType.Error)
{
return BindingValue<object?>.BindingError(
Error!,
HasValue ? new Optional<object?>(Value) : Optional<object?>.Empty);
}
else
{
return BindingValue<object?>.DataValidationError(
Error!,
HasValue ? new Optional<object?>(Value) : Optional<object?>.Empty);
}
}
/// <inheritdoc/>
public override string ToString()
{

116
src/Avalonia.Base/Data/BindingValue.cs

@ -231,19 +231,64 @@ namespace Avalonia.Data
/// <summary>
/// Creates a <see cref="BindingValue{T}"/> from an object, handling the special values
/// <see cref="AvaloniaProperty.UnsetValue"/> and <see cref="BindingOperations.DoNothing"/>.
/// <see cref="AvaloniaProperty.UnsetValue"/>, <see cref="BindingOperations.DoNothing"/> and
/// <see cref="BindingNotification"/>.
/// </summary>
/// <param name="value">The untyped value.</param>
/// <returns>The typed binding value.</returns>
public static BindingValue<T> FromUntyped(object? value)
{
return value switch
if (value == AvaloniaProperty.UnsetValue)
return Unset;
else if (value == BindingOperations.DoNothing)
return DoNothing;
var type = BindingValueType.Value;
T? v = default;
Exception? error = null;
List<Exception>? errors = null;
if (value is BindingNotification n)
{
UnsetValueType _ => Unset,
DoNothingType _ => DoNothing,
BindingNotification n => n.ToBindingValue().Cast<T>(),
_ => new BindingValue<T>((T)value!)
};
error = n.Error;
type = n.ErrorType switch
{
BindingErrorType.Error => BindingValueType.BindingError,
BindingErrorType.DataValidationError => BindingValueType.DataValidationError,
_ => BindingValueType.Value,
};
if (n.HasValue)
type |= BindingValueType.HasValue;
value = n.Value;
}
if ((type & BindingValueType.HasValue) != 0)
{
if (TypeUtilities.TryConvertImplicit(typeof(T), value, out var typed))
v = (T)typed!;
else
{
var e = new InvalidCastException(
$"Unable to convert object '{value ?? "(null)"}' " +
$"of type '{value?.GetType()}' to type '{typeof(T)}'.");
if (error is null)
error = e;
else
{
errors ??= new List<Exception>() { error };
errors.Add(e);
}
type = BindingValueType.BindingError;
}
}
if (errors is not null)
error = new AggregateException(errors);
return new BindingValue<T>(type, v, error);
}
public static bool operator !=(BindingValue<T> x, Optional<T> y)
@ -401,61 +446,4 @@ namespace Avalonia.Data
}
}
}
public static class BindingValueExtensions
{
/// <summary>
/// Casts the type of a <see cref="BindingValue{T}"/> using only the C# cast operator.
/// </summary>
/// <typeparam name="T">The target type.</typeparam>
/// <param name="value">The binding value.</param>
/// <returns>The cast value.</returns>
public static BindingValue<T> Cast<T>(this BindingValue<object?> value)
{
return value.Type switch
{
BindingValueType.DoNothing => BindingValue<T>.DoNothing,
BindingValueType.UnsetValue => BindingValue<T>.Unset,
BindingValueType.Value => new BindingValue<T>((T)value.Value!),
BindingValueType.BindingError => BindingValue<T>.BindingError(value.Error!),
BindingValueType.BindingErrorWithFallback => BindingValue<T>.BindingError(
value.Error!,
(T)value.Value!),
BindingValueType.DataValidationError => BindingValue<T>.DataValidationError(value.Error!),
BindingValueType.DataValidationErrorWithFallback => BindingValue<T>.DataValidationError(
value.Error!,
(T)value.Value!),
_ => throw new NotSupportedException("Invalid BindingValue type."),
};
}
/// <summary>
/// Casts the type of a <see cref="BindingValue{T}"/> using the implicit conversions
/// allowed by the C# language.
/// </summary>
/// <typeparam name="T">The target type.</typeparam>
/// <param name="value">The binding value.</param>
/// <returns>The cast value.</returns>
/// <remarks>
/// Note that this method uses reflection and as such may be slow.
/// </remarks>
public static BindingValue<T> Convert<T>(this BindingValue<object?> value)
{
return value.Type switch
{
BindingValueType.DoNothing => BindingValue<T>.DoNothing,
BindingValueType.UnsetValue => BindingValue<T>.Unset,
BindingValueType.Value => new BindingValue<T>(TypeUtilities.ConvertImplicit<T>(value.Value!)),
BindingValueType.BindingError => BindingValue<T>.BindingError(value.Error!),
BindingValueType.BindingErrorWithFallback => BindingValue<T>.BindingError(
value.Error!,
TypeUtilities.ConvertImplicit<T>(value.Value!)),
BindingValueType.DataValidationError => BindingValue<T>.DataValidationError(value.Error!),
BindingValueType.DataValidationErrorWithFallback => BindingValue<T>.DataValidationError(
value.Error!,
TypeUtilities.ConvertImplicit<T>(value.Value!)),
_ => throw new NotSupportedException("Invalid BindingValue type."),
};
}
}
}

14
src/Avalonia.Base/DirectPropertyBase.cs

@ -178,19 +178,7 @@ namespace Avalonia
IObservable<object?> source,
BindingPriority priority)
{
// TODO: this requires a double adapter, we should make AvaloniaObject
// accept an `IObservable<object?>` for direct properties directly.
return RouteBind(o, source.ToBindingValue(), priority);
}
/// <inheritdoc/>
internal override IDisposable RouteBind(
AvaloniaObject o,
IObservable<BindingValue<object?>> source,
BindingPriority priority)
{
var adapter = TypedBindingAdapter<TValue>.Create(o, this, source);
return o.Bind<TValue>(this, adapter);
return o.Bind(this, source);
}
}
}

76
src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs

@ -0,0 +1,76 @@
using System;
using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.PropertyStore
{
internal class DirectBindingObserver<T> : IObserver<T>,
IObserver<BindingValue<T>>,
IDisposable
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
public DirectBindingObserver(ValueStore owner, DirectPropertyBase<T> property)
{
_owner = owner;
Property = property;
}
public DirectPropertyBase<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 OnError(Exception error) => OnCompleted();
public void OnNext(T value)
{
if (Dispatcher.UIThread.CheckAccess())
{
_owner.Owner.SetDirectValueUnchecked<T>(Property, 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 = _owner.Owner;
var property = Property;
var newValue = value;
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
}
}
public void OnNext(BindingValue<T> value)
{
if (Dispatcher.UIThread.CheckAccess())
{
_owner.Owner.SetDirectValueUnchecked<T>(Property, 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 = _owner.Owner;
var property = Property;
var newValue = value;
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
}
}
}
}

55
src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs

@ -0,0 +1,55 @@
using System;
using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.PropertyStore
{
internal class DirectUntypedBindingObserver<T> : IObserver<object?>,
IDisposable
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
public DirectUntypedBindingObserver(ValueStore owner, DirectPropertyBase<T> property)
{
_owner = owner;
Property = property;
}
public DirectPropertyBase<T> Property { get;}
public void Start(IObservable<object?> 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 OnError(Exception error) => OnCompleted();
public void OnNext(object? value)
{
var typed = BindingValue<T>.FromUntyped(value);
if (Dispatcher.UIThread.CheckAccess())
{
_owner.Owner.SetDirectValueUnchecked<T>(Property, typed);
}
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 = _owner.Owner;
var property = Property;
var newValue = value;
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, typed));
}
}
}
}

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

@ -120,6 +120,36 @@ namespace Avalonia.PropertyStore
}
}
public IDisposable AddBinding<T>(DirectPropertyBase<T> property, IObservable<BindingValue<T>> source)
{
var observer = new DirectBindingObserver<T>(this, property);
DisposeExistingLocalValueBinding(property);
_localValueBindings ??= new();
_localValueBindings[property.Id] = observer;
observer.Start(source);
return observer;
}
public IDisposable AddBinding<T>(DirectPropertyBase<T> property, IObservable<T> source)
{
var observer = new DirectBindingObserver<T>(this, property);
DisposeExistingLocalValueBinding(property);
_localValueBindings ??= new();
_localValueBindings[property.Id] = observer;
observer.Start(source);
return observer;
}
public IDisposable AddBinding<T>(DirectPropertyBase<T> property, IObservable<object?> source)
{
var observer = new DirectUntypedBindingObserver<T>(this, property);
DisposeExistingLocalValueBinding(property);
_localValueBindings ??= new();
_localValueBindings[property.Id] = observer;
observer.Start(source);
return observer;
}
public void ClearLocalValue(AvaloniaProperty property)
{
if (TryGetEffectiveValue(property, out var effective) &&
@ -470,7 +500,8 @@ namespace Avalonia.PropertyStore
}
/// <summary>
/// Called when a <see cref="LocalValueBindingObserver{T}"/> completes.
/// Called when a <see cref="LocalValueBindingObserver{T}"/> or
/// <see cref="DirectBindingObserver{T}"/> completes.
/// </summary>
/// <param name="property">The previously bound property.</param>
/// <param name="observer">The observer.</param>
@ -641,7 +672,6 @@ namespace Avalonia.PropertyStore
/// Adds a new effective value, raises the initial <see cref="AvaloniaObject.PropertyChanged"/>
/// event and notifies inheritance children if necessary .
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="property">The property.</param>
/// <param name="value">The property value.</param>
/// <param name="priority">The value priority.</param>

59
src/Avalonia.Base/Reactive/BindingValueAdapter.cs

@ -1,59 +0,0 @@
using System;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Reactive
{
internal class BindingValueAdapter<T> : SingleSubscriberObservableBase<BindingValue<T>>,
IObserver<T>
{
private readonly IObservable<T> _source;
private IDisposable? _subscription;
public BindingValueAdapter(IObservable<T> source) => _source = source;
public void OnCompleted() => PublishCompleted();
public void OnError(Exception error) => PublishError(error);
public void OnNext(T value) => PublishNext(BindingValue<T>.FromUntyped(value));
protected override void Subscribed() => _subscription = _source.Subscribe(this);
protected override void Unsubscribed() => _subscription?.Dispose();
}
internal class BindingValueSubjectAdapter<T> : SingleSubscriberObservableBase<BindingValue<T>>,
ISubject<BindingValue<T>>
{
private readonly ISubject<T> _source;
private readonly Inner _inner;
private IDisposable? _subscription;
public BindingValueSubjectAdapter(ISubject<T> source)
{
_source = source;
_inner = new Inner(this);
}
public void OnCompleted() => _source.OnCompleted();
public void OnError(Exception error) => _source.OnError(error);
public void OnNext(BindingValue<T> value)
{
if (value.HasValue)
{
_source.OnNext(value.Value);
}
}
protected override void Subscribed() => _subscription = _source.Subscribe(_inner);
protected override void Unsubscribed() => _subscription?.Dispose();
private class Inner : IObserver<T>
{
private readonly BindingValueSubjectAdapter<T> _owner;
public Inner(BindingValueSubjectAdapter<T> owner) => _owner = owner;
public void OnCompleted() => _owner.PublishCompleted();
public void OnError(Exception error) => _owner.PublishError(error);
public void OnNext(T value) => _owner.PublishNext(BindingValue<T>.FromUntyped(value));
}
}
}

33
src/Avalonia.Base/Reactive/BindingValueExtensions.cs

@ -1,33 +0,0 @@
using System;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Reactive
{
public static class BindingValueExtensions
{
public static IObservable<BindingValue<T>> ToBindingValue<T>(this IObservable<T> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new BindingValueAdapter<T>(source);
}
public static ISubject<BindingValue<T>> ToBindingValue<T>(this ISubject<T> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new BindingValueSubjectAdapter<T>(source);
}
public static IObservable<object?> ToUntyped<T>(this IObservable<BindingValue<T>> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new UntypedBindingAdapter<T>(source);
}
public static ISubject<object?> ToUntyped<T>(this ISubject<BindingValue<T>> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new UntypedBindingSubjectAdapter<T>(source);
}
}
}

62
src/Avalonia.Base/Reactive/TypedBindingAdapter.cs

@ -1,62 +0,0 @@
using System;
using Avalonia.Data;
using Avalonia.Logging;
namespace Avalonia.Reactive
{
internal class TypedBindingAdapter<T> : SingleSubscriberObservableBase<BindingValue<T>>,
IObserver<BindingValue<object?>>
{
private readonly IAvaloniaObject _target;
private readonly AvaloniaProperty<T> _property;
private readonly IObservable<BindingValue<object?>> _source;
private IDisposable? _subscription;
public TypedBindingAdapter(
IAvaloniaObject target,
AvaloniaProperty<T> property,
IObservable<BindingValue<object?>> source)
{
_target = target;
_property = property;
_source = source;
}
public void OnNext(BindingValue<object?> value)
{
try
{
PublishNext(value.Convert<T>());
}
catch (InvalidCastException e)
{
var unwrappedValue = value.HasValue ? value.Value : null;
Logger.TryGet(LogEventLevel.Error, LogArea.Binding)?.Log(
_target,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
_property.Name,
_property.PropertyType,
unwrappedValue,
unwrappedValue?.GetType());
PublishNext(BindingValue<T>.BindingError(e));
}
}
public void OnCompleted() => PublishCompleted();
public void OnError(Exception error) => PublishError(error);
public static IObservable<BindingValue<T>> Create(
IAvaloniaObject target,
AvaloniaProperty<T> property,
IObservable<BindingValue<object?>> source)
{
return source is IObservable<BindingValue<T>> result ?
result :
new TypedBindingAdapter<T>(target, property, source);
}
protected override void Subscribed() => _subscription = _source.Subscribe(this);
protected override void Unsubscribed() => _subscription?.Dispose();
}
}

55
src/Avalonia.Base/Reactive/UntypedBindingAdapter.cs

@ -1,55 +0,0 @@
using System;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Reactive
{
internal class UntypedBindingAdapter<T> : SingleSubscriberObservableBase<object?>,
IObserver<BindingValue<T>>
{
private readonly IObservable<BindingValue<T>> _source;
private IDisposable? _subscription;
public UntypedBindingAdapter(IObservable<BindingValue<T>> source) => _source = source;
public void OnCompleted() => PublishCompleted();
public void OnError(Exception error) => PublishError(error);
public void OnNext(BindingValue<T> value) => value.ToUntyped();
protected override void Subscribed() => _subscription = _source.Subscribe(this);
protected override void Unsubscribed() => _subscription?.Dispose();
}
internal class UntypedBindingSubjectAdapter<T> : SingleSubscriberObservableBase<object?>,
ISubject<object?>
{
private readonly ISubject<BindingValue<T>> _source;
private readonly Inner _inner;
private IDisposable? _subscription;
public UntypedBindingSubjectAdapter(ISubject<BindingValue<T>> source)
{
_source = source;
_inner = new Inner(this);
}
public void OnCompleted() => _source.OnCompleted();
public void OnError(Exception error) => _source.OnError(error);
public void OnNext(object? value)
{
_source.OnNext(BindingValue<T>.FromUntyped(value));
}
protected override void Subscribed() => _subscription = _source.Subscribe(_inner);
protected override void Unsubscribed() => _subscription?.Dispose();
private class Inner : IObserver<BindingValue<T>>
{
private readonly UntypedBindingSubjectAdapter<T> _owner;
public Inner(UntypedBindingSubjectAdapter<T> owner) => _owner = owner;
public void OnCompleted() => _owner.PublishCompleted();
public void OnError(Exception error) => _owner.PublishError(error);
public void OnNext(BindingValue<T> value) => _owner.PublishNext(value.ToUntyped());
}
}
}

10
src/Avalonia.Base/StyledPropertyBase.cs

@ -239,16 +239,6 @@ namespace Avalonia
return target.Bind<TValue>(this, source, priority);
}
/// <inheritdoc/>
internal override IDisposable RouteBind(
AvaloniaObject o,
IObservable<BindingValue<object?>> source,
BindingPriority priority)
{
var adapter = TypedBindingAdapter<TValue>.Create(o, this, source);
return o.Bind<TValue>(this, adapter, priority);
}
private object? GetDefaultBoxedValue(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));

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

@ -156,14 +156,6 @@ namespace Avalonia.Base.UnitTests
throw new NotImplementedException();
}
internal override IDisposable RouteBind(
AvaloniaObject o,
IObservable<BindingValue<object>> source,
BindingPriority priority)
{
throw new NotImplementedException();
}
internal override void RouteClearValue(AvaloniaObject o)
{
throw new NotImplementedException();

Loading…
Cancel
Save