Browse Source

WIP: Splitting PerspexProperty up

Steven Kirk 9 years ago
parent
commit
03e2ff8784

+ 41 - 0
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
+{
+    /// <summary>
+    /// An attached perspex property.
+    /// </summary>
+    /// <typeparam name="TValue">The type of the property's value.</typeparam>
+    public class AttachedProperty<TValue> : StyledPropertyBase<TValue>
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AttachedProperty{TValue}"/> class.
+        /// </summary>
+        /// <param name="name">The name of the property.</param>
+        /// <param name="ownerType">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>
+        public AttachedProperty(
+            string name,
+            Type ownerType,
+            TValue defaultValue = default(TValue),
+            bool inherits = false,
+            BindingMode defaultBindingMode = BindingMode.Default,
+            Func<IPerspexObject, TValue, TValue> validate = null)
+            : base(name, ownerType, defaultValue, inherits, defaultBindingMode, validate)
+        {
+        }
+
+        /// <inheritdoc/>
+        public override string FullName => OwnerType + "." + Name;
+
+        /// <inheritdoc/>
+        public override bool IsAttached => true;
+    }
+}

+ 109 - 0
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
+{
+    /// <summary>
+    /// A direct perspex property.
+    /// </summary>
+    /// <typeparam name="TOwner">The class that registered the property.</typeparam>
+    /// <typeparam name="TValue">The type of the property's value.</typeparam>
+    /// <remarks>
+    /// Direct perspex properties are backed by a field on the object, but exposed via the
+    /// <see cref="PerspexProperty"/> system. They hold a getter and an optional setter which
+    /// allows the perspex property system to read and write the current value.
+    /// </remarks>
+    public class DirectProperty<TOwner, TValue> : PerspexProperty<TValue>, IDirectPropertyAccessor
+        where TOwner : IPerspexObject
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DirectProperty{TOwner, TValue}"/> class.
+        /// </summary>
+        /// <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. May be null.</param>
+        public DirectProperty(
+            string name,
+            Func<TOwner, TValue> getter,
+            Action<TOwner, TValue> setter = null)
+            : base(name, typeof(TOwner))
+        {
+            Contract.Requires<ArgumentNullException>(getter != null);
+
+            Getter = getter;
+            Setter = setter;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="PerspexProperty"/> class.
+        /// </summary>
+        /// <param name="source">The property to copy.</param>
+        /// <param name="getter">Gets the current value of the property.</param>
+        /// <param name="setter">Sets the value of the property. May be null.</param>
+        private DirectProperty(
+            PerspexProperty<TValue> source,
+            Func<TOwner, TValue> getter,
+            Action<TOwner, TValue> setter)
+            : base(source, typeof(TOwner))
+        {
+            Contract.Requires<ArgumentNullException>(getter != null);
+
+            Getter = getter;
+            Setter = setter;
+        }
+
+        /// <inheritdoc/>
+        public override bool IsDirect => true;
+
+        /// <inheritdoc/>
+        public override bool IsReadOnly => Setter == null;
+
+        /// <summary>
+        /// Gets the getter function.
+        /// </summary>
+        public Func<TOwner, TValue> Getter { get; }
+
+        /// <summary>
+        /// Gets the setter function.
+        /// </summary>
+        public Action<TOwner, TValue> Setter { get; }
+
+        /// <summary>
+        /// Registers the direct property on another type.
+        /// </summary>
+        /// <typeparam name="TNewOwner">The type of the additional owner.</typeparam>
+        /// <returns>The property.</returns>
+        public DirectProperty<TNewOwner, TValue> AddOwner<TNewOwner>(
+            Func<TNewOwner, TValue> getter,
+            Action<TNewOwner, TValue> setter = null)
+                where TNewOwner : PerspexObject
+        {
+            var result = new DirectProperty<TNewOwner, TValue>(
+                this,
+                getter,
+                setter);
+
+            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
+            return result;
+        }
+
+        /// <inheritdoc/>
+        object IDirectPropertyAccessor.GetValue(IPerspexObject instance)
+        {
+            return Getter((TOwner)instance);
+        }
+
+        /// <inheritdoc/>
+        void IDirectPropertyAccessor.SetValue(IPerspexObject instance, object value)
+        {
+            if (Setter == null)
+            {
+                throw new ArgumentException($"The property {Name} is readonly.");
+            }
+
+            Setter((TOwner)instance, (TValue)value);
+        }
+    }
+}

+ 31 - 0
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
+{
+    /// <summary>
+    /// Provides a runtime interface for getting and setting 
+    /// <see cref="DirectProperty{TOwner, TValue}"/> values.
+    /// </summary>
+    internal interface IDirectPropertyAccessor
+    {
+        /// <summary>
+        /// Gets a value indicating whether the property is read-only.
+        /// </summary>
+        bool IsReadOnly { get; }
+
+        /// <summary>
+        /// Gets the value of the property on the instance.
+        /// </summary>
+        /// <param name="instance">The instance.</param>
+        /// <returns>The property value.</returns>
+        object GetValue(IPerspexObject instance);
+
+        /// <summary>
+        /// Sets the value of the property on the instance.
+        /// </summary>
+        /// <param name="instance">The instance.</param>
+        /// <param name="value">The value.</param>
+        void SetValue(IPerspexObject instance, object value);
+    }
+}

+ 31 - 0
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
+{
+    /// <summary>
+    /// Provides a runtime interface for interfacing with <see cref="StyledProperty{TValue}"/>.
+    /// </summary>
+    internal interface IStyledPropertyAccessor
+    {
+        /// <summary>
+        /// Gets the default value for the property for the specified type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <returns>
+        /// The default value.
+        /// </returns>
+        object GetDefaultValue(Type type);
+
+        /// <summary>
+        /// Gets a 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 exists.
+        /// </returns>
+        Func<IPerspexObject, object, object> GetValidationFunc(Type type);
+    }
+}

+ 6 - 0
src/Perspex.Base/Perspex.Base.csproj

@@ -48,12 +48,18 @@
     <Compile Include="Collections\PerspexDictionary.cs" />
     <Compile Include="Data\BindingMode.cs" />
     <Compile Include="Diagnostics\PerspexObjectExtensions.cs" />
+    <Compile Include="AttachedProperty.cs" />
+    <Compile Include="IStyledPropertyAccessor.cs" />
+    <Compile Include="IDirectPropertyAccessor.cs" />
+    <Compile Include="DirectProperty.cs" />
     <Compile Include="IPerspexObject.cs" />
     <Compile Include="Metadata\ContentAttribute.cs" />
     <Compile Include="PerspexDisposable.cs" />
     <Compile Include="PerspexLocator.cs" />
     <Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />
     <Compile Include="PerspexObjectExtensions.cs" />
+    <Compile Include="StyledProperty.cs" />
+    <Compile Include="StyledPropertyBase.cs" />
     <Compile Include="PerspexPropertyRegistry.cs" />
     <Compile Include="PerspexProperty`1.cs" />
     <Compile Include="Platform\IAssetLoader.cs" />

+ 24 - 61
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);
+            ////}
         }
 
         /// <summary>
@@ -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<T>)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<ArgumentNullException>(property != null);
-            VerifyAccess();
-            if (property.IsDirect)
-            {
-                property = (PerspexProperty<T>)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);
         }
 
         /// <summary>
@@ -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<ArgumentNullException>(property != null);
-            VerifyAccess();
-            if (property.IsDirect)
-            {
-                property = (PerspexProperty<T>)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);
         }
 
         /// <summary>
@@ -621,7 +584,7 @@ namespace Perspex
         /// <returns>The <see cref="PriorityValue"/>.</returns>
         private PriorityValue CreatePriorityValue(PerspexProperty property)
         {
-            Func<PerspexObject, object, object> validate = property.GetValidationFunc(GetType());
+            var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType());
             Func<object, object> validate2 = null;
 
             if (validate != null)
@@ -673,7 +636,7 @@ namespace Perspex
             }
             else
             {
-                return property.GetDefaultValue(GetType());
+                return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
             }
         }
 

+ 63 - 342
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
 {
     /// <summary>
-    /// A perspex property.
+    /// Base class for perspex property metadata.
     /// </summary>
-    /// <remarks>
-    /// This class is analogous to DependencyProperty in WPF.
-    /// </remarks>
     public class PerspexProperty : IEquatable<PerspexProperty>
     {
         /// <summary>
@@ -29,16 +23,6 @@ namespace Perspex
         /// </summary>
         private static int s_nextId = 1;
 
-        /// <summary>
-        /// The default value provided when the property was first registered.
-        /// </summary>
-        private readonly object _defaultValue;
-
-        /// <summary>
-        /// The overridden default values for the property, by type.
-        /// </summary>
-        private readonly Dictionary<Type, object> _defaultValues;
-
         /// <summary>
         /// Observable fired when this property changes on any <see cref="PerspexObject"/>.
         /// </summary>
@@ -49,11 +33,6 @@ namespace Perspex
         /// </summary>
         private readonly Subject<PerspexPropertyChangedEventArgs> _changed;
 
-        /// <summary>
-        /// The validation functions for the property, by type.
-        /// </summary>
-        private readonly Dictionary<Type, Func<PerspexObject, object, object>> _validation;
-
         /// <summary>
         /// Gets the ID of the property.
         /// </summary>
@@ -65,26 +44,18 @@ namespace Perspex
         /// <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="notifying">
         /// 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.
         /// </param>
-        /// <param name="isAttached">Whether the property is an attached property.</param>
-        public PerspexProperty(
+        protected PerspexProperty(
             string name,
             Type valueType,
             Type ownerType,
-            object defaultValue,
-            bool inherits = false,
             BindingMode defaultBindingMode = BindingMode.Default,
-            Func<PerspexObject, object, object> validate = null,
-            Action<PerspexObject, bool> notifying = null,
-            bool isAttached = false)
+            Action<PerspexObject, bool> notifying = null)
         {
             Contract.Requires<ArgumentNullException>(name != null);
             Contract.Requires<ArgumentNullException>(valueType != null);
@@ -95,64 +66,15 @@ namespace Perspex
                 throw new ArgumentException("'name' may not contain periods.");
             }
 
-            _defaultValues = new Dictionary<Type, object>();
             _initialized = new Subject<PerspexPropertyChangedEventArgs>();
             _changed = new Subject<PerspexPropertyChangedEventArgs>();
-            _validation = new Dictionary<Type, Func<PerspexObject, object, object>>();
 
             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);
-            }
-        }
-
-        /// <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.");
-            }
-
-            _defaultValues = new Dictionary<Type, object>();
-            _initialized = new Subject<PerspexPropertyChangedEventArgs>();
-            _changed = new Subject<PerspexPropertyChangedEventArgs>();
-            _validation = new Dictionary<Type, Func<PerspexObject, object, object>>();
-
-            Name = name;
-            PropertyType = valueType;
-            OwnerType = ownerType;
-            Getter = getter;
-            Setter = setter;
-            IsDirect = true;
-            _id = s_nextId++;
         }
 
         /// <summary>
@@ -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;
         }
 
         /// <summary>
-        /// Initializes a new instance of the <see cref="PerspexProperty"/> class.
+        /// Gets the name of the property.
         /// </summary>
-        /// <param name="source">The direct property to copy.</param>
-        /// <param name="ownerType">The new owner type.</param>
-        /// <param name="getter">A new getter.</param>
-        /// <param name="setter">A new setter.</param>
-        protected PerspexProperty(
-            PerspexProperty source,
-            Type ownerType,
-            Func<PerspexObject, object> getter,
-            Action<PerspexObject, object> setter)
-        {
-            Contract.Requires<ArgumentNullException>(source != null);
-            Contract.Requires<ArgumentNullException>(ownerType != null);
-            Contract.Requires<ArgumentNullException>(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; }
 
         /// <summary>
-        /// Gets the name of the property.
+        /// Gets the full name of the property, wich includes the owner type in the case of
+        /// attached properties.
         /// </summary>
-        /// <value>
-        /// The name of the property.
-        /// </value>
-        public string Name { get; }
+        public virtual string FullName => Name;
 
         /// <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.
+        /// Gets the type of the class that registered 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; }
+        public virtual bool Inherits => false;
 
         /// <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; }
+        public virtual bool IsAttached => false;
 
         /// <summary>
         /// Gets a value indicating whether this is a direct property.
         /// </summary>
-        public bool IsDirect { get; }
+        public virtual bool IsDirect => false;
 
         /// <summary>
         /// Gets a value indicating whether this is a readonly property.
         /// </summary>
-        public bool IsReadOnly => IsDirect && Setter == null;
+        public virtual bool IsReadOnly => false;
 
         /// <summary>
         /// Gets an observable that is fired when this property is initialized on a
@@ -349,16 +216,6 @@ namespace Perspex
             };
         }
 
-        /// <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>
@@ -407,55 +264,26 @@ namespace Perspex
         /// object; the bool argument will be true before and false afterwards. This callback is
         /// intended to support IsDataContextChanging.
         /// </param>
-        /// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
-        public static PerspexProperty<TValue> Register<TOwner, TValue>(
+        /// <returns>A <see cref="StyledProperty{TValue}"/></returns>
+        public static StyledProperty<TValue> Register<TOwner, TValue>(
             string name,
             TValue defaultValue = default(TValue),
             bool inherits = false,
             BindingMode defaultBindingMode = BindingMode.OneWay,
             Func<TOwner, TValue, TValue> validate = null,
-            Action<PerspexObject, bool> notifying = null)
-            where TOwner : PerspexObject
+            Action<IPerspexObject, bool> notifying = null)
+            where TOwner : IPerspexObject
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
-            PerspexProperty<TValue> result = new PerspexProperty<TValue>(
+            var result = new StyledProperty<TValue>(
                 name,
                 typeof(TOwner),
                 defaultValue,
                 inherits,
                 defaultBindingMode,
                 Cast(validate),
-                notifying,
-                false);
-
-            PerspexPropertyRegistry.Instance.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));
+                notifying);
 
             PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
 
@@ -474,24 +302,23 @@ namespace Perspex
         /// <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>(
+        public static AttachedProperty<TValue> RegisterAttached<TOwner, THost, TValue>(
             string name,
             TValue defaultValue = default(TValue),
             bool inherits = false,
             BindingMode defaultBindingMode = BindingMode.OneWay,
-            Func<PerspexObject, TValue, TValue> validate = null)
+            Func<THost, TValue, TValue> validate = null)
+                where THost : IPerspexObject
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
-            PerspexProperty<TValue> result = new PerspexProperty<TValue>(
+            var result = new AttachedProperty<TValue>(
                 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<PerspexObject, TValue, TValue> validate = null)
+            Func<THost, TValue, TValue> validate = null)
+                where THost : IPerspexObject
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
-            PerspexProperty<TValue> result = new PerspexProperty<TValue>(
+            var result = new AttachedProperty<TValue>(
                 name,
                 ownerType,
                 defaultValue,
                 inherits,
                 defaultBindingMode,
-                validate,
-                null,
-                true);
+                Cast(validate));
 
             PerspexPropertyRegistry.Instance.Register(typeof(THost), 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 DirectProperty<TOwner, TValue> RegisterDirect<TOwner, TValue>(
+            string name,
+            Func<TOwner, TValue> getter,
+            Action<TOwner, TValue> setter = null)
+                where TOwner : IPerspexObject
+        {
+            Contract.Requires<ArgumentNullException>(name != null);
+
+            var result = new DirectProperty<TOwner, TValue>(name, getter, setter);
+            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
+            return result;
+        }
+
         /// <inheritdoc/>
         public override bool Equals(object obj)
         {
@@ -570,56 +418,6 @@ namespace Perspex
             };
         }
 
-        /// <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 _defaultValue;
-        }
-
-        /// <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>
@@ -630,59 +428,6 @@ namespace Perspex
             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>
@@ -711,45 +456,24 @@ namespace Perspex
         }
 
         /// <summary>
-        /// Casts a getter function accepting a typed owner to one accepting a
-        /// <see cref="PerspexObject"/>.
+        /// Casts a validation function accepting a typed owner to one accepting an
+        /// <see cref="IPerspexObject"/>.
         /// </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
+        protected static Func<IPerspexObject, TValue, TValue> Cast<TOwner, TValue>(Func<TOwner, TValue, TValue> f)
+            where TOwner : IPerspexObject
         {
-            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;
+            if (f == null)
+            {
+                return null;
+            }
+            else
+            {
+                return (o, v) => f((TOwner)o, v);
+            }
         }
 
         /// <summary>
@@ -761,10 +485,7 @@ namespace Perspex
             /// Returns the string representation of the <see cref="UnsetValue"/>.
             /// </summary>
             /// <returns>The string "(unset)".</returns>
-            public override string ToString()
-            {
-                return "(unset)";
-            }
+            public override string ToString() => "(unset)";
         }
     }
 }

+ 4 - 187
src/Perspex.Base/PerspexProperty`1.cs

@@ -17,217 +17,34 @@ namespace Perspex
         /// </summary>
         /// <param name="name">The name of the property.</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="notifying">
         /// 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.
         /// </param>
-        /// <param name="isAttached">Whether the property is an attached property.</param>
-        public PerspexProperty(
+        protected PerspexProperty(
             string name,
             Type ownerType,
-            TValue defaultValue = default(TValue),
-            bool inherits = false,
             BindingMode defaultBindingMode = BindingMode.Default,
-            Func<PerspexObject, TValue, TValue> validate = null,
-            Action<PerspexObject, bool> notifying = null,
-            bool isAttached = false)
+            Action<PerspexObject, bool> notifying = null)
             : base(
                 name,
                 typeof(TValue),
                 ownerType,
-                defaultValue,
-                inherits,
                 defaultBindingMode,
-                Cast(validate),
-                notifying,
-                isAttached)
+                notifying)
         {
         }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="PerspexProperty{TValue}"/> class.
-        /// </summary>
-        /// <param name="name">The name of the property.</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 ownerType,
-            Func<PerspexObject, TValue> getter,
-            Action<PerspexObject, TValue> setter)
-            : base(name, typeof(TValue), ownerType, CastParamReturn(getter), CastParams(setter))
-        {
-            Getter = getter;
-            Setter = setter;
-        }
-
         /// <summary>
         /// Initializes a new instance of the <see cref="PerspexProperty"/> class.
         /// </summary>
         /// <param name="source">The property to copy.</param>
         /// <param name="ownerType">The new owner type.</param>
-        private PerspexProperty(PerspexProperty source, Type ownerType)
+        protected PerspexProperty(PerspexProperty source, Type ownerType)
             : base(source, ownerType)
         {
         }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="PerspexProperty"/> class.
-        /// </summary>
-        /// <param name="source">The direct property to copy.</param>
-        /// <param name="ownerType">The new owner type.</param>
-        /// <param name="getter">A new getter.</param>
-        /// <param name="setter">A new setter.</param>
-        private PerspexProperty(
-            PerspexProperty source,
-            Type ownerType,
-            Func<PerspexObject, TValue> getter,
-            Action<PerspexObject, TValue> setter)
-            : base(source, ownerType, CastParamReturn(getter), CastParams(setter))
-        {
-            Getter = getter;
-            Setter = setter;
-        }
-
-        /// <summary>
-        /// Gets the getter function for direct properties.
-        /// </summary>
-        internal new Func<PerspexObject, TValue> Getter { get; }
-
-        /// <summary>
-        /// Gets the etter function for direct properties.
-        /// </summary>
-        internal new Action<PerspexObject, TValue> Setter { get; }
-
-        /// <summary>
-        /// Registers the property on another type.
-        /// </summary>
-        /// <typeparam name="TOwner">The type of the additional owner.</typeparam>
-        /// <returns>The property.</returns>
-        public PerspexProperty<TValue> AddOwner<TOwner>() 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<TValue>(this, typeof(TOwner));
-            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
-            return result;
-        }
-
-        /// <summary>
-        /// Registers the direct property on another type.
-        /// </summary>
-        /// <typeparam name="TOwner">The type of the additional owner.</typeparam>
-        /// <returns>The property.</returns>
-        public PerspexProperty<TValue> AddOwner<TOwner>(
-            Func<TOwner, TValue> getter,
-            Action<TOwner, TValue> setter = null)
-                where TOwner : PerspexObject
-        {
-            if (!IsDirect)
-            {
-                throw new InvalidOperationException(
-                    "This overload of AddOwner is for direct PerspexProperties.");
-            }
-
-            var result = new PerspexProperty<TValue>(
-                this,
-                typeof(TOwner),
-                CastReturn(getter), 
-                CastParam1(setter));
-
-            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
-            return result;
-        }
-
-        /// <summary>
-        /// Gets the default value for the property on the specified type.
-        /// </summary>
-        /// <typeparam name="T">The type.</typeparam>
-        /// <returns>The default value.</returns>
-        public TValue GetDefaultValue<T>()
-        {
-            return (TValue)GetDefaultValue(typeof(T));
-        }
-
-        /// <summary>
-        /// Overrides the validation function for the property on the specified type.
-        /// </summary>
-        /// <typeparam name="T">The type.</typeparam>
-        /// <param name="validation">The validation function.</param>
-        public void OverrideValidation<T>(Func<T, TValue, TValue> validation) where T : PerspexObject
-        {
-            var f = validation != null ?
-                (o, v) => validation((T)o, (TValue)v) :
-                (Func<PerspexObject, object, object>)null;
-            OverrideValidation(typeof(T), f);
-        }
-
-        /// <summary>
-        /// Casts a typed getter function to an untyped.
-        /// </summary>
-        /// <typeparam name="TOwner">The owner type.</typeparam>
-        /// <param name="f">The typed function.</param>
-        /// <returns>The untyped function.</returns>
-        private static Func<PerspexObject, object> CastParamReturn<TOwner>(Func<TOwner, TValue> f)
-            where TOwner : PerspexObject
-        {
-            return (f != null) ? o => f((TOwner)o) : (Func<PerspexObject, object>)null;
-        }
-
-        /// <summary>
-        /// Casts a typed getter function to an untyped.
-        /// </summary>
-        /// <typeparam name="TOwner">The owner type.</typeparam>
-        /// <param name="f">The typed function.</param>
-        /// <returns>The untyped function.</returns>
-        private static Func<PerspexObject, TValue> CastReturn<TOwner>(Func<TOwner, TValue> f)
-            where TOwner : PerspexObject
-        {
-            return (f != null) ? o => f((TOwner)o) : (Func<PerspexObject, TValue>)null;
-        }
-
-        /// <summary>
-        /// Casts a typed setter function to an untyped.
-        /// </summary>
-        /// <typeparam name="TOwner">The owner type.</typeparam>
-        /// <param name="f">The typed function.</param>
-        /// <returns>The untyped function.</returns>
-        private static Action<PerspexObject, object> CastParams<TOwner>(Action<TOwner, TValue> f)
-            where TOwner : PerspexObject
-        {
-            return (f != null) ? (o, v) => f((TOwner)o, (TValue)v) : (Action<PerspexObject, object>)null;
-        }
-
-        /// <summary>
-        /// Casts a typed setter function to an untyped.
-        /// </summary>
-        /// <typeparam name="TOwner">The owner type.</typeparam>
-        /// <param name="f">The typed function.</param>
-        /// <returns>The untyped function.</returns>
-        private static Action<PerspexObject, TValue> CastParam1<TOwner>(Action<TOwner, TValue> f)
-            where TOwner : PerspexObject
-        {
-            return (f != null) ? (o, v) => f((TOwner)o, v) : (Action<PerspexObject, TValue>)null;
-        }
-
-        /// <summary>
-        /// Casts a typed validation function to an untyped.
-        /// </summary>
-        /// <param name="f">The typed validation function.</param>
-        /// <returns>The untyped validation function.</returns>
-        private static Func<PerspexObject, object, object> Cast(Func<PerspexObject, TValue, TValue> f)
-        {
-            return f != null ? (o, v) => f(o, (TValue)v) : (Func<PerspexObject, object, object>)null;
-        }
     }
 }

+ 40 - 0
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
+{
+    /// <summary>
+    /// A styled perspex property.
+    /// </summary>
+    public class StyledProperty<TValue> : StyledPropertyBase<TValue>
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
+        /// </summary>
+        /// <param name="name">The name of the property.</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="notifying">
+        /// 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.
+        /// </param>
+        public StyledProperty(
+            string name,
+            Type ownerType,
+            TValue defaultValue,
+            bool inherits = false,
+            BindingMode defaultBindingMode = BindingMode.Default,
+            Func<IPerspexObject, TValue, TValue> validate = null,
+            Action<IPerspexObject, bool> notifying = null)
+                : base(name, ownerType, defaultValue, inherits, defaultBindingMode, validate, notifying)
+        {
+        }
+    }
+}

+ 204 - 0
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
+{
+    /// <summary>
+    /// Base class for styled properties.
+    /// </summary>
+    public class StyledPropertyBase<TValue> : PerspexProperty<TValue>, IStyledPropertyAccessor
+    {
+        private readonly TValue _defaultValue;
+        private readonly Dictionary<Type, TValue> _defaultValues;
+        private bool _inherits;
+        private readonly Dictionary<Type, Func<IPerspexObject, TValue, TValue>> _validation;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
+        /// </summary>
+        /// <param name="name">The name of the property.</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="notifying">
+        /// 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.
+        /// </param>
+        protected StyledPropertyBase(
+            string name,
+            Type ownerType,
+            TValue defaultValue,
+            bool inherits = false,
+            BindingMode defaultBindingMode = BindingMode.Default,
+            Func<IPerspexObject, TValue, TValue> validate = null,
+            Action<IPerspexObject, bool> notifying = null)
+                : base(name, ownerType, defaultBindingMode, notifying)
+        {
+            Contract.Requires<ArgumentNullException>(name != null);
+            Contract.Requires<ArgumentNullException>(ownerType != null);
+
+            if (name.Contains("."))
+            {
+                throw new ArgumentException("'name' may not contain periods.");
+            }
+
+            _defaultValues = new Dictionary<Type, TValue>();
+            _validation = new Dictionary<Type, Func<IPerspexObject, TValue, TValue>>();
+
+            _defaultValue = defaultValue;
+            _inherits = inherits;
+
+            if (validate != null)
+            {
+                _validation.Add(ownerType, validate);
+            }
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether the property inherits its value.
+        /// </summary>
+        /// <value>
+        /// A value indicating whether the property inherits its value.
+        /// </value>
+        public override bool Inherits => _inherits;
+
+        /// <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 TValue GetDefaultValue(Type type)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+
+            while (type != null)
+            {
+                TValue result;
+
+                if (_defaultValues.TryGetValue(type, out result))
+                {
+                    return result;
+                }
+
+                type = type.GetTypeInfo().BaseType;
+            }
+
+            return _defaultValue;
+        }
+
+        /// <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<IPerspexObject, TValue, TValue> GetValidationFunc(Type type)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+
+            while (type != null)
+            {
+                Func<IPerspexObject, TValue, TValue> result;
+
+                if (_validation.TryGetValue(type, out result))
+                {
+                    return result;
+                }
+
+                type = type.GetTypeInfo().BaseType;
+            }
+
+            return null;
+        }
+
+        /// <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>(TValue defaultValue) where T : IPerspexObject
+        {
+            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, TValue defaultValue)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+
+            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>
+        /// <typeparam name="T">The type.</typeparam>
+        /// <param name="validation">The validation function.</param>
+        public void OverrideValidation<T>(Func<T, TValue, TValue> 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));
+        }
+
+        /// <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<IPerspexObject, TValue, TValue> 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;
+        }
+
+        /// <inheritdoc/>
+        Func<IPerspexObject, object, object> IStyledPropertyAccessor.GetValidationFunc(Type type)
+        {
+            var typed = GetValidationFunc(type);
+            return (o, v) => typed(o, (TValue)v);
+        }
+
+        /// <inheritdoc/>
+        object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultValue(type);
+    }
+}

+ 26 - 2
src/Perspex.Controls/Grid.cs

@@ -17,7 +17,9 @@ namespace Perspex.Controls
         /// Defines the Column attached property.
         /// </summary>
         public static readonly PerspexProperty<int> ColumnProperty =
-            PerspexProperty.RegisterAttached<Grid, Control, int>("Column");
+            PerspexProperty.RegisterAttached<Grid, Control, int>(
+                "Column",
+                validate: ValidateColumn);
 
         /// <summary>
         /// Defines the ColumnSpan attached property.
@@ -29,7 +31,9 @@ namespace Perspex.Controls
         /// Defines the Row attached property.
         /// </summary>
         public static readonly PerspexProperty<int> RowProperty =
-            PerspexProperty.RegisterAttached<Grid, Control, int>("Row");
+            PerspexProperty.RegisterAttached<Grid, Control, int>(
+                "Row",
+                validate: ValidateRow);
 
         /// <summary>
         /// 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 ||

+ 3 - 3
tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs

@@ -341,13 +341,13 @@ namespace Perspex.Base.UnitTests
 
         private class Class1 : PerspexObject
         {
-            public static readonly PerspexProperty<string> FooProperty =
+            public static readonly DirectProperty<Class1, string> FooProperty =
                 PerspexProperty.RegisterDirect<Class1, string>("Foo", o => o.Foo, (o, v) => o.Foo = v);
 
-            public static readonly PerspexProperty<string> BarProperty =
+            public static readonly DirectProperty<Class1, string> BarProperty =
                 PerspexProperty.RegisterDirect<Class1, string>("Bar", o => o.Bar);
 
-            public static readonly PerspexProperty<int> BazProperty =
+            public static readonly DirectProperty<Class1, int> BazProperty =
                 PerspexProperty.RegisterDirect<Class1, int>("Bar", o => o.Baz, (o,v) => o.Baz = v);
 
             private string _foo = "initial";

+ 2 - 2
tests/Perspex.Base.UnitTests/PerspexObjectTests_GetValue.cs

@@ -55,10 +55,10 @@ namespace Perspex.Base.UnitTests
 
         private class Class1 : PerspexObject
         {
-            public static readonly PerspexProperty<string> FooProperty =
+            public static readonly StyledProperty<string> FooProperty =
                 PerspexProperty.Register<Class1, string>("Foo", "foodefault");
 
-            public static readonly PerspexProperty<string> BazProperty =
+            public static readonly StyledProperty<string> BazProperty =
                 PerspexProperty.Register<Class1, string>("Baz", "bazdefault", true);
         }
 

+ 2 - 2
tests/Perspex.Base.UnitTests/PerspexObjectTests_Inheritance.cs

@@ -77,10 +77,10 @@ namespace Perspex.Base.UnitTests
 
         private class Class1 : PerspexObject
         {
-            public static readonly PerspexProperty<string> FooProperty =
+            public static readonly StyledProperty<string> FooProperty =
                 PerspexProperty.Register<Class1, string>("Foo", "foodefault");
 
-            public static readonly PerspexProperty<string> BazProperty =
+            public static readonly StyledProperty<string> BazProperty =
                 PerspexProperty.Register<Class1, string>("Baz", "bazdefault", true);
         }
 

+ 2 - 2
tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs

@@ -71,7 +71,7 @@ namespace Perspex.Base.UnitTests
 
         private class Class1 : PerspexObject
         {
-            public static readonly PerspexProperty<int> QuxProperty =
+            public static readonly StyledProperty<int> QuxProperty =
                 PerspexProperty.Register<Class1, int>("Qux", validate: Validate);
 
             public Class1()
@@ -97,7 +97,7 @@ namespace Perspex.Base.UnitTests
 
         private class Class2 : PerspexObject
         {
-            public static readonly PerspexProperty<int> QuxProperty =
+            public static readonly StyledProperty<int> QuxProperty =
                 Class1.QuxProperty.AddOwner<Class2>();
 
             static Class2()