Browse Source

Added conversion option to PropertyObservable

pull/10824/head
Tom Edwards 4 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 System;
using Avalonia.Reactive;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia namespace Avalonia
{ {
@ -34,8 +34,8 @@ namespace Avalonia
/// </remarks> /// </remarks>
public static IObservable<object?> GetObservable(this AvaloniaObject o, AvaloniaProperty property) public static IObservable<object?> GetObservable(this AvaloniaObject o, AvaloniaProperty property)
{ {
return new AvaloniaPropertyObservable<object?>( return new AvaloniaPropertyObservable<object?, object?>(
o ?? throw new ArgumentNullException(nameof(o)), o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property))); property ?? throw new ArgumentNullException(nameof(property)));
} }
@ -54,11 +54,23 @@ namespace Avalonia
/// </remarks> /// </remarks>
public static IObservable<T> GetObservable<T>(this AvaloniaObject o, AvaloniaProperty<T> property) 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)), o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property))); 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> /// <summary>
/// Gets an observable for an <see cref="AvaloniaProperty"/>. /// Gets an observable for an <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
@ -75,7 +87,7 @@ namespace Avalonia
this AvaloniaObject o, this AvaloniaObject o,
AvaloniaProperty property) AvaloniaProperty property)
{ {
return new AvaloniaPropertyBindingObservable<object?>( return new AvaloniaPropertyBindingObservable<object?, object?>(
o ?? throw new ArgumentNullException(nameof(o)), o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property))); property ?? throw new ArgumentNullException(nameof(property)));
} }
@ -97,12 +109,27 @@ namespace Avalonia
this AvaloniaObject o, this AvaloniaObject o,
AvaloniaProperty<T> property) AvaloniaProperty<T> property)
{ {
return new AvaloniaPropertyBindingObservable<T>( return new AvaloniaPropertyBindingObservable<T, T>(
o ?? throw new ArgumentNullException(nameof(o)), o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property))); 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> /// <summary>
/// Gets an observable that listens for property changed events for an /// Gets an observable that listens for property changed events for an
/// <see cref="AvaloniaProperty"/>. /// <see cref="AvaloniaProperty"/>.
@ -338,7 +365,7 @@ namespace Avalonia
return InstancedBinding.OneWay(_source); return InstancedBinding.OneWay(_source);
} }
} }
private class ClassHandlerObserver<TTarget, TValue> : IObserver<AvaloniaPropertyChangedEventArgs<TValue>> private class ClassHandlerObserver<TTarget, TValue> : IObserver<AvaloniaPropertyChangedEventArgs<TValue>>
{ {
private readonly Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> _action; 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) 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 else
{ {

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

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