diff --git a/src/Perspex.Base/AttachedProperty.cs b/src/Perspex.Base/AttachedProperty.cs new file mode 100644 index 0000000000..b713f61a07 --- /dev/null +++ b/src/Perspex.Base/AttachedProperty.cs @@ -0,0 +1,41 @@ +// 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 Perspex.Data; + +namespace Perspex +{ + /// + /// An attached perspex property. + /// + /// The type of the property's value. + public class AttachedProperty : StyledPropertyBase + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// 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 validation function. + public AttachedProperty( + string name, + Type ownerType, + TValue defaultValue = default(TValue), + bool inherits = false, + BindingMode defaultBindingMode = BindingMode.Default, + Func validate = null) + : base(name, ownerType, defaultValue, inherits, defaultBindingMode, validate) + { + } + + /// + public override string FullName => OwnerType + "." + Name; + + /// + public override bool IsAttached => true; + } +} diff --git a/src/Perspex.Base/DirectProperty.cs b/src/Perspex.Base/DirectProperty.cs new file mode 100644 index 0000000000..d36eb675cf --- /dev/null +++ b/src/Perspex.Base/DirectProperty.cs @@ -0,0 +1,109 @@ +// 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; + +namespace Perspex +{ + /// + /// A direct perspex property. + /// + /// The class that registered the property. + /// The type of the property's value. + /// + /// Direct perspex properties are backed by a field on the object, but exposed via the + /// system. They hold a getter and an optional setter which + /// allows the perspex property system to read and write the current value. + /// + public class DirectProperty : PerspexProperty, IDirectPropertyAccessor + where TOwner : IPerspexObject + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// Gets the current value of the property. + /// Sets the value of the property. May be null. + public DirectProperty( + string name, + Func getter, + Action setter = null) + : base(name, typeof(TOwner)) + { + Contract.Requires(getter != null); + + Getter = getter; + Setter = setter; + } + + /// + /// Initializes a new instance of the class. + /// + /// The property to copy. + /// Gets the current value of the property. + /// Sets the value of the property. May be null. + private DirectProperty( + PerspexProperty source, + Func getter, + Action setter) + : base(source, typeof(TOwner)) + { + Contract.Requires(getter != null); + + Getter = getter; + Setter = setter; + } + + /// + public override bool IsDirect => true; + + /// + public override bool IsReadOnly => Setter == null; + + /// + /// Gets the getter function. + /// + public Func Getter { get; } + + /// + /// Gets the setter function. + /// + public Action Setter { get; } + + /// + /// Registers the direct property on another type. + /// + /// The type of the additional owner. + /// The property. + public DirectProperty AddOwner( + Func getter, + Action setter = null) + where TNewOwner : PerspexObject + { + var result = new DirectProperty( + this, + getter, + setter); + + PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); + return result; + } + + /// + object IDirectPropertyAccessor.GetValue(IPerspexObject instance) + { + return Getter((TOwner)instance); + } + + /// + void IDirectPropertyAccessor.SetValue(IPerspexObject instance, object value) + { + if (Setter == null) + { + throw new ArgumentException($"The property {Name} is readonly."); + } + + Setter((TOwner)instance, (TValue)value); + } + } +} diff --git a/src/Perspex.Base/IDirectPropertyAccessor.cs b/src/Perspex.Base/IDirectPropertyAccessor.cs new file mode 100644 index 0000000000..497be4f122 --- /dev/null +++ b/src/Perspex.Base/IDirectPropertyAccessor.cs @@ -0,0 +1,31 @@ +// 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. + +namespace Perspex +{ + /// + /// Provides a runtime interface for getting and setting + /// values. + /// + internal interface IDirectPropertyAccessor + { + /// + /// Gets a value indicating whether the property is read-only. + /// + bool IsReadOnly { get; } + + /// + /// Gets the value of the property on the instance. + /// + /// The instance. + /// The property value. + object GetValue(IPerspexObject instance); + + /// + /// Sets the value of the property on the instance. + /// + /// The instance. + /// The value. + void SetValue(IPerspexObject instance, object value); + } +} diff --git a/src/Perspex.Base/IStyledPropertyAccessor.cs b/src/Perspex.Base/IStyledPropertyAccessor.cs new file mode 100644 index 0000000000..c8207332a4 --- /dev/null +++ b/src/Perspex.Base/IStyledPropertyAccessor.cs @@ -0,0 +1,31 @@ +// 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; + +namespace Perspex +{ + /// + /// Provides a runtime interface for interfacing with . + /// + internal interface IStyledPropertyAccessor + { + /// + /// Gets the default value for the property for the specified type. + /// + /// The type. + /// + /// The default value. + /// + object GetDefaultValue(Type type); + + /// + /// Gets a validation function for the property on the specified type. + /// + /// The type. + /// + /// The validation function, or null if no validation function exists. + /// + Func GetValidationFunc(Type type); + } +} diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 3a61fbc996..8b44506c15 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -48,12 +48,18 @@ + + + + + + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 5310651514..e550c96302 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -57,21 +57,21 @@ namespace Perspex new PropertyEnricher("Id", GetHashCode()), }); - foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(this)) - { - object value = property.IsDirect ? - property.Getter(this) : - property.GetDefaultValue(GetType()); - - var e = new PerspexPropertyChangedEventArgs( - this, - property, - PerspexProperty.UnsetValue, - value, - BindingPriority.Unset); - - property.NotifyInitialized(e); - } + ////foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(this)) + ////{ + //// object value = property.IsDirect ? + //// ((IDirectPropertyAccessor)property).GetValue(this) : + //// property.GetDefaultValue(GetType()); + + //// var e = new PerspexPropertyChangedEventArgs( + //// this, + //// property, + //// PerspexProperty.UnsetValue, + //// value, + //// BindingPriority.Unset); + + //// property.NotifyInitialized(e); + ////} } /// @@ -229,7 +229,7 @@ namespace Perspex if (property.IsDirect) { - return GetRegistered(property).Getter(this); + return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this); } else { @@ -267,7 +267,7 @@ namespace Perspex if (property.IsDirect) { - return ((PerspexProperty)GetRegistered(property)).Getter(this); + return (T)((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this); } else { @@ -310,15 +310,9 @@ namespace Perspex if (property.IsDirect) { - property = GetRegistered(property); - - if (property.Setter == null) - { - throw new ArgumentException($"The property {property.Name} is readonly."); - } - + var accessor = (IDirectPropertyAccessor)GetRegistered(property); LogPropertySet(property, value, priority); - property.Setter(this, UnsetToDefault(value, property)); + accessor.SetValue(this, UnsetToDefault(value, property)); } else { @@ -368,23 +362,8 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - VerifyAccess(); - if (property.IsDirect) - { - property = (PerspexProperty)GetRegistered(property); - - if (property.Setter == null) - { - throw new ArgumentException($"The property {property.Name} is readonly."); - } - LogPropertySet(property, value, priority); - property.Setter(this, value); - } - else - { - SetValue((PerspexProperty)property, value, priority); - } + SetValue((PerspexProperty)property, value, priority); } /// @@ -406,9 +385,7 @@ namespace Perspex if (property.IsDirect) { - property = GetRegistered(property); - - if (property.Setter == null) + if (property.IsReadOnly) { throw new ArgumentException($"The property {property.Name} is readonly."); } @@ -463,22 +440,8 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - VerifyAccess(); - if (property.IsDirect) - { - property = (PerspexProperty)GetRegistered(property); - if (property.Setter == null) - { - throw new ArgumentException($"The property {property.Name} is readonly."); - } - - return source.Subscribe(x => SetValue(property, x)); - } - else - { - return Bind((PerspexProperty)property, source.Select(x => (object)x), priority); - } + return Bind((PerspexProperty)property, source.Select(x => (object)x), priority); } /// @@ -621,7 +584,7 @@ namespace Perspex /// The . private PriorityValue CreatePriorityValue(PerspexProperty property) { - Func validate = property.GetValidationFunc(GetType()); + var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType()); Func validate2 = null; if (validate != null) @@ -673,7 +636,7 @@ namespace Perspex } else { - return property.GetDefaultValue(GetType()); + return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType()); } } diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs index 27c80ca323..a2752ec9c2 100644 --- a/src/Perspex.Base/PerspexProperty.cs +++ b/src/Perspex.Base/PerspexProperty.cs @@ -2,21 +2,15 @@ // 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.Linq; using System.Reactive.Subjects; -using System.Reflection; using Perspex.Data; using Perspex.Utilities; namespace Perspex { /// - /// A perspex property. + /// Base class for perspex property metadata. /// - /// - /// This class is analogous to DependencyProperty in WPF. - /// public class PerspexProperty : IEquatable { /// @@ -29,16 +23,6 @@ namespace Perspex /// private static int s_nextId = 1; - /// - /// The default value provided when the property was first registered. - /// - private readonly object _defaultValue; - - /// - /// The overridden default values for the property, by type. - /// - private readonly Dictionary _defaultValues; - /// /// Observable fired when this property changes on any . /// @@ -49,11 +33,6 @@ namespace Perspex /// private readonly Subject _changed; - /// - /// The validation functions for the property, by type. - /// - private readonly Dictionary> _validation; - /// /// Gets the ID of the property. /// @@ -65,26 +44,18 @@ namespace Perspex /// 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 validation function. /// /// A method that gets called before and after the property starts being notified on an /// object; the bool argument will be true before and false afterwards. This callback is /// intended to support IsDataContextChanging. /// - /// Whether the property is an attached property. - public PerspexProperty( + protected PerspexProperty( string name, Type valueType, Type ownerType, - object defaultValue, - bool inherits = false, BindingMode defaultBindingMode = BindingMode.Default, - Func validate = null, - Action notifying = null, - bool isAttached = false) + Action notifying = null) { Contract.Requires(name != null); Contract.Requires(valueType != null); @@ -95,64 +66,15 @@ namespace Perspex throw new ArgumentException("'name' may not contain periods."); } - _defaultValues = new Dictionary(); _initialized = new Subject(); _changed = new Subject(); - _validation = new Dictionary>(); Name = name; PropertyType = valueType; OwnerType = ownerType; - _defaultValue = defaultValue; - Inherits = inherits; DefaultBindingMode = defaultBindingMode; - IsAttached = isAttached; Notifying = notifying; _id = s_nextId++; - - if (validate != null) - { - _validation.Add(ownerType, validate); - } - } - - /// - /// 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. - /// Gets the current value of the property. - /// Sets the value of the property. - public PerspexProperty( - string name, - Type valueType, - Type ownerType, - Func getter, - Action setter) - { - Contract.Requires(name != null); - Contract.Requires(valueType != null); - Contract.Requires(ownerType != null); - Contract.Requires(getter != null); - - if (name.Contains(".")) - { - throw new ArgumentException("'name' may not contain periods."); - } - - _defaultValues = new Dictionary(); - _initialized = new Subject(); - _changed = new Subject(); - _validation = new Dictionary>(); - - Name = name; - PropertyType = valueType; - OwnerType = ownerType; - Getter = getter; - Setter = setter; - IsDirect = true; - _id = s_nextId++; } /// @@ -171,117 +93,62 @@ namespace Perspex "This method cannot be called on direct PerspexProperties."); } - _defaultValues = source._defaultValues; _initialized = source._initialized; _changed = source._changed; - _validation = source._validation; Name = source.Name; PropertyType = source.PropertyType; OwnerType = ownerType; - _defaultValue = source._defaultValue; - Inherits = source.Inherits; DefaultBindingMode = source.DefaultBindingMode; - IsAttached = false; Notifying = Notifying; - _validation = source._validation; _id = source._id; } /// - /// Initializes a new instance of the class. + /// Gets the name of the property. /// - /// The direct property to copy. - /// The new owner type. - /// A new getter. - /// A new setter. - protected PerspexProperty( - PerspexProperty source, - Type ownerType, - Func getter, - Action setter) - { - Contract.Requires(source != null); - Contract.Requires(ownerType != null); - Contract.Requires(getter != null); - - if (!source.IsDirect) - { - throw new InvalidOperationException( - "This method can only be called on direct PerspexProperties."); - } - - _defaultValues = source._defaultValues; - _initialized = source._initialized; - _changed = source._changed; - _validation = source._validation; - - Name = source.Name; - PropertyType = source.PropertyType; - OwnerType = ownerType; - Getter = getter; - Setter = setter; - IsDirect = true; - _id = source._id; - } + public string Name { get; } /// - /// Gets the name of the property. + /// Gets the full name of the property, wich includes the owner type in the case of + /// attached properties. /// - /// - /// The name of the property. - /// - public string Name { get; } + public virtual string FullName => Name; /// /// 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. + /// Gets the type of the class that registered 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; } + public virtual bool Inherits => false; /// /// Gets the default binding mode for the property. /// - /// - /// The default binding mode for the property. - /// public BindingMode DefaultBindingMode { get; } /// /// Gets a value indicating whether this is an attached property. /// - /// - /// A value indicating whether this is an attached property. - /// - public bool IsAttached { get; } + public virtual bool IsAttached => false; /// /// Gets a value indicating whether this is a direct property. /// - public bool IsDirect { get; } + public virtual bool IsDirect => false; /// /// Gets a value indicating whether this is a readonly property. /// - public bool IsReadOnly => IsDirect && Setter == null; + public virtual bool IsReadOnly => false; /// /// Gets an observable that is fired when this property is initialized on a @@ -349,16 +216,6 @@ namespace Perspex }; } - /// - /// Gets the getter function for direct properties. - /// - internal Func Getter { get; } - - /// - /// Gets the etter function for direct properties. - /// - internal Action Setter { get; } - /// /// Tests two s for equality. /// @@ -407,55 +264,26 @@ namespace Perspex /// object; the bool argument will be true before and false afterwards. This callback is /// intended to support IsDataContextChanging. /// - /// A - public static PerspexProperty Register( + /// A + public static StyledProperty Register( string name, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func validate = null, - Action notifying = null) - where TOwner : PerspexObject + Action notifying = null) + where TOwner : IPerspexObject { Contract.Requires(name != null); - PerspexProperty result = new PerspexProperty( + var result = new StyledProperty( name, typeof(TOwner), defaultValue, inherits, defaultBindingMode, Cast(validate), - notifying, - false); - - PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); - - return result; - } - - /// - /// Registers a direct . - /// - /// The type of the class that is registering the property. - /// The type of the property's value. - /// The name of the property. - /// Gets the current value of the property. - /// Sets the value of the property. - /// A - public static PerspexProperty RegisterDirect( - string name, - Func getter, - Action setter = null) - where TOwner : PerspexObject - { - Contract.Requires(name != null); - - PerspexProperty result = new PerspexProperty( - name, - typeof(TOwner), - Cast(getter), - Cast(setter)); + notifying); PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); @@ -474,24 +302,23 @@ namespace Perspex /// The default binding mode for the property. /// A validation function. /// A - public static PerspexProperty RegisterAttached( + public static AttachedProperty RegisterAttached( string name, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, - Func validate = null) + Func validate = null) + where THost : IPerspexObject { Contract.Requires(name != null); - PerspexProperty result = new PerspexProperty( + var result = new AttachedProperty( name, typeof(TOwner), defaultValue, inherits, defaultBindingMode, - validate, - null, - true); + Cast(validate)); PerspexPropertyRegistry.Instance.Register(typeof(THost), result); @@ -516,25 +343,46 @@ namespace Perspex TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, - Func validate = null) + Func validate = null) + where THost : IPerspexObject { Contract.Requires(name != null); - PerspexProperty result = new PerspexProperty( + var result = new AttachedProperty( name, ownerType, defaultValue, inherits, defaultBindingMode, - validate, - null, - true); + Cast(validate)); PerspexPropertyRegistry.Instance.Register(typeof(THost), result); return result; } + /// + /// Registers a direct . + /// + /// The type of the class that is registering the property. + /// The type of the property's value. + /// The name of the property. + /// Gets the current value of the property. + /// Sets the value of the property. + /// A + public static DirectProperty RegisterDirect( + string name, + Func getter, + Action setter = null) + where TOwner : IPerspexObject + { + Contract.Requires(name != null); + + var result = new DirectProperty(name, getter, setter); + PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); + return result; + } + /// public override bool Equals(object obj) { @@ -570,56 +418,6 @@ namespace Perspex }; } - /// - /// 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 (_defaultValues.TryGetValue(type, out result)) - { - return result; - } - - type = type.GetTypeInfo().BaseType; - } - - return _defaultValue; - } - - /// - /// Gets the validation function for the property on the specified type. - /// - /// The type. - /// - /// The validation function, or null if no validation function registered for this type. - /// - public Func GetValidationFunc(Type type) - { - Contract.Requires(type != null); - - while (type != null) - { - Func result; - - if (_validation.TryGetValue(type, out result)) - { - return result; - } - - type = type.GetTypeInfo().BaseType; - } - - return null; - } - /// /// Checks whether the is valid for the property. /// @@ -630,59 +428,6 @@ namespace Perspex return TypeUtilities.TryCast(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) - { - 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(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); - } - - /// - /// Overrides the validation function for the property on the specified type. - /// - /// The type. - /// The validation function. - public void OverrideValidation(Type type, Func validation) - { - Contract.Requires(type != null); - - if (_validation.ContainsKey(type)) - { - throw new InvalidOperationException("Validation is already set for this property."); - } - - _validation.Add(type, validation); - } - /// /// Gets the string representation of the property. /// @@ -711,45 +456,24 @@ namespace Perspex } /// - /// Casts a getter function accepting a typed owner to one accepting a - /// . + /// Casts a validation function accepting a typed owner to one accepting an + /// . /// /// The owner type. /// The property value type. /// The typed function. /// The untyped function. - private static Func Cast(Func f) - where TOwner : PerspexObject + protected static Func Cast(Func f) + where TOwner : IPerspexObject { - return (f != null) ? o => f((TOwner)o) : (Func)null; - } - - /// - /// Casts a setter action accepting a typed owner to one accepting a - /// . - /// - /// The owner type. - /// The property value type. - /// The typed action. - /// The untyped action. - private static Action Cast(Action f) - where TOwner : PerspexObject - { - return f != null ? (o, v) => f((TOwner)o, v) : (Action)null; - } - - /// - /// Casts a validation function accepting a typed owner to one accepting a - /// . - /// - /// The owner type. - /// The property value type. - /// The typed function. - /// The untyped function. - private static Func Cast(Func f) - where TOwner : PerspexObject - { - return f != null ? (o, v) => f((TOwner)o, v) : (Func)null; + if (f == null) + { + return null; + } + else + { + return (o, v) => f((TOwner)o, v); + } } /// @@ -761,10 +485,7 @@ namespace Perspex /// Returns the string representation of the . /// /// The string "(unset)". - public override string ToString() - { - return "(unset)"; - } + public override string ToString() => "(unset)"; } } } diff --git a/src/Perspex.Base/PerspexProperty`1.cs b/src/Perspex.Base/PerspexProperty`1.cs index dae476da31..81b11b4397 100644 --- a/src/Perspex.Base/PerspexProperty`1.cs +++ b/src/Perspex.Base/PerspexProperty`1.cs @@ -17,217 +17,34 @@ namespace Perspex /// /// 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 validation function. /// /// A method that gets called before and after the property starts being notified on an /// object; the bool argument will be true before and false afterwards. This callback is /// intended to support IsDataContextChanging. /// - /// Whether the property is an attached property. - public PerspexProperty( + protected PerspexProperty( string name, Type ownerType, - TValue defaultValue = default(TValue), - bool inherits = false, BindingMode defaultBindingMode = BindingMode.Default, - Func validate = null, - Action notifying = null, - bool isAttached = false) + Action notifying = null) : base( name, typeof(TValue), ownerType, - defaultValue, - inherits, defaultBindingMode, - Cast(validate), - notifying, - isAttached) + notifying) { } - /// - /// Initializes a new instance of the class. - /// - /// The name of the property. - /// The type of the class that registers the property. - /// Gets the current value of the property. - /// Sets the value of the property. - public PerspexProperty( - string name, - Type ownerType, - Func getter, - Action setter) - : base(name, typeof(TValue), ownerType, CastParamReturn(getter), CastParams(setter)) - { - Getter = getter; - Setter = setter; - } - /// /// Initializes a new instance of the class. /// /// The property to copy. /// The new owner type. - private PerspexProperty(PerspexProperty source, Type ownerType) + protected PerspexProperty(PerspexProperty source, Type ownerType) : base(source, ownerType) { } - - /// - /// Initializes a new instance of the class. - /// - /// The direct property to copy. - /// The new owner type. - /// A new getter. - /// A new setter. - private PerspexProperty( - PerspexProperty source, - Type ownerType, - Func getter, - Action setter) - : base(source, ownerType, CastParamReturn(getter), CastParams(setter)) - { - Getter = getter; - Setter = setter; - } - - /// - /// Gets the getter function for direct properties. - /// - internal new Func Getter { get; } - - /// - /// Gets the etter function for direct properties. - /// - internal new Action Setter { get; } - - /// - /// Registers the property on another type. - /// - /// The type of the additional owner. - /// The property. - public PerspexProperty AddOwner() where TOwner : PerspexObject - { - if (IsDirect) - { - throw new InvalidOperationException( - "You must provide a new getter and setter when calling AddOwner on a direct PerspexProperty."); - } - - var result = new PerspexProperty(this, typeof(TOwner)); - PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); - return result; - } - - /// - /// Registers the direct property on another type. - /// - /// The type of the additional owner. - /// The property. - public PerspexProperty AddOwner( - Func getter, - Action setter = null) - where TOwner : PerspexObject - { - if (!IsDirect) - { - throw new InvalidOperationException( - "This overload of AddOwner is for direct PerspexProperties."); - } - - var result = new PerspexProperty( - this, - typeof(TOwner), - CastReturn(getter), - CastParam1(setter)); - - PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); - return result; - } - - /// - /// Gets the default value for the property on the specified type. - /// - /// The type. - /// The default value. - public TValue GetDefaultValue() - { - return (TValue)GetDefaultValue(typeof(T)); - } - - /// - /// Overrides the validation function for the property on the specified type. - /// - /// The type. - /// The validation function. - public void OverrideValidation(Func validation) where T : PerspexObject - { - var f = validation != null ? - (o, v) => validation((T)o, (TValue)v) : - (Func)null; - OverrideValidation(typeof(T), f); - } - - /// - /// Casts a typed getter function to an untyped. - /// - /// The owner type. - /// The typed function. - /// The untyped function. - private static Func CastParamReturn(Func f) - where TOwner : PerspexObject - { - return (f != null) ? o => f((TOwner)o) : (Func)null; - } - - /// - /// Casts a typed getter function to an untyped. - /// - /// The owner type. - /// The typed function. - /// The untyped function. - private static Func CastReturn(Func f) - where TOwner : PerspexObject - { - return (f != null) ? o => f((TOwner)o) : (Func)null; - } - - /// - /// Casts a typed setter function to an untyped. - /// - /// The owner type. - /// The typed function. - /// The untyped function. - private static Action CastParams(Action f) - where TOwner : PerspexObject - { - return (f != null) ? (o, v) => f((TOwner)o, (TValue)v) : (Action)null; - } - - /// - /// Casts a typed setter function to an untyped. - /// - /// The owner type. - /// The typed function. - /// The untyped function. - private static Action CastParam1(Action f) - where TOwner : PerspexObject - { - return (f != null) ? (o, v) => f((TOwner)o, v) : (Action)null; - } - - /// - /// Casts a typed validation function to an untyped. - /// - /// The typed validation function. - /// The untyped validation function. - private static Func Cast(Func f) - { - return f != null ? (o, v) => f(o, (TValue)v) : (Func)null; - } } } diff --git a/src/Perspex.Base/StyledProperty.cs b/src/Perspex.Base/StyledProperty.cs new file mode 100644 index 0000000000..2d78b5c872 --- /dev/null +++ b/src/Perspex.Base/StyledProperty.cs @@ -0,0 +1,40 @@ +// 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 Perspex.Data; + +namespace Perspex +{ + /// + /// A styled perspex property. + /// + public class StyledProperty : StyledPropertyBase + { + /// + /// 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 validation function. + /// + /// A method that gets called before and after the property starts being notified on an + /// object; the bool argument will be true before and false afterwards. This callback is + /// intended to support IsDataContextChanging. + /// + public StyledProperty( + string name, + Type ownerType, + TValue defaultValue, + bool inherits = false, + BindingMode defaultBindingMode = BindingMode.Default, + Func validate = null, + Action notifying = null) + : base(name, ownerType, defaultValue, inherits, defaultBindingMode, validate, notifying) + { + } + } +} diff --git a/src/Perspex.Base/StyledPropertyBase.cs b/src/Perspex.Base/StyledPropertyBase.cs new file mode 100644 index 0000000000..90723fdf58 --- /dev/null +++ b/src/Perspex.Base/StyledPropertyBase.cs @@ -0,0 +1,204 @@ +// 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.Reflection; +using Perspex.Data; + +namespace Perspex +{ + /// + /// Base class for styled properties. + /// + public class StyledPropertyBase : PerspexProperty, IStyledPropertyAccessor + { + private readonly TValue _defaultValue; + private readonly Dictionary _defaultValues; + private bool _inherits; + private readonly Dictionary> _validation; + + /// + /// 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 validation function. + /// + /// A method that gets called before and after the property starts being notified on an + /// object; the bool argument will be true before and false afterwards. This callback is + /// intended to support IsDataContextChanging. + /// + protected StyledPropertyBase( + string name, + Type ownerType, + TValue defaultValue, + bool inherits = false, + BindingMode defaultBindingMode = BindingMode.Default, + Func validate = null, + Action notifying = null) + : base(name, ownerType, defaultBindingMode, notifying) + { + Contract.Requires(name != null); + Contract.Requires(ownerType != null); + + if (name.Contains(".")) + { + throw new ArgumentException("'name' may not contain periods."); + } + + _defaultValues = new Dictionary(); + _validation = new Dictionary>(); + + _defaultValue = defaultValue; + _inherits = inherits; + + if (validate != null) + { + _validation.Add(ownerType, validate); + } + } + + /// + /// Gets a value indicating whether the property inherits its value. + /// + /// + /// A value indicating whether the property inherits its value. + /// + public override bool Inherits => _inherits; + + /// + /// Gets the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public TValue GetDefaultValue(Type type) + { + Contract.Requires(type != null); + + while (type != null) + { + TValue result; + + if (_defaultValues.TryGetValue(type, out result)) + { + return result; + } + + type = type.GetTypeInfo().BaseType; + } + + return _defaultValue; + } + + /// + /// Gets the validation function for the property on the specified type. + /// + /// The type. + /// + /// The validation function, or null if no validation function registered for this type. + /// + public Func GetValidationFunc(Type type) + { + Contract.Requires(type != null); + + while (type != null) + { + Func result; + + if (_validation.TryGetValue(type, out result)) + { + return result; + } + + type = type.GetTypeInfo().BaseType; + } + + return null; + } + + /// + /// Overrides the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public void OverrideDefaultValue(TValue defaultValue) where T : IPerspexObject + { + 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, TValue defaultValue) + { + Contract.Requires(type != null); + + if (_defaultValues.ContainsKey(type)) + { + throw new InvalidOperationException("Default value is already set for this property."); + } + + _defaultValues.Add(type, defaultValue); + } + + /// + /// Overrides the validation function for the property on the specified type. + /// + /// The type. + /// The validation function. + public void OverrideValidation(Func validation) + where T : IPerspexObject + { + var type = typeof(T); + + if (_validation.ContainsKey(type)) + { + throw new InvalidOperationException("Validation is already set for this property."); + } + + _validation.Add(type, Cast(validation)); + } + + /// + /// Overrides the validation function for the property on the specified type. + /// + /// The type. + /// The validation function. + public void OverrideValidation(Type type, Func validation) + { + Contract.Requires(type != null); + + if (_validation.ContainsKey(type)) + { + throw new InvalidOperationException("Validation is already set for this property."); + } + + _validation.Add(type, validation); + } + + /// + /// Gets the string representation of the property. + /// + /// The property's string representation. + public override string ToString() + { + return Name; + } + + /// + Func IStyledPropertyAccessor.GetValidationFunc(Type type) + { + var typed = GetValidationFunc(type); + return (o, v) => typed(o, (TValue)v); + } + + /// + object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultValue(type); + } +} diff --git a/src/Perspex.Controls/Grid.cs b/src/Perspex.Controls/Grid.cs index c2da194441..1c848f6887 100644 --- a/src/Perspex.Controls/Grid.cs +++ b/src/Perspex.Controls/Grid.cs @@ -17,7 +17,9 @@ namespace Perspex.Controls /// Defines the Column attached property. /// public static readonly PerspexProperty ColumnProperty = - PerspexProperty.RegisterAttached("Column"); + PerspexProperty.RegisterAttached( + "Column", + validate: ValidateColumn); /// /// Defines the ColumnSpan attached property. @@ -29,7 +31,9 @@ namespace Perspex.Controls /// Defines the Row attached property. /// public static readonly PerspexProperty RowProperty = - PerspexProperty.RegisterAttached("Row"); + PerspexProperty.RegisterAttached( + "Row", + validate: ValidateRow); /// /// Defines the RowSpan attached property. @@ -578,6 +582,26 @@ namespace Perspex.Controls } } + private static int ValidateColumn(PerspexObject o, int value) + { + if (value < 0) + { + throw new ArgumentException("Invalid Grid.Column value."); + } + + return value; + } + + private static int ValidateRow(PerspexObject o, int value) + { + if (value < 0) + { + throw new ArgumentException("Invalid Grid.Row value."); + } + + return value; + } + private void CreateMatrices(int rowCount, int colCount) { if (_rowMatrix == null || _colMatrix == null || diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs index 07a4758292..13e36f40a8 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs @@ -341,13 +341,13 @@ namespace Perspex.Base.UnitTests private class Class1 : PerspexObject { - public static readonly PerspexProperty FooProperty = + public static readonly DirectProperty FooProperty = PerspexProperty.RegisterDirect("Foo", o => o.Foo, (o, v) => o.Foo = v); - public static readonly PerspexProperty BarProperty = + public static readonly DirectProperty BarProperty = PerspexProperty.RegisterDirect("Bar", o => o.Bar); - public static readonly PerspexProperty BazProperty = + public static readonly DirectProperty BazProperty = PerspexProperty.RegisterDirect("Bar", o => o.Baz, (o,v) => o.Baz = v); private string _foo = "initial"; diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetValue.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetValue.cs index f143f56c56..c5af5273e9 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetValue.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetValue.cs @@ -55,10 +55,10 @@ namespace Perspex.Base.UnitTests private class Class1 : PerspexObject { - public static readonly PerspexProperty FooProperty = + public static readonly StyledProperty FooProperty = PerspexProperty.Register("Foo", "foodefault"); - public static readonly PerspexProperty BazProperty = + public static readonly StyledProperty BazProperty = PerspexProperty.Register("Baz", "bazdefault", true); } diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Inheritance.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Inheritance.cs index cfaf89a30a..4c4e233613 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Inheritance.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Inheritance.cs @@ -77,10 +77,10 @@ namespace Perspex.Base.UnitTests private class Class1 : PerspexObject { - public static readonly PerspexProperty FooProperty = + public static readonly StyledProperty FooProperty = PerspexProperty.Register("Foo", "foodefault"); - public static readonly PerspexProperty BazProperty = + public static readonly StyledProperty BazProperty = PerspexProperty.Register("Baz", "bazdefault", true); } diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs index 422967f858..cfbf31a764 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs @@ -71,7 +71,7 @@ namespace Perspex.Base.UnitTests private class Class1 : PerspexObject { - public static readonly PerspexProperty QuxProperty = + public static readonly StyledProperty QuxProperty = PerspexProperty.Register("Qux", validate: Validate); public Class1() @@ -97,7 +97,7 @@ namespace Perspex.Base.UnitTests private class Class2 : PerspexObject { - public static readonly PerspexProperty QuxProperty = + public static readonly StyledProperty QuxProperty = Class1.QuxProperty.AddOwner(); static Class2()