From 055f8940123e0d704e457f379eb5400a5484f95c Mon Sep 17 00:00:00 2001 From: Tom Edwards Date: Thu, 16 Mar 2023 21:38:03 +0100 Subject: [PATCH] Added conversion option to PropertyObservable --- src/Avalonia.Base/AvaloniaObjectExtensions.cs | 41 +++++++-- .../Data/Core/AvaloniaPropertyAccessorNode.cs | 2 +- .../AvaloniaPropertyBindingObservable.cs | 85 +++++++++++++++---- .../Reactive/AvaloniaPropertyObservable.cs | 73 +++++++++++++--- 4 files changed, 162 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 9fbf680a5c..0c22213d33 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/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 /// public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property) { - return new AvaloniaPropertyObservable( - o ?? throw new ArgumentNullException(nameof(o)), + return new AvaloniaPropertyObservable( + o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } @@ -54,11 +54,23 @@ namespace Avalonia /// public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property) { - return new AvaloniaPropertyObservable( + return new AvaloniaPropertyObservable( o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } + /// + /// + /// + /// A method which is executed to convert each property value to . + public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property, Func converter) + { + return new AvaloniaPropertyObservable( + o ?? throw new ArgumentNullException(nameof(o)), + property ?? throw new ArgumentNullException(nameof(property)), + converter ?? throw new ArgumentNullException(nameof(converter))); + } + /// /// Gets an observable for an . /// @@ -75,7 +87,7 @@ namespace Avalonia this AvaloniaObject o, AvaloniaProperty property) { - return new AvaloniaPropertyBindingObservable( + return new AvaloniaPropertyBindingObservable( o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } @@ -97,12 +109,27 @@ namespace Avalonia this AvaloniaObject o, AvaloniaProperty property) { - return new AvaloniaPropertyBindingObservable( + return new AvaloniaPropertyBindingObservable( o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } + /// + /// + /// + /// A method which is executed to convert each property value to . + public static IObservable> GetBindingObservable( + this AvaloniaObject o, + AvaloniaProperty property, + Func converter) + { + return new AvaloniaPropertyBindingObservable( + o ?? throw new ArgumentNullException(nameof(o)), + property ?? throw new ArgumentNullException(nameof(property)), + converter ?? throw new ArgumentNullException(nameof(converter))); + } + /// /// Gets an observable that listens for property changed events for an /// . @@ -338,7 +365,7 @@ namespace Avalonia return InstancedBinding.OneWay(_source); } } - + private class ClassHandlerObserver : IObserver> { private readonly Action> _action; diff --git a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs index 92fc843394..4bf24e901e 100644 --- a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs +++ b/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(obj, _property).Subscribe(ValueChanged); + _subscription = new AvaloniaPropertyObservable(obj, _property).Subscribe(ValueChanged); } else { diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs index 0789684eff..fd68381d55 100644 --- a/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs +++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs @@ -4,18 +4,21 @@ using Avalonia.Data; namespace Avalonia.Reactive { - internal class AvaloniaPropertyBindingObservable : LightweightObservableBase>, IDescription + internal class AvaloniaPropertyBindingObservable : LightweightObservableBase>, IDescription { private readonly WeakReference _target; private readonly AvaloniaProperty _property; - private BindingValue _value = BindingValue.Unset; + private readonly Func? _converter; + private BindingValue _value = BindingValue.Unset; public AvaloniaPropertyBindingObservable( AvaloniaObject target, - AvaloniaProperty property) + AvaloniaProperty property, + Func? converter = null) { _target = new WeakReference(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> observer, bool first) + protected override void Subscribed(IObserver> observer, bool first) { if (_value.Type != BindingValueType.UnsetValue) { @@ -49,27 +68,59 @@ namespace Avalonia.Reactive { if (e.Property == _property) { - if (e is AvaloniaPropertyChangedEventArgs typedArgs) + if (e is AvaloniaPropertyChangedEventArgs typedArgs) { - var newValue = e.Sender.GetValue(typedArgs.Property); + PublishValue(e.Sender.GetValue(typedArgs.Property)); + } + else + { + PublishUntypedValue(e.Sender.GetValue(e.Property)); + } + } + } - if (!_value.HasValue || !EqualityComparer.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 typedArgs) + { + var newValueRaw = e.Sender.GetValue(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.Default.Equals(newValue, _value.Value)) + { + _value = newValue; + PublishNext(_value); + } + } + + private void PublishUntypedValue(object? newValue) + { + if (!Equals(newValue, _value)) + { + _value = (TResult)newValue!; + PublishNext(_value); + } + } } } diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs index a4fa587a50..0d40fa96e6 100644 --- a/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs +++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs @@ -4,18 +4,21 @@ using Avalonia.Data; namespace Avalonia.Reactive { - internal class AvaloniaPropertyObservable : LightweightObservableBase, IDescription + internal class AvaloniaPropertyObservable : LightweightObservableBase, IDescription { private readonly WeakReference _target; private readonly AvaloniaProperty _property; - private Optional _value; + private readonly Func? _converter; + private Optional _value; public AvaloniaPropertyObservable( AvaloniaObject target, - AvaloniaProperty property) + AvaloniaProperty property, + Func? converter = null) { _target = new WeakReference(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 observer, bool first) + protected override void Subscribed(IObserver 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 typed) + if (e is AvaloniaPropertyChangedEventArgs 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.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 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.Default.Equals(newValue, _value.Value)) + { + _value = newValue; + PublishNext(_value.Value!); } } }