From d25e057ccce9b75bc30debf45f826bea85aa9af1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Jan 2016 20:53:39 +0100 Subject: [PATCH] More WIP refactoring properties. Compiles now but lots of tests failing. --- .../Perspex.Markup.Xaml/Data/MultiBinding.cs | 2 +- src/Perspex.Base/AttachedProperty.cs | 28 +- src/Perspex.Base/DirectProperty.cs | 2 +- src/Perspex.Base/Perspex.Base.csproj | 2 + src/Perspex.Base/PerspexObject.cs | 34 ++- src/Perspex.Base/PerspexObjectExtensions.cs | 2 +- src/Perspex.Base/PerspexProperty.cs | 196 +++++++------ src/Perspex.Base/PerspexProperty`1.cs | 14 +- src/Perspex.Base/PropertyMetadata.cs | 66 +++++ src/Perspex.Base/StyledProperty.cs | 40 ++- src/Perspex.Base/StyledPropertyBase.cs | 151 +++++----- src/Perspex.Base/StyledPropertyMetadata.cs | 63 ++++ src/Perspex.Controls/Border.cs | 8 +- src/Perspex.Controls/Button.cs | 10 +- src/Perspex.Controls/Carousel.cs | 5 +- src/Perspex.Controls/ContentControl.cs | 9 +- src/Perspex.Controls/Control.cs | 4 +- src/Perspex.Controls/Decorator.cs | 5 +- src/Perspex.Controls/HotkeyManager.cs | 4 +- src/Perspex.Controls/ItemsControl.cs | 8 +- src/Perspex.Controls/ListBoxItem.cs | 2 +- src/Perspex.Controls/MenuItem.cs | 10 +- .../Presenters/ScrollContentPresenter.cs | 9 +- .../Primitives/HeaderedContentControl.cs | 2 +- .../Primitives/HeaderedSelectingControl.cs | 2 - src/Perspex.Controls/Primitives/PopupRoot.cs | 1 - src/Perspex.Controls/Primitives/RangeBase.cs | 6 +- src/Perspex.Controls/Primitives/ScrollBar.cs | 6 +- .../Primitives/SelectingItemsControl.cs | 8 +- .../Primitives/TemplatedControl.cs | 17 +- src/Perspex.Controls/ScrollViewer.cs | 24 +- src/Perspex.Controls/TextBlock.cs | 21 +- src/Perspex.Controls/TextBox.cs | 21 +- src/Perspex.HtmlRenderer/HtmlControl.cs | 2 +- src/Perspex.Input/InputElement.cs | 14 +- src/Perspex.Input/KeyboardNavigation.cs | 6 +- src/Perspex.SceneGraph/Visual.cs | 16 +- src/Perspex.Styling/Styling/Setter.cs | 2 +- .../AttachedPropertyTests.cs | 26 ++ .../DirectPropertyTests.cs | 87 ++++++ .../Perspex.Base.UnitTests.csproj | 3 + .../PerspexPropertyRegistryTests.cs | 4 +- .../PerspexPropertyTests.cs | 274 +++++++++--------- .../StyledPropertyTests.cs | 48 +++ 44 files changed, 798 insertions(+), 466 deletions(-) create mode 100644 src/Perspex.Base/PropertyMetadata.cs create mode 100644 src/Perspex.Base/StyledPropertyMetadata.cs create mode 100644 tests/Perspex.Base.UnitTests/AttachedPropertyTests.cs create mode 100644 tests/Perspex.Base.UnitTests/DirectPropertyTests.cs create mode 100644 tests/Perspex.Base.UnitTests/StyledPropertyTests.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs index ea3760cf83..2911335b8d 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs @@ -90,7 +90,7 @@ namespace Perspex.Markup.Xaml.Data internal void Bind(IPerspexObject target, PerspexProperty property, ISubject subject) { var mode = Mode == BindingMode.Default ? - property.DefaultBindingMode : Mode; + property.GetMetadata(target.GetType()).DefaultBindingMode : Mode; switch (mode) { diff --git a/src/Perspex.Base/AttachedProperty.cs b/src/Perspex.Base/AttachedProperty.cs index b713f61a07..5a86033a4f 100644 --- a/src/Perspex.Base/AttachedProperty.cs +++ b/src/Perspex.Base/AttachedProperty.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Perspex.Data; namespace Perspex { @@ -17,25 +16,30 @@ namespace Perspex /// /// 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. + /// The property metadata. 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) + bool inherits, + StyledPropertyMetadata metadata) + : base(name, ownerType, inherits, metadata) { } - /// - public override string FullName => OwnerType + "." + Name; - /// public override bool IsAttached => true; + + /// + /// Attaches the property as a non-attached property on the specified type. + /// + /// The owner type. + /// The property. + public StyledProperty AddOwner() + { + var result = new StyledProperty(this, typeof(TOwner)); + PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); + return result; + } } } diff --git a/src/Perspex.Base/DirectProperty.cs b/src/Perspex.Base/DirectProperty.cs index d36eb675cf..e0849b7785 100644 --- a/src/Perspex.Base/DirectProperty.cs +++ b/src/Perspex.Base/DirectProperty.cs @@ -28,7 +28,7 @@ namespace Perspex string name, Func getter, Action setter = null) - : base(name, typeof(TOwner)) + : base(name, typeof(TOwner), new PropertyMetadata()) { Contract.Requires(getter != null); diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 8b44506c15..793bda2487 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -58,6 +58,7 @@ + @@ -85,6 +86,7 @@ + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index e550c96302..5a0e1f5cf8 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 ? - //// ((IDirectPropertyAccessor)property).GetValue(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) : + ((IStyledPropertyAccessor)property).GetDefaultValue(GetType()); + + var e = new PerspexPropertyChangedEventArgs( + this, + property, + PerspexProperty.UnsetValue, + value, + BindingPriority.Unset); + + property.NotifyInitialized(e); + } } /// @@ -162,8 +162,10 @@ namespace Perspex set { + var metadata = binding.Property.GetMetadata(GetType()); + var mode = (binding.Mode == BindingMode.Default) ? - binding.Property.DefaultBindingMode : + metadata.DefaultBindingMode : binding.Mode; var sourceBinding = value as IndexerDescriptor; diff --git a/src/Perspex.Base/PerspexObjectExtensions.cs b/src/Perspex.Base/PerspexObjectExtensions.cs index cbfb8e45e7..331f0d97a8 100644 --- a/src/Perspex.Base/PerspexObjectExtensions.cs +++ b/src/Perspex.Base/PerspexObjectExtensions.cs @@ -184,7 +184,7 @@ namespace Perspex if (mode == BindingMode.Default) { - mode = property.DefaultBindingMode; + mode = property.GetMetadata(o.GetType()).DefaultBindingMode; } return o.Bind( diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs index a2752ec9c2..f2a40aa582 100644 --- a/src/Perspex.Base/PerspexProperty.cs +++ b/src/Perspex.Base/PerspexProperty.cs @@ -2,14 +2,16 @@ // 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.Data; using Perspex.Utilities; namespace Perspex { /// - /// Base class for perspex property metadata. + /// Base class for perspex properties. /// public class PerspexProperty : IEquatable { @@ -18,25 +20,12 @@ namespace Perspex /// public static readonly object UnsetValue = new Unset(); - /// - /// Gets the next ID that will be allocated to a property. - /// private static int s_nextId = 1; - - /// - /// Observable fired when this property changes on any . - /// + private readonly int _id; private readonly Subject _initialized; - - /// - /// Observable fired when this property changes on any . - /// private readonly Subject _changed; - - /// - /// Gets the ID of the property. - /// - private readonly int _id; + private readonly PropertyMetadata _defaultMetadata; + private readonly Dictionary _metadata; /// /// Initializes a new instance of the class. @@ -44,22 +33,17 @@ 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 binding mode for the property. - /// - /// 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. - /// + /// The property metadata. protected PerspexProperty( string name, Type valueType, Type ownerType, - BindingMode defaultBindingMode = BindingMode.Default, - Action notifying = null) + PropertyMetadata metadata) { Contract.Requires(name != null); Contract.Requires(valueType != null); Contract.Requires(ownerType != null); + Contract.Requires(metadata != null); if (name.Contains(".")) { @@ -68,13 +52,15 @@ namespace Perspex _initialized = new Subject(); _changed = new Subject(); + _metadata = new Dictionary(); Name = name; PropertyType = valueType; OwnerType = ownerType; - DefaultBindingMode = defaultBindingMode; - Notifying = notifying; _id = s_nextId++; + + _metadata.Add(ownerType, metadata); + _defaultMetadata = metadata; } /// @@ -87,19 +73,13 @@ namespace Perspex Contract.Requires(source != null); Contract.Requires(ownerType != null); - if (source.IsDirect) - { - throw new InvalidOperationException( - "This method cannot be called on direct PerspexProperties."); - } - _initialized = source._initialized; _changed = source._changed; + _metadata = source._metadata; Name = source.Name; PropertyType = source.PropertyType; OwnerType = ownerType; - DefaultBindingMode = source.DefaultBindingMode; Notifying = Notifying; _id = source._id; } @@ -109,12 +89,6 @@ namespace Perspex /// public string Name { get; } - /// - /// Gets the full name of the property, wich includes the owner type in the case of - /// attached properties. - /// - public virtual string FullName => Name; - /// /// Gets the type of the property's value. /// @@ -130,11 +104,6 @@ namespace Perspex /// public virtual bool Inherits => false; - /// - /// Gets the default binding mode for the property. - /// - public BindingMode DefaultBindingMode { get; } - /// /// Gets a value indicating whether this is an attached property. /// @@ -272,21 +241,18 @@ namespace Perspex BindingMode defaultBindingMode = BindingMode.OneWay, Func validate = null, Action notifying = null) - where TOwner : IPerspexObject + where TOwner : IPerspexObject { Contract.Requires(name != null); - var result = new StyledProperty( - name, - typeof(TOwner), + var metadata = new StyledPropertyMetadata( defaultValue, - inherits, - defaultBindingMode, - Cast(validate), - notifying); + validate: Cast(validate), + defaultBindingMode: defaultBindingMode, + notifyingCallback: notifying); + var result = new StyledProperty(name, typeof(TOwner), inherits, metadata); PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); - return result; } @@ -312,16 +278,13 @@ namespace Perspex { Contract.Requires(name != null); - var result = new AttachedProperty( - name, - typeof(TOwner), + var metadata = new StyledPropertyMetadata( defaultValue, - inherits, - defaultBindingMode, - Cast(validate)); + validate: Cast(validate), + defaultBindingMode: defaultBindingMode); + var result = new AttachedProperty(name, typeof(TOwner), inherits, metadata); PerspexPropertyRegistry.Instance.Register(typeof(THost), result); - return result; } @@ -337,7 +300,7 @@ namespace Perspex /// The default binding mode for the property. /// A validation function. /// A - public static PerspexProperty RegisterAttached( + public static AttachedProperty RegisterAttached( string name, Type ownerType, TValue defaultValue = default(TValue), @@ -348,16 +311,13 @@ namespace Perspex { Contract.Requires(name != null); - var result = new AttachedProperty( - name, - ownerType, + var metadata = new StyledPropertyMetadata( defaultValue, - inherits, - defaultBindingMode, - Cast(validate)); + validate: Cast(validate), + defaultBindingMode: defaultBindingMode); + var result = new AttachedProperty(name, ownerType, inherits, metadata); PerspexPropertyRegistry.Instance.Register(typeof(THost), result); - return result; } @@ -383,6 +343,22 @@ namespace Perspex 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 IndexerDescriptor Bind() + { + return new IndexerDescriptor + { + Property = this, + }; + } + /// public override bool Equals(object obj) { @@ -403,19 +379,56 @@ namespace Perspex } /// - /// Returns a binding accessor that can be passed to 's [] - /// operator to initiate a binding. + /// Gets the property metadata for the specified type. /// - /// A . - /// - /// The ! and ~ operators are short forms of this. - /// - public IndexerDescriptor Bind() + /// The type. + /// + /// The property metadata. + /// + public PropertyMetadata GetMetadata() where T : IPerspexObject { - return new IndexerDescriptor + var type = typeof(T); + + while (type != null) { - Property = this, - }; + PropertyMetadata result; + + if (_metadata.TryGetValue(type, out result)) + { + return result; + } + + type = type.GetTypeInfo().BaseType; + } + + return _defaultMetadata; + } + + + /// + /// Gets the property metadata for the specified type. + /// + /// The type. + /// + /// The property metadata. + /// + public PropertyMetadata GetMetadata(Type type) + { + Contract.Requires(type != null); + + while (type != null) + { + PropertyMetadata result; + + if (_metadata.TryGetValue(type, out result)) + { + return result; + } + + type = type.GetTypeInfo().BaseType; + } + + return _defaultMetadata; } /// @@ -463,7 +476,7 @@ namespace Perspex /// The property value type. /// The typed function. /// The untyped function. - protected static Func Cast(Func f) + protected static Func Cast(Func f) where TOwner : IPerspexObject { if (f == null) @@ -472,10 +485,31 @@ namespace Perspex } else { - return (o, v) => f((TOwner)o, v); + return (o, v) => f((TOwner)o, (TValue)v); } } + /// + /// Overrides the metadata for the property on the specified type. + /// + /// The type. + /// The metadata. + protected void OverrideMetadata(Type type, PropertyMetadata metadata) + { + Contract.Requires(type != null); + Contract.Requires(metadata != null); + + if (_metadata.ContainsKey(type)) + { + throw new InvalidOperationException( + $"Metadata is already set for {Name} on {type}."); + } + + var baseMetadata = GetMetadata(type); + metadata.Merge(baseMetadata, this); + _metadata.Add(type, metadata); + } + /// /// Class representing the . /// diff --git a/src/Perspex.Base/PerspexProperty`1.cs b/src/Perspex.Base/PerspexProperty`1.cs index 81b11b4397..4044a164b5 100644 --- a/src/Perspex.Base/PerspexProperty`1.cs +++ b/src/Perspex.Base/PerspexProperty`1.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Perspex.Data; namespace Perspex { @@ -17,23 +16,16 @@ namespace Perspex /// /// The name of the property. /// The type of the class that registers the property. - /// The default binding mode for the property. - /// - /// 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. - /// + /// The property metadata. protected PerspexProperty( string name, Type ownerType, - BindingMode defaultBindingMode = BindingMode.Default, - Action notifying = null) + PropertyMetadata metadata) : base( name, typeof(TValue), ownerType, - defaultBindingMode, - notifying) + metadata) { } diff --git a/src/Perspex.Base/PropertyMetadata.cs b/src/Perspex.Base/PropertyMetadata.cs new file mode 100644 index 0000000000..99fc1fd021 --- /dev/null +++ b/src/Perspex.Base/PropertyMetadata.cs @@ -0,0 +1,66 @@ +// 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 +{ + /// + /// Base class for perspex property metadata. + /// + public class PropertyMetadata + { + /// + /// Initializes a new instance of the class. + /// + /// The default binding mode. + /// The property notifying callback. + public PropertyMetadata( + BindingMode defaultBindingMode = BindingMode.Default, + Action notifyingCallback = null) + { + DefaultBindingMode = defaultBindingMode; + NotifyingCallback = notifyingCallback; + } + + /// + /// Gets the default binding mode for the property. + /// + public BindingMode DefaultBindingMode { get; private set; } + + /// + /// Gets a method that gets called before and after the property starts being notified on an + /// object. + /// + /// + /// When a property changes, change notifications are sent to all property subscribers; + /// for example via the observable and and the + /// event. If this callback is set for a property, + /// then it will be called before and after these notifications take place. The bool argument + /// will be true before the property change notifications are sent and false afterwards. This + /// callback is intended to support Control.IsDataContextChanging. + /// + public Action NotifyingCallback { get; private set; } + + /// + /// Merges the metadata with the base metadata. + /// + /// The base metadata to merge. + /// The property to which the metadata is being applied. + public virtual void Merge( + PropertyMetadata baseMetadata, + PerspexProperty property) + { + if (DefaultBindingMode == BindingMode.Default) + { + DefaultBindingMode = baseMetadata.DefaultBindingMode; + } + + if (NotifyingCallback == null) + { + NotifyingCallback = baseMetadata.NotifyingCallback; + } + } + } +} diff --git a/src/Perspex.Base/StyledProperty.cs b/src/Perspex.Base/StyledProperty.cs index 2d78b5c872..53710c2e25 100644 --- a/src/Perspex.Base/StyledProperty.cs +++ b/src/Perspex.Base/StyledProperty.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Perspex.Data; namespace Perspex { @@ -16,25 +15,36 @@ 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. - /// + /// The property metadata. 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) + bool inherits, + StyledPropertyMetadata metadata) + : base(name, ownerType, inherits, metadata) { } + + /// + /// Initializes a new instance of the class. + /// + /// The property to add the owner to. + /// The type of the class that registers the property. + internal StyledProperty(StyledPropertyBase source, Type ownerType) + : base(source, ownerType) + { + } + + /// + /// Registers the property on another type. + /// + /// The type of the additional owner. + /// The property. + public StyledProperty AddOwner() + { + PerspexPropertyRegistry.Instance.Register(typeof(TOwner), this); + return this; + } } } diff --git a/src/Perspex.Base/StyledPropertyBase.cs b/src/Perspex.Base/StyledPropertyBase.cs index 90723fdf58..3bd54f5084 100644 --- a/src/Perspex.Base/StyledPropertyBase.cs +++ b/src/Perspex.Base/StyledPropertyBase.cs @@ -2,9 +2,8 @@ // 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; +using Perspex.Utilities; namespace Perspex { @@ -13,34 +12,21 @@ namespace Perspex /// 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. - /// + /// The property metadata. 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) + bool inherits, + StyledPropertyMetadata metadata) + : base(name, ownerType, CheckMetadata(metadata)) { Contract.Requires(name != null); Contract.Requires(ownerType != null); @@ -50,16 +36,18 @@ namespace Perspex 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); - } + /// + /// Initializes a new instance of the class. + /// + /// The property to add the owner to. + /// The type of the class that registers the property. + protected StyledPropertyBase(StyledPropertyBase source, Type ownerType) + : base(source, ownerType) + { + _inherits = source.Inherits; } /// @@ -79,19 +67,7 @@ namespace Perspex { Contract.Requires(type != null); - while (type != null) - { - TValue result; - - if (_defaultValues.TryGetValue(type, out result)) - { - return result; - } - - type = type.GetTypeInfo().BaseType; - } - - return _defaultValue; + return (TValue)(GetMetadata(type) as StyledPropertyMetadata)?.DefaultValue; } /// @@ -103,21 +79,22 @@ namespace Perspex /// public Func GetValidationFunc(Type type) { - Contract.Requires(type != null); + return null; + ////Contract.Requires(type != null); - while (type != null) - { - Func result; + ////while (type != null) + ////{ + //// Func result; - if (_validation.TryGetValue(type, out result)) - { - return result; - } + //// if (_validation.TryGetValue(type, out result)) + //// { + //// return result; + //// } - type = type.GetTypeInfo().BaseType; - } + //// type = type.GetTypeInfo().BaseType; + ////} - return null; + ////return null; } /// @@ -137,14 +114,7 @@ namespace Perspex /// 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); + OverrideMetadata(type, new StyledPropertyMetadata(defaultValue)); } /// @@ -155,14 +125,15 @@ namespace Perspex public void OverrideValidation(Func validation) where T : IPerspexObject { - var type = typeof(T); + throw new NotImplementedException(); + ////var type = typeof(T); - if (_validation.ContainsKey(type)) - { - throw new InvalidOperationException("Validation is already set for this property."); - } + ////if (_validation.ContainsKey(type)) + ////{ + //// throw new InvalidOperationException("Validation is already set for this property."); + ////} - _validation.Add(type, Cast(validation)); + ////_validation.Add(type, Cast(validation)); } /// @@ -172,14 +143,15 @@ namespace Perspex /// The validation function. public void OverrideValidation(Type type, Func validation) { - Contract.Requires(type != null); + throw new NotImplementedException(); + //Contract.Requires(type != null); - if (_validation.ContainsKey(type)) - { - throw new InvalidOperationException("Validation is already set for this property."); - } + //if (_validation.ContainsKey(type)) + //{ + // throw new InvalidOperationException("Validation is already set for this property."); + //} - _validation.Add(type, validation); + //_validation.Add(type, validation); } /// @@ -195,10 +167,45 @@ namespace Perspex Func IStyledPropertyAccessor.GetValidationFunc(Type type) { var typed = GetValidationFunc(type); - return (o, v) => typed(o, (TValue)v); + + if (typed != null) + { + return (o, v) => typed(o, (TValue)v); + } + else + { + return null; + } } /// object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultValue(type); + + private static PropertyMetadata CheckMetadata(StyledPropertyMetadata metadata) + { + var valueType = typeof(TValue).GetTypeInfo(); + + if (metadata.DefaultValue != null) + { + var defaultType = metadata.DefaultValue.GetType().GetTypeInfo(); + + if (!valueType.IsAssignableFrom(defaultType)) + { + throw new ArgumentException( + "Invalid default property value. " + + $"Expected {typeof(TValue)} but recieved {metadata.DefaultValue.GetType()}."); + } + } + else + { + if (!TypeUtilities.AcceptsNull(typeof(TValue))) + { + throw new ArgumentException( + $"Invalid default property value. Null is not a valid value for {typeof(TValue)}."); + } + } + + return metadata; + } } } diff --git a/src/Perspex.Base/StyledPropertyMetadata.cs b/src/Perspex.Base/StyledPropertyMetadata.cs new file mode 100644 index 0000000000..a6b2bc8418 --- /dev/null +++ b/src/Perspex.Base/StyledPropertyMetadata.cs @@ -0,0 +1,63 @@ +// 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 +{ + /// + /// Styled perspex property metadata. + /// + public class StyledPropertyMetadata : PropertyMetadata + { + /// + /// Initializes a new instance of the class. + /// + /// The default value of the property. + /// A validation function. + /// The default binding mode. + /// The property notifying callback. + public StyledPropertyMetadata( + object defaultValue, + Func validate = null, + BindingMode defaultBindingMode = BindingMode.Default, + Action notifyingCallback = null) + : base(defaultBindingMode, notifyingCallback) + { + DefaultValue = defaultValue; + Validate = validate; + } + + /// + /// Gets the default value for the property. + /// + public object DefaultValue { get; private set; } + + /// + /// Gets the validation callback. + /// + public Func Validate { get; private set; } + + /// + public override void Merge(PropertyMetadata baseMetadata, PerspexProperty property) + { + base.Merge(baseMetadata, property); + + var src = baseMetadata as StyledPropertyMetadata; + + if (src != null) + { + if (DefaultValue == null) + { + DefaultValue = src.DefaultValue; + } + + if (Validate != null) + { + Validate = src.Validate; + } + } + } + } +} diff --git a/src/Perspex.Controls/Border.cs b/src/Perspex.Controls/Border.cs index bcb1235f25..da1843073c 100644 --- a/src/Perspex.Controls/Border.cs +++ b/src/Perspex.Controls/Border.cs @@ -13,25 +13,25 @@ namespace Perspex.Controls /// /// Defines the property. /// - public static readonly PerspexProperty BackgroundProperty = + public static readonly StyledProperty BackgroundProperty = PerspexProperty.Register(nameof(Background)); /// /// Defines the property. /// - public static readonly PerspexProperty BorderBrushProperty = + public static readonly StyledProperty BorderBrushProperty = PerspexProperty.Register(nameof(BorderBrush)); /// /// Defines the property. /// - public static readonly PerspexProperty BorderThicknessProperty = + public static readonly StyledProperty BorderThicknessProperty = PerspexProperty.Register(nameof(BorderThickness)); /// /// Defines the property. /// - public static readonly PerspexProperty CornerRadiusProperty = + public static readonly StyledProperty CornerRadiusProperty = PerspexProperty.Register(nameof(CornerRadius)); /// diff --git a/src/Perspex.Controls/Button.cs b/src/Perspex.Controls/Button.cs index 71dbbf22e0..9c92b299b5 100644 --- a/src/Perspex.Controls/Button.cs +++ b/src/Perspex.Controls/Button.cs @@ -35,31 +35,31 @@ namespace Perspex.Controls /// /// Defines the property. /// - public static readonly PerspexProperty ClickModeProperty = + public static readonly StyledProperty ClickModeProperty = PerspexProperty.Register(nameof(ClickMode)); /// /// Defines the property. /// - public static readonly PerspexProperty CommandProperty = + public static readonly StyledProperty CommandProperty = PerspexProperty.Register(nameof(Command)); /// /// Defines the property. /// - public static readonly PerspexProperty HotKeyProperty = + public static readonly StyledProperty HotKeyProperty = HotKeyManager.HotKeyProperty.AddOwner