// 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. /// /// /// 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 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)); } /// /// 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); } } } }