using System; using Avalonia.Reactive; using Avalonia.Data; namespace Avalonia { /// /// Provides extension methods for and related classes. /// public static class AvaloniaObjectExtensions { /// /// Converts an to an . /// /// The type produced by the observable. /// The observable /// An . public static IBinding ToBinding(this IObservable source) { return new BindingAdaptor(source.Select(x => (object?)x)); } /// /// Gets an observable for an . /// /// The object. /// The property. /// /// An observable which fires immediately with the current value of the property on the /// object and subsequently each time the property value changes. /// /// /// The subscription to is created using a weak reference. /// public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyObservable( o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } /// /// Gets an observable for an . /// /// The object. /// The property type. /// The property. /// /// An observable which fires immediately with the current value of the property on the /// object and subsequently each time the property value changes. /// /// /// The subscription to is created using a weak reference. /// public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyObservable( o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } /// /// Gets an observable for an . /// /// The object. /// The property. /// /// An observable which fires immediately with the current value of the property on the /// object and subsequently each time the property value changes. /// /// /// The subscription to is created using a weak reference. /// public static IObservable> GetBindingObservable( this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyBindingObservable( o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } /// /// Gets an observable for an . /// /// The object. /// The property type. /// The property. /// /// An observable which fires immediately with the current value of the property on the /// object and subsequently each time the property value changes. /// /// /// The subscription to is created using a weak reference. /// public static IObservable> GetBindingObservable( this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyBindingObservable( o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } /// /// Gets an observable that listens for property changed events for an /// . /// /// The object. /// The property. /// /// An observable which when subscribed pushes the property changed event args /// each time a event is raised /// for the specified property. /// public static IObservable GetPropertyChangedObservable( this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyChangedObservable( o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } /// /// Binds an to an observable. /// /// The type of the property. /// The object. /// The property. /// The observable. /// The priority of the binding. /// /// A disposable which can be used to terminate the binding. /// public static IDisposable Bind( this AvaloniaObject target, AvaloniaProperty property, IObservable> 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)); return property switch { StyledProperty styled => target.Bind(styled, source, priority), DirectPropertyBase direct => target.Bind(direct, source), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), }; } /// /// Binds an to an observable. /// /// The object. /// The property. /// The observable. /// The priority of the binding. /// /// A disposable which can be used to terminate the binding. /// public static IDisposable Bind( this AvaloniaObject target, AvaloniaProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) { return property switch { StyledProperty styled => target.Bind(styled, source, priority), DirectPropertyBase direct => target.Bind(direct, source), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), }; } /// /// Binds a property on an to an . /// /// The object. /// The property to bind. /// The binding. /// /// An optional anchor from which to locate required context. When binding to objects that /// are not in the logical tree, certain types of binding need an anchor into the tree in /// order to locate named controls or resources. The parameter /// can be used to provice this context. /// /// An which can be used to cancel the binding. public static IDisposable Bind( this AvaloniaObject target, AvaloniaProperty property, IBinding binding, object? anchor = null) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); binding = binding ?? throw new ArgumentNullException(nameof(binding)); var result = binding.Initiate( target, property, anchor, property.GetMetadata(target.GetType()).EnableDataValidation ?? false); if (result != null) { return BindingOperations.Apply(target, property, result, anchor); } else { return Disposable.Empty; } } /// /// Gets a value. /// /// The type of the property. /// The object. /// The property. /// The value. public static T GetValue(this AvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); return property switch { StyledProperty styled => target.GetValue(styled), DirectPropertyBase direct => target.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; } /// /// Gets an base value. /// /// The object. /// The property. /// /// For styled properties, gets the value of the property excluding animated values, otherwise /// . Note that this method does not return /// property values that come from inherited or default values. /// /// For direct properties returns the current value of the property. /// public static object? GetBaseValue( this AvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); return property.RouteGetBaseValue(target); } /// /// Gets an base value. /// /// The object. /// The property. /// /// For styled properties, gets the value of the property excluding animated values, otherwise /// . Note that this method does not return property values /// that come from inherited or default values. /// /// For direct properties returns the current value of the property. /// public static Optional GetBaseValue( this AvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); return property switch { StyledProperty styled => target.GetBaseValue(styled), DirectPropertyBase direct => target.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; } /// /// Subscribes to a property changed notifications for changes that originate from a /// . /// /// The type of the property change sender. /// The property changed observable. /// /// The method to call. The parameters are the sender and the event args. /// /// A disposable that can be used to terminate the subscription. public static IDisposable AddClassHandler( this IObservable observable, Action action) where TTarget : AvaloniaObject { return observable.Subscribe(new ClassHandlerObserver(action)); } /// /// Subscribes to a property changed notifications for changes that originate from a /// . /// /// The type of the property change sender. /// /// The type of the property.. /// The property changed observable. /// /// The method to call. The parameters are the sender and the event args. /// /// A disposable that can be used to terminate the subscription. public static IDisposable AddClassHandler( this IObservable> observable, Action> action) where TTarget : AvaloniaObject { return observable.Subscribe(new ClassHandlerObserver(action)); } private class BindingAdaptor : IBinding { private IObservable _source; public BindingAdaptor(IObservable source) { this._source = source; } public InstancedBinding? Initiate( AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor = null, bool enableDataValidation = false) { return InstancedBinding.OneWay(_source); } } private class ClassHandlerObserver : IObserver> { private readonly Action> _action; public ClassHandlerObserver(Action> action) { _action = action; } public void OnCompleted() { } public void OnError(Exception error) { } public void OnNext(AvaloniaPropertyChangedEventArgs value) { if (value.Sender is TTarget target) { _action(target, value); } } } private class ClassHandlerObserver : IObserver { private readonly Action _action; public ClassHandlerObserver(Action action) { _action = action; } public void OnCompleted() { } public void OnError(Exception error) { } public void OnNext(AvaloniaPropertyChangedEventArgs value) { if (value.Sender is TTarget target) { _action(target, value); } } } } }