| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- // Copyright (c) The Perspex 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 Perspex.Data;
- using Perspex.Reactive;
- namespace Perspex
- {
- /// <summary>
- /// Provides extension methods for <see cref="PerspexObject"/> and related classes.
- /// </summary>
- public static class PerspexObjectExtensions
- {
- /// <summary>
- /// Gets an observable for a <see cref="PerspexProperty"/>.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <param name="property">The property.</param>
- /// <returns>An observable.</returns>
- public static IObservable<object> GetObservable(this IPerspexObject o, PerspexProperty property)
- {
- Contract.Requires<ArgumentNullException>(o != null);
- Contract.Requires<ArgumentNullException>(property != null);
- return new PerspexObservable<object>(
- observer =>
- {
- EventHandler<PerspexPropertyChangedEventArgs> 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="PerspexProperty"/>.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <typeparam name="T">The property type.</typeparam>
- /// <param name="property">The property.</param>
- /// <returns>An observable.</returns>
- public static IObservable<T> GetObservable<T>(this IPerspexObject o, PerspexProperty<T> property)
- {
- Contract.Requires<ArgumentNullException>(o != null);
- Contract.Requires<ArgumentNullException>(property != null);
- return o.GetObservable((PerspexProperty)property).Cast<T>();
- }
- /// <summary>
- /// Gets an observable for a <see cref="PerspexProperty"/>.
- /// </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.
- /// </returns>
- public static IObservable<Tuple<T, T>> GetObservableWithHistory<T>(
- this IPerspexObject o,
- PerspexProperty<T> property)
- {
- Contract.Requires<ArgumentNullException>(o != null);
- Contract.Requires<ArgumentNullException>(property != null);
- return new PerspexObservable<Tuple<T, T>>(
- observer =>
- {
- EventHandler<PerspexPropertyChangedEventArgs> 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="PerspexProperty"/>.
- /// </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 IPerspexObject o,
- PerspexProperty property,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the
- // AnonymousSubject classes and use Subject.Create<T>.
- var output = new Subject<object>();
- var result = new AnonymousSubject<object>(
- Observer.Create<object>(
- x => output.OnNext(x),
- e => output.OnError(e),
- () => output.OnCompleted()),
- o.GetObservable(property));
- o.Bind(property, output, priority);
- return result;
- }
- /// <summary>
- /// Gets a subject for a <see cref="PerspexProperty"/>.
- /// </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 IPerspexObject o,
- PerspexProperty<T> property,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the
- // AnonymousSubject classes from this file and use Subject.Create<T>.
- var output = new Subject<T>();
- var result = new AnonymousSubject<T>(
- Observer.Create<T>(
- x => output.OnNext(x),
- e => output.OnError(e),
- () => output.OnCompleted()),
- o.GetObservable(property));
- o.Bind(property, output, priority);
- return result;
- }
- /// <summary>
- /// Binds a property on an <see cref="IPerspexObject"/> to an <see cref="IBinding"/>.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <param name="property">The property to bind.</param>
- /// <param name="binding">The binding.</param>
- /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
- public static IDisposable Bind(
- this IPerspexObject o,
- PerspexProperty property,
- IBinding binding)
- {
- Contract.Requires<ArgumentNullException>(o != null);
- Contract.Requires<ArgumentNullException>(property != null);
- Contract.Requires<ArgumentNullException>(binding != null);
- var mode = binding.Mode;
- if (mode == BindingMode.Default)
- {
- mode = property.DefaultBindingMode;
- }
- return o.Bind(
- property,
- binding.CreateSubject(o, property),
- mode,
- binding.Priority);
- }
- /// <summary>
- /// Binds a property to a subject according to a <see cref="BindingMode"/>.
- /// </summary>
- /// <param name="o">The object.</param>
- /// <param name="property">The property to bind.</param>
- /// <param name="source">The binding source.</param>
- /// <param name="mode">The binding mode.</param>
- /// <param name="priority">The binding priority.</param>
- /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
- public static IDisposable Bind(
- this IPerspexObject o,
- PerspexProperty property,
- ISubject<object> source,
- BindingMode mode,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- Contract.Requires<ArgumentNullException>(o != null);
- Contract.Requires<ArgumentNullException>(property != null);
- Contract.Requires<ArgumentNullException>(source != null);
- switch (mode)
- {
- case BindingMode.Default:
- case BindingMode.OneWay:
- return o.Bind(property, source, priority);
- case BindingMode.TwoWay:
- return new CompositeDisposable(
- o.Bind(property, source, priority),
- o.GetObservable(property).Subscribe(source));
- case BindingMode.OneTime:
- return source.Take(1).Subscribe(x => o.SetValue(property, x, priority));
- case BindingMode.OneWayToSource:
- return o.GetObservable(property).Subscribe(source);
- default:
- throw new ArgumentException("Invalid binding mode.");
- }
- }
- /// <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<PerspexPropertyChangedEventArgs> observable,
- Action<TTarget, PerspexPropertyChangedEventArgs> action)
- where TTarget : PerspexObject
- {
- 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<PerspexPropertyChangedEventArgs> observable,
- Func<TTarget, Action<PerspexPropertyChangedEventArgs>> 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(IPerspexObject o, PerspexProperty property)
- {
- return $"{o.GetType().Name}.{property.Name}";
- }
- /// <summary>
- /// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{PerspexPropertyChangedEventArgs},
- /// Func{TTarget, Action{PerspexPropertyChangedEventArgs}})"/>.
- /// </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>(
- PerspexPropertyChangedEventArgs e,
- Func<TTarget, Action<PerspexPropertyChangedEventArgs>> handler)
- where TTarget : class
- {
- var target = e.Sender as TTarget;
- if (target != null)
- {
- handler(target)(e);
- }
- }
- }
- }
|