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