// ----------------------------------------------------------------------- // // 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; using Perspex.Utilities; /// /// A perspex property. /// /// /// This class is analogous to DependencyProperty in WPF. /// public class PerspexProperty { /// /// Represents an unset property value. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields must be private", Justification = "It's readonly")] 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(); /// /// 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. /// Whether the property is an attached property. public PerspexProperty( string name, Type valueType, Type ownerType, object defaultValue, bool inherits = false, BindingMode defaultBindingMode = BindingMode.Default, Func coerce = null, bool isAttached = false) { 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; this.IsAttached = isAttached; } /// /// Gets the name of the property. /// /// /// The name of the property. /// public string Name { get; } /// /// Gets the type of the property's value. /// /// /// The type of the property's value. /// public Type PropertyType { get; } /// /// Gets the type of the class that registers the property. /// /// /// The type of the class that registers the property. /// public Type OwnerType { get; } /// /// Gets a value indicating whether the property inherits its value. /// /// /// A value indicating whether the property inherits its value. /// public bool Inherits { get; } /// /// Gets the default binding mode for the property. /// /// /// The default binding mode for the property. /// public BindingMode DefaultBindingMode { get; } /// /// Gets the property's coerce function. /// /// /// The property's coerce function. /// public Func Coerce { get; } /// /// Gets a value indicating whether this is an attached property. /// /// /// A value indicating whether this is an attached property. /// public bool IsAttached { get; } /// /// 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 /// . /// /// /// An observable that is fired when this property is initialized on a new /// instance. /// public IObservable Initialized { get { return this.initialized; } } /// /// Gets an observable that is fired when this property changes on any /// instance. /// /// /// An observable that is fired when this property changes on any /// instance. /// public IObservable Changed { get { return this.changed; } } /// /// 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, }; } /// /// 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, false); 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) { Contract.Requires(name != null); PerspexProperty result = new PerspexProperty( name, typeof(TOwner), defaultValue, inherits, defaultBindingMode, coerce, true); PerspexObject.Register(typeof(THost), result); return result; } /// /// Registers an attached . /// /// 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 type of the class that is registering 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, Type ownerType, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func coerce = null) { Contract.Requires(name != null); PerspexProperty result = new PerspexProperty( name, ownerType, defaultValue, inherits, defaultBindingMode, coerce, true); PerspexObject.Register(typeof(THost), result); return result; } /// /// 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]; } /// /// Checks whether the is valid for the property. /// /// The value. /// True if the value is valid, otherwise false. public bool IsValidValue(object value) { return TypeUtilities.TryCast(this.PropertyType, value, out value); } /// /// Overrides the default value for the property on the specified type. /// /// The type. /// The default value. public void OverrideDefaultValue(object defaultValue) { this.OverrideDefaultValue(typeof(T), defaultValue); } /// /// Overrides 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); if (!TypeUtilities.TryCast(this.PropertyType, defaultValue, out defaultValue)) { throw new InvalidOperationException(string.Format( "Invalid value for Property '{0}': {1} ({2})", this.Name, defaultValue, defaultValue.GetType().FullName)); } if (this.defaultValues.ContainsKey(type)) { throw new InvalidOperationException("Default value is already set for this property."); } this.defaultValues.Add(type, defaultValue); } /// /// Gets the string representation of the property. /// /// The property's string representation. public override string ToString() { return this.Name; } /// /// Notifies the observable. /// /// The observable arguments. internal void NotifyInitialized(PerspexPropertyChangedEventArgs e) { this.initialized.OnNext(e); } /// /// Notifies the observable. /// /// The observable arguments. internal void NotifyChanged(PerspexPropertyChangedEventArgs e) { this.changed.OnNext(e); } /// /// Class representing the . /// private class Unset { /// /// Returns the string representation of the . /// /// The string "(unset)". public override string ToString() { return "(unset)"; } } } }