| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- // 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
- {
- /// <summary>
- /// Provides extension methods for <see cref="AvaloniaObject"/> and related classes.
- /// </summary>
- public static class AvaloniaObjectExtensions
- {
- /// <summary>
- /// Converts an <see cref="IObservable{T}"/> to an <see cref="IBinding"/>.
- /// </summary>
- /// <typeparam name="T">The type produced by the observable.</typeparam>
- /// <param name="source">The observable</param>
- /// <returns>An <see cref="IBinding"/>.</returns>
- public static IBinding ToBinding<T>(this IObservable<T> source)
- {
- return new BindingAdaptor(source.Select(x => (object)x));
- }
- /// <summary>
- /// Gets an observable for a <see cref="AvaloniaProperty"/>.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <param name="property">The property.</param>
- /// <returns>
- /// An observable which fires immediately with the current value of the property on the
- /// object and subsequently each time the property value changes.
- /// </returns>
- public static IObservable<object> GetObservable(this IAvaloniaObject o, AvaloniaProperty property)
- {
- Contract.Requires<ArgumentNullException>(o != null);
- Contract.Requires<ArgumentNullException>(property != null);
- return new AvaloniaObservable<object>(
- observer =>
- {
- EventHandler<AvaloniaPropertyChangedEventArgs> 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));
- }
- /// <summary>
- /// Gets an observable for a <see cref="AvaloniaProperty"/>.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <typeparam name="T">The property type.</typeparam>
- /// <param name="property">The property.</param>
- /// <returns>
- /// An observable which fires immediately with the current value of the property on the
- /// object and subsequently each time the property value changes.
- /// </returns>
- public static IObservable<T> GetObservable<T>(this IAvaloniaObject o, AvaloniaProperty<T> property)
- {
- Contract.Requires<ArgumentNullException>(o != null);
- Contract.Requires<ArgumentNullException>(property != null);
- return o.GetObservable((AvaloniaProperty)property).Cast<T>();
- }
- /// <summary>
- /// Gets an observable for a <see cref="AvaloniaProperty"/>.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <typeparam name="T">The type of the property.</typeparam>
- /// <param name="property">The property.</param>
- /// <returns>
- /// 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.
- /// </returns>
- public static IObservable<Tuple<T, T>> GetObservableWithHistory<T>(
- this IAvaloniaObject o,
- AvaloniaProperty<T> property)
- {
- Contract.Requires<ArgumentNullException>(o != null);
- Contract.Requires<ArgumentNullException>(property != null);
- return new AvaloniaObservable<Tuple<T, T>>(
- observer =>
- {
- EventHandler<AvaloniaPropertyChangedEventArgs> 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));
- }
- /// <summary>
- /// Gets a subject for a <see cref="AvaloniaProperty"/>.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <param name="property">The property.</param>
- /// <param name="priority">
- /// The priority with which binding values are written to the object.
- /// </param>
- /// <returns>
- /// An <see cref="ISubject{Object}"/> which can be used for two-way binding to/from the
- /// property.
- /// </returns>
- public static ISubject<object> GetSubject(
- this IAvaloniaObject o,
- AvaloniaProperty property,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- return Subject.Create<object>(
- Observer.Create<object>(x => o.SetValue(property, x, priority)),
- o.GetObservable(property));
- }
- /// <summary>
- /// Gets a subject for a <see cref="AvaloniaProperty"/>.
- /// </summary>
- /// <typeparam name="T">The property type.</typeparam>
- /// <param name="o">The object.</param>
- /// <param name="property">The property.</param>
- /// <param name="priority">
- /// The priority with which binding values are written to the object.
- /// </param>
- /// <returns>
- /// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the
- /// property.
- /// </returns>
- public static ISubject<T> GetSubject<T>(
- this IAvaloniaObject o,
- AvaloniaProperty<T> property,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- return Subject.Create<T>(
- Observer.Create<T>(x => o.SetValue(property, x, priority)),
- o.GetObservable(property));
- }
- /// <summary>
- /// Gets a weak observable for a <see cref="AvaloniaProperty"/>.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <param name="property">The property.</param>
- /// <returns>An observable.</returns>
- public static IObservable<object> GetWeakObservable(this IAvaloniaObject o, AvaloniaProperty property)
- {
- Contract.Requires<ArgumentNullException>(o != null);
- Contract.Requires<ArgumentNullException>(property != null);
- return new WeakPropertyChangedObservable(
- new WeakReference<IAvaloniaObject>(o),
- property,
- GetDescription(o, property));
- }
- /// <summary>
- /// Binds a property on an <see cref="IAvaloniaObject"/> to an <see cref="IBinding"/>.
- /// </summary>
- /// <param name="target">The object.</param>
- /// <param name="property">The property to bind.</param>
- /// <param name="binding">The binding.</param>
- /// <param name="anchor">
- /// 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 <paramref name="anchor"/> parameter
- /// can be used to provice this context.
- /// </param>
- /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
- public static IDisposable Bind(
- this IAvaloniaObject target,
- AvaloniaProperty property,
- IBinding binding,
- object anchor = null)
- {
- Contract.Requires<ArgumentNullException>(target != null);
- Contract.Requires<ArgumentNullException>(property != null);
- Contract.Requires<ArgumentNullException>(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;
- }
- }
- /// <summary>
- /// Subscribes to a property changed notifications for changes that originate from a
- /// <typeparamref name="TTarget"/>.
- /// </summary>
- /// <typeparam name="TTarget">The type of the property change sender.</typeparam>
- /// <param name="observable">The property changed observable.</param>
- /// <param name="action">
- /// The method to call. The parameters are the sender and the event args.
- /// </param>
- /// <returns>A disposable that can be used to terminate the subscription.</returns>
- public static IDisposable AddClassHandler<TTarget>(
- this IObservable<AvaloniaPropertyChangedEventArgs> observable,
- Action<TTarget, AvaloniaPropertyChangedEventArgs> action)
- where TTarget : AvaloniaObject
- {
- return observable.Subscribe(e =>
- {
- if (e.Sender is TTarget)
- {
- action((TTarget)e.Sender, e);
- }
- });
- }
- /// <summary>
- /// Subscribes to a property changed notifications for changes that originate from a
- /// <typeparamref name="TTarget"/>.
- /// </summary>
- /// <typeparam name="TTarget">The type of the property change sender.</typeparam>
- /// <param name="observable">The property changed observable.</param>
- /// <param name="handler">Given a TTarget, returns the handler.</param>
- /// <returns>A disposable that can be used to terminate the subscription.</returns>
- public static IDisposable AddClassHandler<TTarget>(
- this IObservable<AvaloniaPropertyChangedEventArgs> observable,
- Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
- where TTarget : class
- {
- return observable.Subscribe(e => SubscribeAdapter(e, handler));
- }
- /// <summary>
- /// Gets a description of a property that van be used in observables.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <param name="property">The property</param>
- /// <returns>The description.</returns>
- private static string GetDescription(IAvaloniaObject o, AvaloniaProperty property)
- {
- return $"{o.GetType().Name}.{property.Name}";
- }
- /// <summary>
- /// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{AvaloniaPropertyChangedEventArgs},
- /// Func{TTarget, Action{AvaloniaPropertyChangedEventArgs}})"/>.
- /// </summary>
- /// <typeparam name="TTarget">The sender type to accept.</typeparam>
- /// <param name="e">The event args.</param>
- /// <param name="handler">Given a TTarget, returns the handler.</param>
- private static void SubscribeAdapter<TTarget>(
- AvaloniaPropertyChangedEventArgs e,
- Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
- where TTarget : class
- {
- var target = e.Sender as TTarget;
- if (target != null)
- {
- handler(target)(e);
- }
- }
- private class BindingAdaptor : IBinding
- {
- private IObservable<object> _source;
- public BindingAdaptor(IObservable<object> source)
- {
- this._source = source;
- }
- public InstancedBinding Initiate(
- IAvaloniaObject target,
- AvaloniaProperty targetProperty,
- object anchor = null,
- bool enableDataValidation = false)
- {
- return InstancedBinding.OneWay(_source);
- }
- }
- }
- }
|