using System; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Reactive; 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 IAvaloniaObject 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 IAvaloniaObject 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 IAvaloniaObject 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 IAvaloniaObject 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 IAvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyChangedObservable( o ?? throw new ArgumentNullException(nameof(o)), property ?? throw new ArgumentNullException(nameof(property))); } /// /// Gets a subject for an . /// /// The object. /// The property. /// /// The priority with which binding values are written to the object. /// /// /// An which can be used for two-way binding to/from the /// property. /// public static ISubject GetSubject( this IAvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { return Subject.Create( Observer.Create(x => o.SetValue(property, x, priority)), o.GetObservable(property)); } /// /// Gets a subject for an . /// /// The property type. /// The object. /// The property. /// /// The priority with which binding values are written to the object. /// /// /// An which can be used for two-way binding to/from the /// property. /// public static ISubject GetSubject( this IAvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { return Subject.Create( Observer.Create(x => o.SetValue(property, x, priority)), o.GetObservable(property)); } /// /// Gets a subject for a . /// /// The object. /// The property. /// /// The priority with which binding values are written to the object. /// /// /// An which can be used for two-way binding to/from the /// property. /// public static ISubject> GetBindingSubject( this IAvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { return Subject.Create>( Observer.Create>(x => { if (x.HasValue) { o.SetValue(property, x.Value, priority); } }), o.GetBindingObservable(property)); } /// /// Gets a subject for a . /// /// The property type. /// The object. /// The property. /// /// The priority with which binding values are written to the object. /// /// /// An which can be used for two-way binding to/from the /// property. /// public static ISubject> GetBindingSubject( this IAvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { return Subject.Create>( Observer.Create>(x => { if (x.HasValue) { o.SetValue(property, x.Value, priority); } }), o.GetBindingObservable(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 IAvaloniaObject 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)); if (target is AvaloniaObject ao) { return property switch { StyledPropertyBase styled => ao.Bind(styled, source, priority), DirectPropertyBase direct => ao.Bind(direct, source), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), }; } throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); } /// /// 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 IAvaloniaObject 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 target.Bind( property, source.ToBindingValue(), priority); } /// /// 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 IAvaloniaObject 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 metadata = property.GetMetadata(target.GetType()) as IDirectPropertyMetadata; var result = binding.Initiate( target, property, anchor, metadata?.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 IAvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); if (target is AvaloniaObject ao) { return property switch { StyledPropertyBase styled => ao.GetValue(styled), DirectPropertyBase direct => ao.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; } throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); } /// /// Gets an base value. /// /// The object. /// The property. /// The maximum priority for the value. /// /// For styled properties, gets the value of the property if set on the object with a /// priority equal or lower to , 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 IAvaloniaObject target, AvaloniaProperty property, BindingPriority maxPriority) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); if (target is AvaloniaObject ao) return property.RouteGetBaseValue(ao, maxPriority); throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); } /// /// Gets an base value. /// /// The object. /// The property. /// The maximum priority for the value. /// /// For styled properties, gets the value of the property if set on the object with a /// priority equal or lower to , 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 IAvaloniaObject target, AvaloniaProperty property, BindingPriority maxPriority) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); if (target is AvaloniaObject ao) { return property switch { StyledPropertyBase styled => ao.GetBaseValue(styled, maxPriority), DirectPropertyBase direct => ao.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; } throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); } /// /// 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(e => { if (e.Sender is TTarget target) { action(target, e); } }); } /// /// 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(e => { if (e.Sender is TTarget target) { action(target, e); } }); } /// /// Subscribes to a property changed notifications for changes that originate from a /// . /// /// The type of the property change sender. /// The property changed observable. /// Given a TTarget, returns the handler. /// A disposable that can be used to terminate the subscription. [Obsolete("Use overload taking Action.")] public static IDisposable AddClassHandler( this IObservable observable, Func> handler) where TTarget : class { return observable.Subscribe(e => SubscribeAdapter(e, handler)); } /// /// Observer method for . /// /// The sender type to accept. /// The event args. /// Given a TTarget, returns the handler. private static void SubscribeAdapter( AvaloniaPropertyChangedEventArgs e, Func> handler) where TTarget : class { if (e.Sender is TTarget target) { handler(target)(e); } } private class BindingAdaptor : IBinding { private IObservable _source; public BindingAdaptor(IObservable source) { this._source = source; } public InstancedBinding? Initiate( IAvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor = null, bool enableDataValidation = false) { return InstancedBinding.OneWay(_source); } } } }