Browse Source

Added conversion option to PropertyObservable

pull/10824/head
Tom Edwards 3 years ago
parent
commit
055f894012
  1. 41
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  2. 2
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  3. 85
      src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
  4. 73
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

41
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -1,6 +1,6 @@
using System;
using Avalonia.Reactive;
using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia
{
@ -34,8 +34,8 @@ namespace Avalonia
/// </remarks>
public static IObservable<object?> GetObservable(this AvaloniaObject o, AvaloniaProperty property)
{
return new AvaloniaPropertyObservable<object?>(
o ?? throw new ArgumentNullException(nameof(o)),
return new AvaloniaPropertyObservable<object?, object?>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)));
}
@ -54,11 +54,23 @@ namespace Avalonia
/// </remarks>
public static IObservable<T> GetObservable<T>(this AvaloniaObject o, AvaloniaProperty<T> property)
{
return new AvaloniaPropertyObservable<T>(
return new AvaloniaPropertyObservable<T, T>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)));
}
/// <inheritdoc cref="GetObservable{T}(AvaloniaObject, AvaloniaProperty{T})"/>
/// <param name="o"/>
/// <param name="property"/>
/// <param name="converter">A method which is executed to convert each property value to <typeparamref name="TResult"/>.</param>
public static IObservable<TResult> GetObservable<TSource, TResult>(this AvaloniaObject o, AvaloniaProperty<TSource> property, Func<TSource, TResult> converter)
{
return new AvaloniaPropertyObservable<TSource, TResult>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)),
converter ?? throw new ArgumentNullException(nameof(converter)));
}
/// <summary>
/// Gets an observable for an <see cref="AvaloniaProperty"/>.
/// </summary>
@ -75,7 +87,7 @@ namespace Avalonia
this AvaloniaObject o,
AvaloniaProperty property)
{
return new AvaloniaPropertyBindingObservable<object?>(
return new AvaloniaPropertyBindingObservable<object?, object?>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)));
}
@ -97,12 +109,27 @@ namespace Avalonia
this AvaloniaObject o,
AvaloniaProperty<T> property)
{
return new AvaloniaPropertyBindingObservable<T>(
return new AvaloniaPropertyBindingObservable<T, T>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)));
}
/// <inheritdoc cref="GetBindingObservable{T}(AvaloniaObject, AvaloniaProperty{T})"/>
/// <param name="o"/>
/// <param name="property"/>
/// <param name="converter">A method which is executed to convert each property value to <typeparamref name="TResult"/>.</param>
public static IObservable<BindingValue<TResult>> GetBindingObservable<TSource, TResult>(
this AvaloniaObject o,
AvaloniaProperty<TSource> property,
Func<TSource, TResult> converter)
{
return new AvaloniaPropertyBindingObservable<TSource, TResult>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)),
converter ?? throw new ArgumentNullException(nameof(converter)));
}
/// <summary>
/// Gets an observable that listens for property changed events for an
/// <see cref="AvaloniaProperty"/>.
@ -338,7 +365,7 @@ namespace Avalonia
return InstancedBinding.OneWay(_source);
}
}
private class ClassHandlerObserver<TTarget, TValue> : IObserver<AvaloniaPropertyChangedEventArgs<TValue>>
{
private readonly Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> _action;

2
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

@ -40,7 +40,7 @@ namespace Avalonia.Data.Core
{
if (reference.TryGetTarget(out var target) && target is AvaloniaObject obj)
{
_subscription = new AvaloniaPropertyObservable<object?>(obj, _property).Subscribe(ValueChanged);
_subscription = new AvaloniaPropertyObservable<object?,object?>(obj, _property).Subscribe(ValueChanged);
}
else
{

85
src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs

@ -4,18 +4,21 @@ using Avalonia.Data;
namespace Avalonia.Reactive
{
internal class AvaloniaPropertyBindingObservable<T> : LightweightObservableBase<BindingValue<T>>, IDescription
internal class AvaloniaPropertyBindingObservable<TSource,TResult> : LightweightObservableBase<BindingValue<TResult>>, IDescription
{
private readonly WeakReference<AvaloniaObject> _target;
private readonly AvaloniaProperty _property;
private BindingValue<T> _value = BindingValue<T>.Unset;
private readonly Func<TSource, TResult>? _converter;
private BindingValue<TResult> _value = BindingValue<TResult>.Unset;
public AvaloniaPropertyBindingObservable(
AvaloniaObject target,
AvaloniaProperty property)
AvaloniaProperty property,
Func<TSource, TResult>? converter = null)
{
_target = new WeakReference<AvaloniaObject>(target);
_property = property;
_converter = converter;
}
public string Description => $"{_target.GetType().Name}.{_property.Name}";
@ -24,8 +27,17 @@ namespace Avalonia.Reactive
{
if (_target.TryGetTarget(out var target))
{
_value = (T)target.GetValue(_property)!;
target.PropertyChanged += PropertyChanged;
if (_converter is { } converter)
{
var unconvertedValue = (TSource)target.GetValue(_property)!;
_value = converter(unconvertedValue);
target.PropertyChanged += PropertyChanged_WithConversion;
}
else
{
_value = (TResult)target.GetValue(_property)!;
target.PropertyChanged += PropertyChanged;
}
}
}
@ -33,11 +45,18 @@ namespace Avalonia.Reactive
{
if (_target.TryGetTarget(out var target))
{
target.PropertyChanged -= PropertyChanged;
if (_converter is not null)
{
target.PropertyChanged -= PropertyChanged_WithConversion;
}
else
{
target.PropertyChanged -= PropertyChanged;
}
}
}
protected override void Subscribed(IObserver<BindingValue<T>> observer, bool first)
protected override void Subscribed(IObserver<BindingValue<TResult>> observer, bool first)
{
if (_value.Type != BindingValueType.UnsetValue)
{
@ -49,27 +68,59 @@ namespace Avalonia.Reactive
{
if (e.Property == _property)
{
if (e is AvaloniaPropertyChangedEventArgs<T> typedArgs)
if (e is AvaloniaPropertyChangedEventArgs<TResult> typedArgs)
{
var newValue = e.Sender.GetValue<T>(typedArgs.Property);
PublishValue(e.Sender.GetValue<TResult>(typedArgs.Property));
}
else
{
PublishUntypedValue(e.Sender.GetValue(e.Property));
}
}
}
if (!_value.HasValue || !EqualityComparer<T>.Default.Equals(newValue, _value.Value))
{
_value = newValue;
PublishNext(_value);
}
private void PropertyChanged_WithConversion(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _property)
{
if (e is AvaloniaPropertyChangedEventArgs<TSource> typedArgs)
{
var newValueRaw = e.Sender.GetValue<TSource>(typedArgs.Property);
var newValue = _converter!(newValueRaw);
PublishValue(newValue);
}
else
{
var newValue = e.Sender.GetValue(e.Property);
if (!Equals(newValue, _value))
if (newValue is TSource source)
{
_value = (T)newValue!;
PublishNext(_value);
newValue = _converter!(source);
}
PublishUntypedValue(newValue);
}
}
}
private void PublishValue(TResult newValue)
{
if (!_value.HasValue || !EqualityComparer<TResult>.Default.Equals(newValue, _value.Value))
{
_value = newValue;
PublishNext(_value);
}
}
private void PublishUntypedValue(object? newValue)
{
if (!Equals(newValue, _value))
{
_value = (TResult)newValue!;
PublishNext(_value);
}
}
}
}

73
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@ -4,18 +4,21 @@ using Avalonia.Data;
namespace Avalonia.Reactive
{
internal class AvaloniaPropertyObservable<T> : LightweightObservableBase<T>, IDescription
internal class AvaloniaPropertyObservable<TSource,TResult> : LightweightObservableBase<TResult>, IDescription
{
private readonly WeakReference<AvaloniaObject> _target;
private readonly AvaloniaProperty _property;
private Optional<T> _value;
private readonly Func<TSource, TResult>? _converter;
private Optional<TResult> _value;
public AvaloniaPropertyObservable(
AvaloniaObject target,
AvaloniaProperty property)
AvaloniaProperty property,
Func<TSource,TResult>? converter = null)
{
_target = new WeakReference<AvaloniaObject>(target);
_property = property;
_converter = converter;
}
public string Description => $"{_target.GetType().Name}.{_property.Name}";
@ -24,8 +27,17 @@ namespace Avalonia.Reactive
{
if (_target.TryGetTarget(out var target))
{
_value = (T)target.GetValue(_property)!;
target.PropertyChanged += PropertyChanged;
if (_converter is { } converter)
{
var unconvertedValue = (TSource)target.GetValue(_property)!;
_value = converter(unconvertedValue);
target.PropertyChanged += PropertyChanged_WithConversion;
}
else
{
_value = (TResult)target.GetValue(_property)!;
target.PropertyChanged += PropertyChanged;
}
}
}
@ -33,13 +45,20 @@ namespace Avalonia.Reactive
{
if (_target.TryGetTarget(out var target))
{
target.PropertyChanged -= PropertyChanged;
if (_converter is not null)
{
target.PropertyChanged -= PropertyChanged_WithConversion;
}
else
{
target.PropertyChanged -= PropertyChanged;
}
}
_value = default;
}
protected override void Subscribed(IObserver<T> observer, bool first)
protected override void Subscribed(IObserver<TResult> observer, bool first)
{
if (_value.HasValue)
observer.OnNext(_value.Value);
@ -49,23 +68,49 @@ namespace Avalonia.Reactive
{
if (e.Property == _property)
{
T newValue;
TResult newValue;
if (e is AvaloniaPropertyChangedEventArgs<T> typed)
if (e is AvaloniaPropertyChangedEventArgs<TResult> typed)
{
newValue = AvaloniaObjectExtensions.GetValue(e.Sender, typed.Property);
}
else
{
newValue = (T)e.Sender.GetValue(e.Property)!;
newValue = (TResult)e.Sender.GetValue(e.Property)!;
}
if (!_value.HasValue ||
!EqualityComparer<T>.Default.Equals(newValue, _value.Value))
PublishNewValue(newValue);
}
}
private void PropertyChanged_WithConversion(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _property)
{
TSource newValueRaw;
if (e is AvaloniaPropertyChangedEventArgs<TSource> typed)
{
_value = newValue;
PublishNext(_value.Value!);
newValueRaw = AvaloniaObjectExtensions.GetValue(e.Sender, typed.Property);
}
else
{
newValueRaw = (TSource)e.Sender.GetValue(e.Property)!;
}
var newValue = _converter!(newValueRaw);
PublishNewValue(newValue);
}
}
private void PublishNewValue(TResult newValue)
{
if (!_value.HasValue ||
!EqualityComparer<TResult>.Default.Equals(newValue, _value.Value))
{
_value = newValue;
PublishNext(_value.Value!);
}
}
}

Loading…
Cancel
Save