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 a . /// /// 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) { Contract.Requires(o != null); Contract.Requires(property != null); return new AvaloniaPropertyObservable(o, property); } /// /// Gets an observable for a . /// /// 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) { Contract.Requires(o != null); Contract.Requires(property != null); return new AvaloniaPropertyObservable(o, property); } /// /// Gets an observable for a . /// /// 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) { Contract.Requires(o != null); Contract.Requires(property != null); return new AvaloniaPropertyBindingObservable(o, property); } /// /// Gets an observable for a . /// /// 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) { Contract.Requires(o != null); Contract.Requires(property != null); return new AvaloniaPropertyBindingObservable(o, 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) { Contract.Requires(o != null); Contract.Requires(property != null); return new AvaloniaPropertyChangedObservable(o, 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 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 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 a 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 property.RouteBind(target, source, priority); } /// /// Binds a 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)); return property switch { StyledPropertyBase styled => target.Bind(styled, source, priority), DirectPropertyBase direct => target.Bind(direct, source), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), }; } /// /// Binds a 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 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; } } /// /// Clears a 's local value. /// /// The object. /// The property. public static void ClearValue(this IAvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); property.RouteClearValue(target); } /// /// Clears a 's local value. /// /// The object. /// The property. public static void ClearValue(this IAvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); switch (property) { case StyledPropertyBase styled: target.ClearValue(styled); break; case DirectPropertyBase direct: target.ClearValue(direct); break; default: throw new NotSupportedException("Unsupported AvaloniaProperty type."); } } /// /// Gets a value. /// /// The object. /// The property. /// The value. public static object GetValue(this IAvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); return property.RouteGetValue(target); } /// /// 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)); return property switch { StyledPropertyBase styled => target.GetValue(styled), DirectPropertyBase direct => target.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; } /// /// 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 . /// 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)); return property.RouteGetBaseValue(target, maxPriority); } /// /// 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 /// . /// 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)); target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); return property switch { StyledPropertyBase styled => target.GetBaseValue(styled, maxPriority), DirectPropertyBase direct => target.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; } /// /// Sets a value. /// /// The object. /// The property. /// The value. /// The priority of the value. /// /// An if setting the property can be undone, otherwise null. /// public static IDisposable SetValue( this IAvaloniaObject target, AvaloniaProperty property, object value, BindingPriority priority = BindingPriority.LocalValue) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); return property.RouteSetValue(target, value, priority); } /// /// Sets a value. /// /// The type of the property. /// The object. /// The property. /// The value. /// The priority of the value. /// /// An if setting the property can be undone, otherwise null. /// public static IDisposable SetValue( this IAvaloniaObject target, AvaloniaProperty property, T value, BindingPriority priority = BindingPriority.LocalValue) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); switch (property) { case StyledPropertyBase styled: return target.SetValue(styled, value, priority); case DirectPropertyBase direct: target.SetValue(direct, value); return null; default: 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(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)); } /// /// Gets a description of a property that van be used in observables. /// /// The object. /// The property /// The description. private static string GetDescription(IAvaloniaObject o, AvaloniaProperty property) { return $"{o.GetType().Name}.{property.Name}"; } /// /// 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); } } } }