| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- // 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.Collections.Generic;
- using System.Reactive.Subjects;
- using System.Reflection;
- using Perspex.Utilities;
- namespace Perspex
- {
- /// <summary>
- /// A perspex property.
- /// </summary>
- /// <remarks>
- /// This class is analogous to DependencyProperty in WPF.
- /// </remarks>
- public class PerspexProperty : IEquatable<PerspexProperty>
- {
- /// <summary>
- /// Represents an unset property value.
- /// </summary>
- public static readonly object UnsetValue = new Unset();
- /// <summary>
- /// Gets the next ID that will be allocated to a property.
- /// </summary>
- private static int s_nextId = 1;
- /// <summary>
- /// The default values for the property, by type.
- /// </summary>
- private readonly Dictionary<Type, object> _defaultValues = new Dictionary<Type, object>();
- /// <summary>
- /// Observable fired when this property changes on any <see cref="PerspexObject"/>.
- /// </summary>
- private readonly Subject<PerspexPropertyChangedEventArgs> _initialized = new Subject<PerspexPropertyChangedEventArgs>();
- /// <summary>
- /// Observable fired when this property changes on any <see cref="PerspexObject"/>.
- /// </summary>
- private readonly Subject<PerspexPropertyChangedEventArgs> _changed = new Subject<PerspexPropertyChangedEventArgs>();
- /// <summary>
- /// The validation functions for the property, by type.
- /// </summary>
- private readonly Dictionary<Type, Func<PerspexObject, object, object>> _validation =
- new Dictionary<Type, Func<PerspexObject, object, object>>();
- /// <summary>
- /// Gets the ID of the property.
- /// </summary>
- private int _id;
- /// <summary>
- /// Initializes a new instance of the <see cref="PerspexProperty"/> class.
- /// </summary>
- /// <param name="name">The name of the property.</param>
- /// <param name="valueType">The type of the property's value.</param>
- /// <param name="ownerType">The type of the class that registers the property.</param>
- /// <param name="defaultValue">The default value of the property.</param>
- /// <param name="inherits">Whether the property inherits its value.</param>
- /// <param name="defaultBindingMode">The default binding mode for the property.</param>
- /// <param name="validate">A validation function.</param>
- /// <param name="isAttached">Whether the property is an attached property.</param>
- public PerspexProperty(
- string name,
- Type valueType,
- Type ownerType,
- object defaultValue,
- bool inherits = false,
- BindingMode defaultBindingMode = BindingMode.Default,
- Func<PerspexObject, object, object> validate = null,
- bool isAttached = false)
- {
- Contract.Requires<ArgumentNullException>(name != null);
- Contract.Requires<ArgumentNullException>(valueType != null);
- Contract.Requires<ArgumentNullException>(ownerType != null);
- if (name.Contains("."))
- {
- throw new ArgumentException("'name' may not contain periods.");
- }
- Name = name;
- PropertyType = valueType;
- OwnerType = ownerType;
- _defaultValues.Add(ownerType, defaultValue);
- Inherits = inherits;
- DefaultBindingMode = defaultBindingMode;
- IsAttached = isAttached;
- _id = s_nextId++;
- if (validate != null)
- {
- _validation.Add(ownerType, validate);
- }
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="PerspexProperty"/> class.
- /// </summary>
- /// <param name="name">The name of the property.</param>
- /// <param name="valueType">The type of the property's value.</param>
- /// <param name="ownerType">The type of the class that registers the property.</param>
- /// <param name="getter">Gets the current value of the property.</param>
- /// <param name="setter">Sets the value of the property.</param>
- public PerspexProperty(
- string name,
- Type valueType,
- Type ownerType,
- Func<PerspexObject, object> getter,
- Action<PerspexObject, object> setter)
- {
- Contract.Requires<ArgumentNullException>(name != null);
- Contract.Requires<ArgumentNullException>(valueType != null);
- Contract.Requires<ArgumentNullException>(ownerType != null);
- Contract.Requires<ArgumentNullException>(getter != null);
- if (name.Contains("."))
- {
- throw new ArgumentException("'name' may not contain periods.");
- }
- Name = name;
- PropertyType = valueType;
- OwnerType = ownerType;
- Getter = getter;
- Setter = setter;
- IsDirect = true;
- _id = s_nextId++;
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="PerspexProperty"/> class.
- /// </summary>
- /// <param name="source">The direct property to copy.</param>
- /// <param name="getter">A new getter.</param>
- /// <param name="setter">A new setter.</param>
- protected PerspexProperty(
- PerspexProperty source,
- Func<PerspexObject, object> getter,
- Action<PerspexObject, object> setter)
- {
- Contract.Requires<ArgumentNullException>(source != null);
- Contract.Requires<ArgumentNullException>(getter != null);
- if (!source.IsDirect)
- {
- throw new InvalidOperationException(
- "This method can only be called on direct PerspexProperties.");
- }
- Name = source.Name;
- PropertyType = source.PropertyType;
- OwnerType = source.OwnerType;
- Getter = getter;
- Setter = setter;
- IsDirect = true;
- _id = source._id;
- }
- /// <summary>
- /// Gets the name of the property.
- /// </summary>
- /// <value>
- /// The name of the property.
- /// </value>
- public string Name { get; }
- /// <summary>
- /// Gets the type of the property's value.
- /// </summary>
- /// <value>
- /// The type of the property's value.
- /// </value>
- public Type PropertyType { get; }
- /// <summary>
- /// Gets the type of the class that registers the property.
- /// </summary>
- /// <value>
- /// The type of the class that registers the property.
- /// </value>
- public Type OwnerType { get; }
- /// <summary>
- /// Gets a value indicating whether the property inherits its value.
- /// </summary>
- /// <value>
- /// A value indicating whether the property inherits its value.
- /// </value>
- public bool Inherits { get; }
- /// <summary>
- /// Gets the default binding mode for the property.
- /// </summary>
- /// <value>
- /// The default binding mode for the property.
- /// </value>
- public BindingMode DefaultBindingMode { get; }
- /// <summary>
- /// Gets a value indicating whether this is an attached property.
- /// </summary>
- /// <value>
- /// A value indicating whether this is an attached property.
- /// </value>
- public bool IsAttached { get; }
- /// <summary>
- /// Gets a value indicating whether this is a direct property.
- /// </summary>
- public bool IsDirect { get; }
- /// <summary>
- /// Gets an observable that is fired when this property is initialized on a
- /// new <see cref="PerspexObject"/> instance.
- /// </summary>
- /// <remarks>
- /// This observable is fired each time a new <see cref="PerspexObject"/> 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
- /// <see cref="UnsetValue"/>.
- /// </remarks>
- /// <value>
- /// An observable that is fired when this property is initialized on a new
- /// <see cref="PerspexObject"/> instance.
- /// </value>
- public IObservable<PerspexPropertyChangedEventArgs> Initialized => _initialized;
- /// <summary>
- /// Gets an observable that is fired when this property changes on any
- /// <see cref="PerspexObject"/> instance.
- /// </summary>
- /// <value>
- /// An observable that is fired when this property changes on any
- /// <see cref="PerspexObject"/> instance.
- /// </value>
- public IObservable<PerspexPropertyChangedEventArgs> Changed => _changed;
- /// <summary>
- /// Provides access to a property's binding via the <see cref="PerspexObject"/>
- /// indexer.
- /// </summary>
- /// <param name="property">The property.</param>
- /// <returns>A <see cref="BindingDescriptor"/> describing the binding.</returns>
- public static BindingDescriptor operator !(PerspexProperty property)
- {
- return new BindingDescriptor
- {
- Priority = BindingPriority.LocalValue,
- Property = property,
- };
- }
- /// <summary>
- /// Provides access to a property's template binding via the <see cref="PerspexObject"/>
- /// indexer.
- /// </summary>
- /// <param name="property">The property.</param>
- /// <returns>A <see cref="BindingDescriptor"/> describing the binding.</returns>
- public static BindingDescriptor operator ~(PerspexProperty property)
- {
- return new BindingDescriptor
- {
- Priority = BindingPriority.TemplatedParent,
- Property = property,
- };
- }
- /// <summary>
- /// Gets the getter function for direct properties.
- /// </summary>
- internal Func<PerspexObject, object> Getter { get; }
- /// <summary>
- /// Gets the etter function for direct properties.
- /// </summary>
- internal Action<PerspexObject, object> Setter { get; }
- /// <summary>
- /// Tests two <see cref="PerspexProperty"/>s for equality.
- /// </summary>
- /// <param name="a">The first property.</param>
- /// <param name="b">The second property.</param>
- /// <returns>True if the properties are equal, otherwise false.</returns>
- public static bool operator ==(PerspexProperty a, PerspexProperty b)
- {
- if (object.ReferenceEquals(a, b))
- {
- return true;
- }
- else if (((object)a == null) || ((object)b == null))
- {
- return false;
- }
- else
- {
- return a.Equals(b);
- }
- }
- /// <summary>
- /// Tests two <see cref="PerspexProperty"/>s for unequality.
- /// </summary>
- /// <param name="a">The first property.</param>
- /// <param name="b">The second property.</param>
- /// <returns>True if the properties are equal, otherwise false.</returns>
- public static bool operator !=(PerspexProperty a, PerspexProperty b)
- {
- return !(a == b);
- }
- /// <summary>
- /// Registers a <see cref="PerspexProperty"/>.
- /// </summary>
- /// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam>
- /// <typeparam name="TValue">The type of the property's value.</typeparam>
- /// <param name="name">The name of the property.</param>
- /// <param name="defaultValue">The default value of the property.</param>
- /// <param name="inherits">Whether the property inherits its value.</param>
- /// <param name="defaultBindingMode">The default binding mode for the property.</param>
- /// <param name="validate">A validation function.</param>
- /// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
- public static PerspexProperty<TValue> Register<TOwner, TValue>(
- string name,
- TValue defaultValue = default(TValue),
- bool inherits = false,
- BindingMode defaultBindingMode = BindingMode.OneWay,
- Func<TOwner, TValue, TValue> validate = null)
- where TOwner : PerspexObject
- {
- Contract.Requires<ArgumentNullException>(name != null);
- PerspexProperty<TValue> result = new PerspexProperty<TValue>(
- name,
- typeof(TOwner),
- defaultValue,
- inherits,
- defaultBindingMode,
- Cast(validate),
- false);
- PerspexObject.Register(typeof(TOwner), result);
- return result;
- }
- /// <summary>
- /// Registers a direct <see cref="PerspexProperty"/>.
- /// </summary>
- /// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam>
- /// <typeparam name="TValue">The type of the property's value.</typeparam>
- /// <param name="name">The name of the property.</param>
- /// <param name="getter">Gets the current value of the property.</param>
- /// <param name="setter">Sets the value of the property.</param>
- /// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
- public static PerspexProperty<TValue> RegisterDirect<TOwner, TValue>(
- string name,
- Func<TOwner, TValue> getter,
- Action<TOwner, TValue> setter = null)
- where TOwner : PerspexObject
- {
- Contract.Requires<ArgumentNullException>(name != null);
- PerspexProperty<TValue> result = new PerspexProperty<TValue>(
- name,
- typeof(TOwner),
- Cast(getter),
- Cast(setter));
- PerspexObject.Register(typeof(TOwner), result);
- return result;
- }
- /// <summary>
- /// Registers an attached <see cref="PerspexProperty"/>.
- /// </summary>
- /// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam>
- /// <typeparam name="THost">The type of the class that the property is to be registered on.</typeparam>
- /// <typeparam name="TValue">The type of the property's value.</typeparam>
- /// <param name="name">The name of the property.</param>
- /// <param name="defaultValue">The default value of the property.</param>
- /// <param name="inherits">Whether the property inherits its value.</param>
- /// <param name="defaultBindingMode">The default binding mode for the property.</param>
- /// <param name="validate">A validation function.</param>
- /// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
- public static PerspexProperty<TValue> RegisterAttached<TOwner, THost, TValue>(
- string name,
- TValue defaultValue = default(TValue),
- bool inherits = false,
- BindingMode defaultBindingMode = BindingMode.OneWay,
- Func<PerspexObject, TValue, TValue> validate = null)
- {
- Contract.Requires<ArgumentNullException>(name != null);
- PerspexProperty<TValue> result = new PerspexProperty<TValue>(
- name,
- typeof(TOwner),
- defaultValue,
- inherits,
- defaultBindingMode,
- validate,
- true);
- PerspexObject.Register(typeof(THost), result);
- return result;
- }
- /// <summary>
- /// Registers an attached <see cref="PerspexProperty"/>.
- /// </summary>
- /// <typeparam name="THost">The type of the class that the property is to be registered on.</typeparam>
- /// <typeparam name="TValue">The type of the property's value.</typeparam>
- /// <param name="name">The name of the property.</param>
- /// <param name="ownerType">The type of the class that is registering the property.</param>
- /// <param name="defaultValue">The default value of the property.</param>
- /// <param name="inherits">Whether the property inherits its value.</param>
- /// <param name="defaultBindingMode">The default binding mode for the property.</param>
- /// <param name="validate">A validation function.</param>
- /// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
- public static PerspexProperty<TValue> RegisterAttached<THost, TValue>(
- string name,
- Type ownerType,
- TValue defaultValue = default(TValue),
- bool inherits = false,
- BindingMode defaultBindingMode = BindingMode.OneWay,
- Func<PerspexObject, TValue, TValue> validate = null)
- {
- Contract.Requires<ArgumentNullException>(name != null);
- PerspexProperty<TValue> result = new PerspexProperty<TValue>(
- name,
- ownerType,
- defaultValue,
- inherits,
- defaultBindingMode,
- validate,
- true);
- PerspexObject.Register(typeof(THost), result);
- return result;
- }
- /// <inheritdoc/>
- public override bool Equals(object obj)
- {
- var p = obj as PerspexProperty;
- return p != null ? Equals(p) : false;
- }
- /// <inheritdoc/>
- public bool Equals(PerspexProperty other)
- {
- return other != null && _id == other._id;
- }
- /// <inheritdoc/>
- public override int GetHashCode()
- {
- return _id;
- }
- /// <summary>
- /// Returns a binding accessor that can be passed to <see cref="PerspexObject"/>'s []
- /// operator to initiate a binding.
- /// </summary>
- /// <returns>A <see cref="BindingDescriptor"/>.</returns>
- /// <remarks>
- /// The ! and ~ operators are short forms of this.
- /// </remarks>
- public BindingDescriptor Bind()
- {
- return new BindingDescriptor
- {
- Property = this,
- };
- }
- /// <summary>
- /// Gets the default value for the property on the specified type.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <returns>The default value.</returns>
- public object GetDefaultValue(Type type)
- {
- Contract.Requires<ArgumentNullException>(type != null);
- while (type != null)
- {
- object result;
- if (_defaultValues.TryGetValue(type, out result))
- {
- return result;
- }
- type = type.GetTypeInfo().BaseType;
- }
- return _defaultValues[OwnerType];
- }
- /// <summary>
- /// Gets the validation function for the property on the specified type.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <returns>
- /// The validation function, or null if no validation function registered for this type.
- /// </returns>
- public Func<PerspexObject, object, object> GetValidationFunc(Type type)
- {
- Contract.Requires<ArgumentNullException>(type != null);
- while (type != null)
- {
- Func<PerspexObject, object, object> result;
- if (_validation.TryGetValue(type, out result))
- {
- return result;
- }
- type = type.GetTypeInfo().BaseType;
- }
- return null;
- }
- /// <summary>
- /// Checks whether the <paramref name="value"/> is valid for the property.
- /// </summary>
- /// <param name="value">The value.</param>
- /// <returns>True if the value is valid, otherwise false.</returns>
- public bool IsValidValue(object value)
- {
- return TypeUtilities.TryCast(PropertyType, value, out value);
- }
- /// <summary>
- /// Overrides the default value for the property on the specified type.
- /// </summary>
- /// <typeparam name="T">The type.</typeparam>
- /// <param name="defaultValue">The default value.</param>
- public void OverrideDefaultValue<T>(object defaultValue)
- {
- OverrideDefaultValue(typeof(T), defaultValue);
- }
- /// <summary>
- /// Overrides the default value for the property on the specified type.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="defaultValue">The default value.</param>
- public void OverrideDefaultValue(Type type, object defaultValue)
- {
- Contract.Requires<ArgumentNullException>(type != null);
- if (!TypeUtilities.TryCast(PropertyType, defaultValue, out defaultValue))
- {
- throw new InvalidOperationException(string.Format(
- "Invalid value for Property '{0}': {1} ({2})",
- Name,
- defaultValue,
- defaultValue.GetType().FullName));
- }
- if (_defaultValues.ContainsKey(type))
- {
- throw new InvalidOperationException("Default value is already set for this property.");
- }
- _defaultValues.Add(type, defaultValue);
- }
- /// <summary>
- /// Overrides the validation function for the property on the specified type.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="validation">The validation function.</param>
- public void OverrideValidation(Type type, Func<PerspexObject, object, object> validation)
- {
- Contract.Requires<ArgumentNullException>(type != null);
- if (_validation.ContainsKey(type))
- {
- throw new InvalidOperationException("Validation is already set for this property.");
- }
- _validation.Add(type, validation);
- }
- /// <summary>
- /// Gets the string representation of the property.
- /// </summary>
- /// <returns>The property's string representation.</returns>
- public override string ToString()
- {
- return Name;
- }
- /// <summary>
- /// Notifies the <see cref="Initialized"/> observable.
- /// </summary>
- /// <param name="e">The observable arguments.</param>
- internal void NotifyInitialized(PerspexPropertyChangedEventArgs e)
- {
- _initialized.OnNext(e);
- }
- /// <summary>
- /// Notifies the <see cref="Changed"/> observable.
- /// </summary>
- /// <param name="e">The observable arguments.</param>
- internal void NotifyChanged(PerspexPropertyChangedEventArgs e)
- {
- _changed.OnNext(e);
- }
- /// <summary>
- /// Casts a getter function accepting a typed owner to one accepting a
- /// <see cref="PerspexObject"/>.
- /// </summary>
- /// <typeparam name="TOwner">The owner type.</typeparam>
- /// <typeparam name="TValue">The property value type.</typeparam>
- /// <param name="f">The typed function.</param>
- /// <returns>The untyped function.</returns>
- private static Func<PerspexObject, TValue> Cast<TOwner, TValue>(Func<TOwner, TValue> f)
- where TOwner : PerspexObject
- {
- return (f != null) ? o => f((TOwner)o) : (Func<PerspexObject, TValue >)null;
- }
- /// <summary>
- /// Casts a setter action accepting a typed owner to one accepting a
- /// <see cref="PerspexObject"/>.
- /// </summary>
- /// <typeparam name="TOwner">The owner type.</typeparam>
- /// <typeparam name="TValue">The property value type.</typeparam>
- /// <param name="f">The typed action.</param>
- /// <returns>The untyped action.</returns>
- private static Action<PerspexObject, TValue> Cast<TOwner, TValue>(Action<TOwner, TValue> f)
- where TOwner : PerspexObject
- {
- return f != null ? (o, v) => f((TOwner)o, v) : (Action<PerspexObject, TValue>)null;
- }
- /// <summary>
- /// Casts a validation function accepting a typed owner to one accepting a
- /// <see cref="PerspexObject"/>.
- /// </summary>
- /// <typeparam name="TOwner">The owner type.</typeparam>
- /// <typeparam name="TValue">The property value type.</typeparam>
- /// <param name="f">The typed function.</param>
- /// <returns>The untyped function.</returns>
- private static Func<PerspexObject, TValue, TValue> Cast<TOwner, TValue>(Func<TOwner, TValue, TValue> f)
- where TOwner : PerspexObject
- {
- return f != null ? (o, v) => f((TOwner)o, v) : (Func<PerspexObject, TValue, TValue>)null;
- }
- /// <summary>
- /// Class representing the <see cref="UnsetValue"/>.
- /// </summary>
- private class Unset
- {
- /// <summary>
- /// Returns the string representation of the <see cref="UnsetValue"/>.
- /// </summary>
- /// <returns>The string "(unset)".</returns>
- public override string ToString()
- {
- return "(unset)";
- }
- }
- }
- }
|