// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex { using System; using System.Collections.Generic; using System.Reactive.Subjects; using System.Reflection; /// /// A perspex property. /// /// /// This class is analogous to DependencyProperty in WPF. /// public class PerspexProperty { /// /// Represents an unset property value. /// public static readonly object UnsetValue = new Unset(); /// /// The default values for the property, by type. /// private Dictionary defaultValues = new Dictionary(); /// /// Observable fired when this property changes on any . /// private Subject initialized = new Subject(); /// /// Observable fired when this property changes on any . /// private Subject changed = new Subject(); /// /// The coerce function. /// private Func coerce; /// /// Initializes a new instance of the class. /// /// The name of the property. /// The type of the property's value. /// The type of the class that registers the property. /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. /// A coercion function. public PerspexProperty( string name, Type valueType, Type ownerType, object defaultValue, bool inherits, BindingMode defaultBindingMode, Func coerce) { Contract.Requires(name != null); Contract.Requires(valueType != null); Contract.Requires(ownerType != null); this.Name = name; this.PropertyType = valueType; this.OwnerType = ownerType; this.defaultValues.Add(ownerType, defaultValue); this.Inherits = inherits; this.DefaultBindingMode = defaultBindingMode; this.Coerce = coerce; } /// /// Gets the name of the property. /// public string Name { get; private set; } /// /// Gets the type of the property's value. /// public Type PropertyType { get; private set; } /// /// Gets the type of the class that registers the property. /// public Type OwnerType { get; private set; } /// /// Gets a value indicating whether the property inherits its value. /// public bool Inherits { get; private set; } /// /// Gets the default binding mode for the property. /// /// public BindingMode DefaultBindingMode { get; private set; } /// /// Gets the property's coerce function. /// public Func Coerce { get; private set; } /// /// Gets an observable that is fired when this property is initialized on a /// new instance. /// /// /// This observable is fired each time a new is constructed /// for all properties registered on the object's type. The default value of the property /// for the object is passed in the args' NewValue (OldValue will always be /// . /// public IObservable Initialized { get { return this.initialized; } } /// /// Gets an observable that is fired when this property changes on any /// instance. /// public IObservable Changed { get { return this.changed; } } /// /// Registers a . /// /// The type of the class that is registering the property. /// The type of the property's value. /// The name of the property. /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. /// A coercion function. /// A public static PerspexProperty Register( string name, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func coerce = null) where TOwner : PerspexObject { Contract.Requires(name != null); PerspexProperty result = new PerspexProperty( name, typeof(TOwner), defaultValue, inherits, defaultBindingMode, coerce); PerspexObject.Register(typeof(TOwner), result); return result; } /// /// Registers an attached . /// /// The type of the class that is registering the property. /// The type of the class that the property is to be registered on. /// The type of the property's value. /// The name of the property. /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. /// A coercion function. /// A public static PerspexProperty RegisterAttached( string name, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func coerce = null) where TOwner : PerspexObject { Contract.Requires(name != null); PerspexProperty result = new PerspexProperty( typeof(TOwner) + "." + name, typeof(TOwner), defaultValue, inherits, defaultBindingMode, coerce); PerspexObject.Register(typeof(THost), result); return result; } /// /// Provides access to a property's binding via the /// indexer. /// /// The property. /// A describing the binding. public static Binding operator !(PerspexProperty property) { return new Binding { Priority = BindingPriority.LocalValue, Property = property, }; } /// /// Provides access to a property's template binding via the /// indexer. /// /// The property. /// A describing the binding. public static Binding operator ~(PerspexProperty property) { return new Binding { Priority = BindingPriority.TemplatedParent, Property = property, }; } /// /// Returns a binding accessor that can be passed to 's [] /// operator to initiate a binding. /// /// A . /// /// The ! and ~ operators are short forms of this. /// public Binding Bind() { return new Binding { Property = this, }; } /// /// Gets the default value for the property on the specified type. /// /// The type. /// The default value. public object GetDefaultValue(Type type) { Contract.Requires(type != null); while (type != null) { object result; if (this.defaultValues.TryGetValue(type, out result)) { return result; } type = type.GetTypeInfo().BaseType; } return this.defaultValues[this.OwnerType]; } public bool IsValidValue(object value) { if (value == UnsetValue) { return true; } else if (value == null) { return !this.PropertyType.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(this.PropertyType) != null; } return this.PropertyType.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo()); } /// /// Gets the default value for the property on the specified type. /// /// The type. /// The default value. public void OverrideDefaultValue(Type type, object defaultValue) { Contract.Requires(type != null); // TODO: Ensure correct type. if (this.defaultValues.ContainsKey(type)) { throw new InvalidOperationException("Default value is already set for this property."); } this.defaultValues.Add(type, defaultValue); } public override string ToString() { return this.Name; } internal void NotifyInitialized(PerspexPropertyChangedEventArgs e) { this.initialized.OnNext(e); } internal void NotifyChanged(PerspexPropertyChangedEventArgs e) { this.changed.OnNext(e); } private class Unset { public override string ToString() { return "{Unset}"; } } } /// /// A typed perspex property. /// public class PerspexProperty : PerspexProperty { /// /// Initializes a new instance of the class. /// /// The name of the property. /// The type of the class that registers the property. /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. /// A coercion function. public PerspexProperty( string name, Type ownerType, TValue defaultValue, bool inherits, BindingMode defaultBindingMode, Func coerce) : base( name, typeof(TValue), ownerType, defaultValue, inherits, defaultBindingMode, Convert(coerce)) { Contract.Requires(name != null); Contract.Requires(ownerType != null); } /// /// Registers the property on another type. /// /// The type of the additional owner. /// The property. public PerspexProperty AddOwner() { PerspexObject.Register(typeof(TOwner), this); return this; } /// /// Gets the default value for the property on the specified type. /// /// The type. /// The default value. public TValue GetDefaultValue() { return (TValue)this.GetDefaultValue(typeof(T)); } /// /// Converts from a typed coercion function to an untyped. /// /// The typed coercion function. /// Te untyped coercion function. private static Func Convert(Func f) { return f != null ? (o, v) => f(o, (TValue)v) : (Func)null; } } }