// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. 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. /// public static IObservable GetObservable(this IAvaloniaObject o, AvaloniaProperty property) { Contract.Requires(o != null); Contract.Requires(property != null); return new AvaloniaObservable( observer => { EventHandler handler = (s, e) => { if (e.Property == property) { observer.OnNext(e.NewValue); } }; observer.OnNext(o.GetValue(property)); o.PropertyChanged += handler; return Disposable.Create(() => { o.PropertyChanged -= handler; }); }, GetDescription(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. /// public static IObservable GetObservable(this IAvaloniaObject o, AvaloniaProperty property) { Contract.Requires(o != null); Contract.Requires(property != null); return o.GetObservable((AvaloniaProperty)property).Cast(); } /// /// Gets an observable for a . /// /// The object. /// The type of the property. /// The property. /// /// An observable which when subscribed pushes the old and new values of the property each /// time it is changed. Note that the observable returned from this method does not fire /// with the current value of the property immediately. /// public static IObservable> GetObservableWithHistory( this IAvaloniaObject o, AvaloniaProperty property) { Contract.Requires(o != null); Contract.Requires(property != null); return new AvaloniaObservable>( observer => { EventHandler handler = (s, e) => { if (e.Property == property) { observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue)); } }; o.PropertyChanged += handler; return Disposable.Create(() => { o.PropertyChanged -= handler; }); }, GetDescription(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 weak observable for a . /// /// The object. /// The property. /// An observable. public static IObservable GetWeakObservable(this IAvaloniaObject o, AvaloniaProperty property) { Contract.Requires(o != null); Contract.Requires(property != null); return new WeakPropertyChangedObservable( new WeakReference(o), property, GetDescription(o, property)); } /// /// 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) { Contract.Requires(target != null); Contract.Requires(property != null); Contract.Requires(binding != null); 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; } } /// /// 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) { action((TTarget)e.Sender, 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. 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 { var target = e.Sender as TTarget; if (target != null) { 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); } } } }