Browse Source

More WIP refactoring properties.

Compiles now but lots of tests failing.
Steven Kirk 9 years ago
parent
commit
d25e057ccc
44 changed files with 798 additions and 466 deletions
  1. 1 1
      src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs
  2. 16 12
      src/Perspex.Base/AttachedProperty.cs
  3. 1 1
      src/Perspex.Base/DirectProperty.cs
  4. 2 0
      src/Perspex.Base/Perspex.Base.csproj
  5. 18 16
      src/Perspex.Base/PerspexObject.cs
  6. 1 1
      src/Perspex.Base/PerspexObjectExtensions.cs
  7. 115 81
      src/Perspex.Base/PerspexProperty.cs
  8. 3 11
      src/Perspex.Base/PerspexProperty`1.cs
  9. 66 0
      src/Perspex.Base/PropertyMetadata.cs
  10. 25 15
      src/Perspex.Base/StyledProperty.cs
  11. 79 72
      src/Perspex.Base/StyledPropertyBase.cs
  12. 63 0
      src/Perspex.Base/StyledPropertyMetadata.cs
  13. 4 4
      src/Perspex.Controls/Border.cs
  14. 5 5
      src/Perspex.Controls/Button.cs
  15. 3 2
      src/Perspex.Controls/Carousel.cs
  16. 3 6
      src/Perspex.Controls/ContentControl.cs
  17. 1 3
      src/Perspex.Controls/Control.cs
  18. 2 3
      src/Perspex.Controls/Decorator.cs
  19. 2 2
      src/Perspex.Controls/HotkeyManager.cs
  20. 3 5
      src/Perspex.Controls/ItemsControl.cs
  21. 1 1
      src/Perspex.Controls/ListBoxItem.cs
  22. 5 5
      src/Perspex.Controls/MenuItem.cs
  23. 4 5
      src/Perspex.Controls/Presenters/ScrollContentPresenter.cs
  24. 1 1
      src/Perspex.Controls/Primitives/HeaderedContentControl.cs
  25. 0 2
      src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs
  26. 0 1
      src/Perspex.Controls/Primitives/PopupRoot.cs
  27. 3 3
      src/Perspex.Controls/Primitives/RangeBase.cs
  28. 3 3
      src/Perspex.Controls/Primitives/ScrollBar.cs
  29. 4 4
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  30. 8 9
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  31. 12 12
      src/Perspex.Controls/ScrollViewer.cs
  32. 12 9
      src/Perspex.Controls/TextBlock.cs
  33. 11 10
      src/Perspex.Controls/TextBox.cs
  34. 1 1
      src/Perspex.HtmlRenderer/HtmlControl.cs
  35. 7 7
      src/Perspex.Input/InputElement.cs
  36. 3 3
      src/Perspex.Input/KeyboardNavigation.cs
  37. 8 8
      src/Perspex.SceneGraph/Visual.cs
  38. 1 1
      src/Perspex.Styling/Styling/Setter.cs
  39. 26 0
      tests/Perspex.Base.UnitTests/AttachedPropertyTests.cs
  40. 87 0
      tests/Perspex.Base.UnitTests/DirectPropertyTests.cs
  41. 3 0
      tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
  42. 2 2
      tests/Perspex.Base.UnitTests/PerspexPropertyRegistryTests.cs
  43. 135 139
      tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs
  44. 48 0
      tests/Perspex.Base.UnitTests/StyledPropertyTests.cs

+ 1 - 1
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<object> subject)
         {
             var mode = Mode == BindingMode.Default ?
-                property.DefaultBindingMode : Mode;
+                property.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
 
             switch (mode)
             {

+ 16 - 12
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
         /// </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>
+        /// <param name="metadata">The property metadata.</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)
+            bool inherits,
+            StyledPropertyMetadata metadata)
+            : base(name, ownerType, inherits, metadata)
         {
         }
 
-        /// <inheritdoc/>
-        public override string FullName => OwnerType + "." + Name;
-
         /// <inheritdoc/>
         public override bool IsAttached => true;
+
+        /// <summary>
+        /// Attaches the property as a non-attached property on the specified type.
+        /// </summary>
+        /// <typeparam name="TOwner">The owner type.</typeparam>
+        /// <returns>The property.</returns>
+        public StyledProperty<TValue> AddOwner<TOwner>()
+        {
+            var result = new StyledProperty<TValue>(this, typeof(TOwner));
+            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
+            return result;
+        }
     }
 }

+ 1 - 1
src/Perspex.Base/DirectProperty.cs

@@ -28,7 +28,7 @@ namespace Perspex
             string name,
             Func<TOwner, TValue> getter,
             Action<TOwner, TValue> setter = null)
-            : base(name, typeof(TOwner))
+            : base(name, typeof(TOwner), new PropertyMetadata())
         {
             Contract.Requires<ArgumentNullException>(getter != null);
 

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

@@ -58,6 +58,7 @@
     <Compile Include="PerspexLocator.cs" />
     <Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />
     <Compile Include="PerspexObjectExtensions.cs" />
+    <Compile Include="PropertyMetadata.cs" />
     <Compile Include="StyledProperty.cs" />
     <Compile Include="StyledPropertyBase.cs" />
     <Compile Include="PerspexPropertyRegistry.cs" />
@@ -85,6 +86,7 @@
     <Compile Include="Reactive\AnonymousSubject`1.cs" />
     <Compile Include="Reactive\AnonymousSubject`2.cs" />
     <Compile Include="Reactive\PerspexObservable.cs" />
+    <Compile Include="StyledPropertyMetadata.cs" />
     <Compile Include="Threading\Dispatcher.cs" />
     <Compile Include="Threading\DispatcherPriority.cs" />
     <Compile Include="Threading\DispatcherTimer.cs" />

+ 18 - 16
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);
+            }
         }
 
         /// <summary>
@@ -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;
 

+ 1 - 1
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(

+ 115 - 81
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
 {
     /// <summary>
-    /// Base class for perspex property metadata.
+    /// Base class for perspex properties.
     /// </summary>
     public class PerspexProperty : IEquatable<PerspexProperty>
     {
@@ -18,25 +20,12 @@ namespace Perspex
         /// </summary>
         public static readonly object UnsetValue = new Unset();
 
-        /// <summary>
-        /// Gets the next ID that will be allocated to a property.
-        /// </summary>
         private static int s_nextId = 1;
-
-        /// <summary>
-        /// Observable fired when this property changes on any <see cref="PerspexObject"/>.
-        /// </summary>
+        private readonly int _id;
         private readonly Subject<PerspexPropertyChangedEventArgs> _initialized;
-
-        /// <summary>
-        /// Observable fired when this property changes on any <see cref="PerspexObject"/>.
-        /// </summary>
         private readonly Subject<PerspexPropertyChangedEventArgs> _changed;
-
-        /// <summary>
-        /// Gets the ID of the property.
-        /// </summary>
-        private readonly int _id;
+        private readonly PropertyMetadata _defaultMetadata;
+        private readonly Dictionary<Type, PropertyMetadata> _metadata;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="PerspexProperty"/> class.
@@ -44,22 +33,17 @@ 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="defaultBindingMode">The default binding mode for the property.</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="metadata">The property metadata.</param>
         protected PerspexProperty(
             string name,
             Type valueType,
             Type ownerType,
-            BindingMode defaultBindingMode = BindingMode.Default,
-            Action<PerspexObject, bool> notifying = null)
+            PropertyMetadata metadata)
         {
             Contract.Requires<ArgumentNullException>(name != null);
             Contract.Requires<ArgumentNullException>(valueType != null);
             Contract.Requires<ArgumentNullException>(ownerType != null);
+            Contract.Requires<ArgumentNullException>(metadata != null);
 
             if (name.Contains("."))
             {
@@ -68,13 +52,15 @@ namespace Perspex
 
             _initialized = new Subject<PerspexPropertyChangedEventArgs>();
             _changed = new Subject<PerspexPropertyChangedEventArgs>();
+            _metadata = new Dictionary<Type, PropertyMetadata>();
 
             Name = name;
             PropertyType = valueType;
             OwnerType = ownerType;
-            DefaultBindingMode = defaultBindingMode;
-            Notifying = notifying;
             _id = s_nextId++;
+
+            _metadata.Add(ownerType, metadata);
+            _defaultMetadata = metadata;
         }
 
         /// <summary>
@@ -87,19 +73,13 @@ namespace Perspex
             Contract.Requires<ArgumentNullException>(source != null);
             Contract.Requires<ArgumentNullException>(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
         /// </summary>
         public string Name { get; }
 
-        /// <summary>
-        /// Gets the full name of the property, wich includes the owner type in the case of
-        /// attached properties.
-        /// </summary>
-        public virtual string FullName => Name;
-
         /// <summary>
         /// Gets the type of the property's value.
         /// </summary>
@@ -130,11 +104,6 @@ namespace Perspex
         /// </summary>
         public virtual bool Inherits => false;
 
-        /// <summary>
-        /// Gets the default binding mode for the property.
-        /// </summary>
-        public BindingMode DefaultBindingMode { get; }
-
         /// <summary>
         /// Gets a value indicating whether this is an attached property.
         /// </summary>
@@ -272,21 +241,18 @@ namespace Perspex
             BindingMode defaultBindingMode = BindingMode.OneWay,
             Func<TOwner, TValue, TValue> validate = null,
             Action<IPerspexObject, bool> notifying = null)
-            where TOwner : IPerspexObject
+                where TOwner : IPerspexObject
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
-            var result = new StyledProperty<TValue>(
-                name,
-                typeof(TOwner),
+            var metadata = new StyledPropertyMetadata(
                 defaultValue,
-                inherits,
-                defaultBindingMode,
-                Cast(validate),
-                notifying);
+                validate: Cast(validate),
+                defaultBindingMode: defaultBindingMode,
+                notifyingCallback: notifying);
 
+            var result = new StyledProperty<TValue>(name, typeof(TOwner), inherits, metadata);
             PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
-
             return result;
         }
 
@@ -312,16 +278,13 @@ namespace Perspex
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
-            var result = new AttachedProperty<TValue>(
-                name,
-                typeof(TOwner),
+            var metadata = new StyledPropertyMetadata(
                 defaultValue,
-                inherits,
-                defaultBindingMode,
-                Cast(validate));
+                validate: Cast(validate),
+                defaultBindingMode: defaultBindingMode);
 
+            var result = new AttachedProperty<TValue>(name, typeof(TOwner), inherits, metadata);
             PerspexPropertyRegistry.Instance.Register(typeof(THost), result);
-
             return result;
         }
 
@@ -337,7 +300,7 @@ 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<THost, TValue>(
+        public static AttachedProperty<TValue> RegisterAttached<THost, TValue>(
             string name,
             Type ownerType,
             TValue defaultValue = default(TValue),
@@ -348,16 +311,13 @@ namespace Perspex
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
-            var result = new AttachedProperty<TValue>(
-                name,
-                ownerType,
+            var metadata = new StyledPropertyMetadata(
                 defaultValue,
-                inherits,
-                defaultBindingMode,
-                Cast(validate));
+                validate: Cast(validate),
+                defaultBindingMode: defaultBindingMode);
 
+            var result = new AttachedProperty<TValue>(name, ownerType, inherits, metadata);
             PerspexPropertyRegistry.Instance.Register(typeof(THost), result);
-
             return result;
         }
 
@@ -383,6 +343,22 @@ namespace Perspex
             return result;
         }
 
+        /// <summary>
+        /// Returns a binding accessor that can be passed to <see cref="PerspexObject"/>'s []
+        /// operator to initiate a binding.
+        /// </summary>
+        /// <returns>A <see cref="IndexerDescriptor"/>.</returns>
+        /// <remarks>
+        /// The ! and ~ operators are short forms of this.
+        /// </remarks>
+        public IndexerDescriptor Bind()
+        {
+            return new IndexerDescriptor
+            {
+                Property = this,
+            };
+        }
+
         /// <inheritdoc/>
         public override bool Equals(object obj)
         {
@@ -403,19 +379,56 @@ namespace Perspex
         }
 
         /// <summary>
-        /// Returns a binding accessor that can be passed to <see cref="PerspexObject"/>'s []
-        /// operator to initiate a binding.
+        /// Gets the property metadata for the specified type.
         /// </summary>
-        /// <returns>A <see cref="IndexerDescriptor"/>.</returns>
-        /// <remarks>
-        /// The ! and ~ operators are short forms of this.
-        /// </remarks>
-        public IndexerDescriptor Bind()
+        /// <typeparam name="T">The type.</typeparam>
+        /// <returns>
+        /// The property metadata.
+        /// </returns>
+        public PropertyMetadata GetMetadata<T>() 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;
+        }
+
+
+        /// <summary>
+        /// Gets the property metadata for the specified type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <returns>
+        /// The property metadata.
+        /// </returns>
+        public PropertyMetadata GetMetadata(Type type)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+
+            while (type != null)
+            {
+                PropertyMetadata result;
+
+                if (_metadata.TryGetValue(type, out result))
+                {
+                    return result;
+                }
+
+                type = type.GetTypeInfo().BaseType;
+            }
+
+            return _defaultMetadata;
         }
 
         /// <summary>
@@ -463,7 +476,7 @@ namespace Perspex
         /// <typeparam name="TValue">The property value type.</typeparam>
         /// <param name="f">The typed function.</param>
         /// <returns>The untyped function.</returns>
-        protected static Func<IPerspexObject, TValue, TValue> Cast<TOwner, TValue>(Func<TOwner, TValue, TValue> f)
+        protected static Func<IPerspexObject, object, object> Cast<TOwner, TValue>(Func<TOwner, TValue, TValue> 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);
             }
         }
 
+        /// <summary>
+        /// Overrides the metadata for the property on the specified type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <param name="metadata">The metadata.</param>
+        protected void OverrideMetadata(Type type, PropertyMetadata metadata)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+            Contract.Requires<ArgumentNullException>(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);
+        }
+
         /// <summary>
         /// Class representing the <see cref="UnsetValue"/>.
         /// </summary>

+ 3 - 11
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
         /// </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="defaultBindingMode">The default binding mode for the property.</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="metadata">The property metadata.</param>
         protected PerspexProperty(
             string name,
             Type ownerType,
-            BindingMode defaultBindingMode = BindingMode.Default,
-            Action<PerspexObject, bool> notifying = null)
+            PropertyMetadata metadata)
             : base(
                 name,
                 typeof(TValue),
                 ownerType,
-                defaultBindingMode,
-                notifying)
+                metadata)
         {
         }
 

+ 66 - 0
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
+{
+    /// <summary>
+    /// Base class for perspex property metadata.
+    /// </summary>
+    public class PropertyMetadata
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="PropertyMetadata"/> class.
+        /// </summary>
+        /// <param name="defaultBindingMode">The default binding mode.</param>
+        /// <param name="notifyingCallback">The property notifying callback.</param>
+        public PropertyMetadata(
+            BindingMode defaultBindingMode = BindingMode.Default,
+            Action<PerspexObject, bool> notifyingCallback = null)
+        {
+            DefaultBindingMode = defaultBindingMode;
+            NotifyingCallback = notifyingCallback;
+        }
+
+        /// <summary>
+        /// Gets the default binding mode for the property.
+        /// </summary>
+        public BindingMode DefaultBindingMode { get; private set; }
+
+        /// <summary>
+        /// Gets a method that gets called before and after the property starts being notified on an
+        /// object.
+        /// </summary>
+        /// <remarks>
+        /// When a property changes, change notifications are sent to all property subscribers; 
+        /// for example via the <see cref="PerspexProperty.Changed"/> observable and and the 
+        /// <see cref="PerspexObject.PropertyChanged"/> 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.
+        /// </remarks>
+        public Action<PerspexObject, bool> NotifyingCallback { get; private set; }
+
+        /// <summary>
+        /// Merges the metadata with the base metadata.
+        /// </summary>
+        /// <param name="baseMetadata">The base metadata to merge.</param>
+        /// <param name="property">The property to which the metadata is being applied.</param>
+        public virtual void Merge(
+            PropertyMetadata baseMetadata, 
+            PerspexProperty property)
+        {
+            if (DefaultBindingMode == BindingMode.Default)
+            {
+                DefaultBindingMode = baseMetadata.DefaultBindingMode;
+            }
+
+            if (NotifyingCallback == null)
+            {
+                NotifyingCallback = baseMetadata.NotifyingCallback;
+            }
+        }
+    }
+}

+ 25 - 15
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
         /// </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="metadata">The property metadata.</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)
+            bool inherits,
+            StyledPropertyMetadata metadata)
+                : base(name, ownerType, inherits, metadata)
         {
         }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
+        /// </summary>
+        /// <param name="source">The property to add the owner to.</param>
+        /// <param name="ownerType">The type of the class that registers the property.</param>
+        internal StyledProperty(StyledPropertyBase<TValue> source, Type ownerType)
+            : base(source, ownerType)
+        {
+        }
+        
+        /// <summary>
+        /// Registers the property on another type.
+        /// </summary>
+        /// <typeparam name="TOwner">The type of the additional owner.</typeparam>
+        /// <returns>The property.</returns>        
+        public StyledProperty<TValue> AddOwner<TOwner>()
+        {
+            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), this);
+            return this;
+        }
     }
 }

+ 79 - 72
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
     /// </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>
+        /// <param name="metadata">The property metadata.</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)
+            bool inherits,
+            StyledPropertyMetadata metadata)
+                : base(name, ownerType, CheckMetadata(metadata))
         {
             Contract.Requires<ArgumentNullException>(name != null);
             Contract.Requires<ArgumentNullException>(ownerType != null);
@@ -50,16 +36,18 @@ namespace Perspex
                 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>
+        /// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
+        /// </summary>
+        /// <param name="source">The property to add the owner to.</param>
+        /// <param name="ownerType">The type of the class that registers the property.</param>
+        protected StyledPropertyBase(StyledPropertyBase<TValue> source, Type ownerType)
+            : base(source, ownerType)
+        {
+            _inherits = source.Inherits;
         }
 
         /// <summary>
@@ -79,19 +67,7 @@ namespace Perspex
         {
             Contract.Requires<ArgumentNullException>(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;
         }
 
         /// <summary>
@@ -103,21 +79,22 @@ namespace Perspex
         /// </returns>
         public Func<IPerspexObject, TValue, TValue> GetValidationFunc(Type type)
         {
-            Contract.Requires<ArgumentNullException>(type != null);
+            return null;
+            ////Contract.Requires<ArgumentNullException>(type != null);
 
-            while (type != null)
-            {
-                Func<IPerspexObject, TValue, TValue> result;
+            ////while (type != null)
+            ////{
+            ////    Func<IPerspexObject, TValue, TValue> 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;
         }
 
         /// <summary>
@@ -137,14 +114,7 @@ namespace Perspex
         /// <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);
+            OverrideMetadata(type, new StyledPropertyMetadata(defaultValue));
         }
 
         /// <summary>
@@ -155,14 +125,15 @@ namespace Perspex
         public void OverrideValidation<T>(Func<T, TValue, TValue> 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));
         }
 
         /// <summary>
@@ -172,14 +143,15 @@ namespace Perspex
         /// <param name="validation">The validation function.</param>
         public void OverrideValidation(Type type, Func<IPerspexObject, TValue, TValue> validation)
         {
-            Contract.Requires<ArgumentNullException>(type != null);
+            throw new NotImplementedException();
+            //Contract.Requires<ArgumentNullException>(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);
         }
 
         /// <summary>
@@ -195,10 +167,45 @@ namespace Perspex
         Func<IPerspexObject, object, object> 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;
+            }
         }
 
         /// <inheritdoc/>
         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;
+        }
     }
 }

+ 63 - 0
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
+{
+    /// <summary>
+    /// Styled perspex property metadata.
+    /// </summary>
+    public class StyledPropertyMetadata : PropertyMetadata
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StyledPropertyMetadata"/> class.
+        /// </summary>
+        /// <param name="defaultValue">The default value of the property.</param>
+        /// <param name="validate">A validation function.</param>
+        /// <param name="defaultBindingMode">The default binding mode.</param>
+        /// <param name="notifyingCallback">The property notifying callback.</param>
+        public StyledPropertyMetadata(
+            object defaultValue,
+            Func<IPerspexObject, object, object> validate = null,
+            BindingMode defaultBindingMode = BindingMode.Default,
+            Action<IPerspexObject, bool> notifyingCallback = null)
+                : base(defaultBindingMode, notifyingCallback)
+        {
+            DefaultValue = defaultValue;
+            Validate = validate;
+        }
+
+        /// <summary>
+        /// Gets the default value for the property.
+        /// </summary>
+        public object DefaultValue { get; private set; }
+
+        /// <summary>
+        /// Gets the validation callback.
+        /// </summary>
+        public Func<IPerspexObject, object, object> Validate { get; private set; }
+
+        /// <inheritdoc/>
+        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;
+                }
+            }
+        }
+    }
+}

+ 4 - 4
src/Perspex.Controls/Border.cs

@@ -13,25 +13,25 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Background"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Brush> BackgroundProperty =
+        public static readonly StyledProperty<Brush> BackgroundProperty =
             PerspexProperty.Register<Border, Brush>(nameof(Background));
 
         /// <summary>
         /// Defines the <see cref="BorderBrush"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Brush> BorderBrushProperty =
+        public static readonly StyledProperty<Brush> BorderBrushProperty =
             PerspexProperty.Register<Border, Brush>(nameof(BorderBrush));
 
         /// <summary>
         /// Defines the <see cref="BorderThickness"/> property.
         /// </summary>
-        public static readonly PerspexProperty<double> BorderThicknessProperty =
+        public static readonly StyledProperty<double> BorderThicknessProperty =
             PerspexProperty.Register<Border, double>(nameof(BorderThickness));
 
         /// <summary>
         /// Defines the <see cref="CornerRadius"/> property.
         /// </summary>
-        public static readonly PerspexProperty<float> CornerRadiusProperty =
+        public static readonly StyledProperty<float> CornerRadiusProperty =
             PerspexProperty.Register<Border, float>(nameof(CornerRadius));
 
         /// <summary>

+ 5 - 5
src/Perspex.Controls/Button.cs

@@ -35,31 +35,31 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="ClickMode"/> property.
         /// </summary>
-        public static readonly PerspexProperty<ClickMode> ClickModeProperty =
+        public static readonly StyledProperty<ClickMode> ClickModeProperty =
             PerspexProperty.Register<Button, ClickMode>(nameof(ClickMode));
 
         /// <summary>
         /// Defines the <see cref="Command"/> property.
         /// </summary>
-        public static readonly PerspexProperty<ICommand> CommandProperty =
+        public static readonly StyledProperty<ICommand> CommandProperty =
             PerspexProperty.Register<Button, ICommand>(nameof(Command));
 
         /// <summary>
         /// Defines the <see cref="HotKey"/> property.
         /// </summary>
-        public static readonly PerspexProperty<KeyGesture> HotKeyProperty =
+        public static readonly StyledProperty<KeyGesture> HotKeyProperty =
             HotKeyManager.HotKeyProperty.AddOwner<Button>();
 
         /// <summary>
         /// Defines the <see cref="CommandParameter"/> property.
         /// </summary>
-        public static readonly PerspexProperty<object> CommandParameterProperty =
+        public static readonly StyledProperty<object> CommandParameterProperty =
             PerspexProperty.Register<Button, object>(nameof(CommandParameter));
 
         /// <summary>
         /// Defines the <see cref="IsDefaultProperty"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> IsDefaultProperty =
+        public static readonly StyledProperty<bool> IsDefaultProperty =
             PerspexProperty.Register<Button, bool>(nameof(IsDefault));
 
         /// <summary>

+ 3 - 2
src/Perspex.Controls/Carousel.cs

@@ -17,11 +17,12 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Transition"/> property.
         /// </summary>
-        public static readonly PerspexProperty<IPageTransition> TransitionProperty =
+        public static readonly StyledProperty<IPageTransition> TransitionProperty =
             PerspexProperty.Register<Carousel, IPageTransition>("Transition");
 
         /// <summary>
-        /// The default value of <see cref="IReparentingControl"/> for <see cref="Carousel"/>.
+        /// The default value of <see cref="ItemsControl.ItemsPanelProperty"/> for 
+        /// <see cref="Carousel"/>.
         /// </summary>
         private static readonly ITemplate<IPanel> PanelTemplate =
             new FuncTemplate<IPanel>(() => new Panel());

+ 3 - 6
src/Perspex.Controls/ContentControl.cs

@@ -1,9 +1,6 @@
 // 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.Linq;
-using System.Reactive.Linq;
 using Perspex.Controls.Mixins;
 using Perspex.Controls.Presenters;
 using Perspex.Controls.Primitives;
@@ -21,19 +18,19 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Content"/> property.
         /// </summary>
-        public static readonly PerspexProperty<object> ContentProperty =
+        public static readonly StyledProperty<object> ContentProperty =
             PerspexProperty.Register<ContentControl, object>(nameof(Content));
 
         /// <summary>
         /// Defines the <see cref="HorizontalContentAlignment"/> property.
         /// </summary>
-        public static readonly PerspexProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
+        public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
             PerspexProperty.Register<ContentControl, HorizontalAlignment>(nameof(HorizontalContentAlignment));
 
         /// <summary>
         /// Defines the <see cref="VerticalContentAlignment"/> property.
         /// </summary>
-        public static readonly PerspexProperty<VerticalAlignment> VerticalContentAlignmentProperty =
+        public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
             PerspexProperty.Register<ContentControl, VerticalAlignment>(nameof(VerticalContentAlignment));
 
         /// <summary>

+ 1 - 3
src/Perspex.Controls/Control.cs

@@ -14,8 +14,6 @@ using Perspex.Controls.Templates;
 using Perspex.Data;
 using Perspex.Input;
 using Perspex.Interactivity;
-using Perspex.LogicalTree;
-using Perspex.Rendering;
 using Perspex.Styling;
 
 namespace Perspex.Controls
@@ -508,7 +506,7 @@ namespace Perspex.Controls
         /// </summary>
         /// <param name="o">The object on which the DataContext is changing.</param>
         /// <param name="notifying">Whether the notifcation is beginning or ending.</param>
-        private static void DataContextNotifying(PerspexObject o, bool notifying)
+        private static void DataContextNotifying(IPerspexObject o, bool notifying)
         {
             var control = o as Control;
 

+ 2 - 3
src/Perspex.Controls/Decorator.cs

@@ -1,7 +1,6 @@
 // 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 Perspex.Collections;
 using Perspex.Metadata;
 
 namespace Perspex.Controls
@@ -14,13 +13,13 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Child"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Control> ChildProperty =
+        public static readonly StyledProperty<Control> ChildProperty =
             PerspexProperty.Register<Decorator, Control>(nameof(Child));
 
         /// <summary>
         /// Defines the <see cref="Padding"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Thickness> PaddingProperty =
+        public static readonly StyledProperty<Thickness> PaddingProperty =
             PerspexProperty.Register<Decorator, Thickness>(nameof(Padding));
 
         /// <summary>

+ 2 - 2
src/Perspex.Controls/HotkeyManager.cs

@@ -12,8 +12,8 @@ namespace Perspex.Controls
 {
     public class HotKeyManager
     {
-        public static readonly PerspexProperty<KeyGesture> HotKeyProperty
-            = PerspexProperty.RegisterAttached<Control, KeyGesture>("HotKey", typeof (HotKeyManager));
+        public static readonly AttachedProperty<KeyGesture> HotKeyProperty
+            = PerspexProperty.RegisterAttached<Control, KeyGesture>("HotKey", typeof(HotKeyManager));
 
         class HotkeyCommandWrapper : ICommand
         {

+ 3 - 5
src/Perspex.Controls/ItemsControl.cs

@@ -1,7 +1,6 @@
 // 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;
 using System.Collections.Generic;
 using System.Collections.Specialized;
@@ -14,7 +13,6 @@ using Perspex.Controls.Primitives;
 using Perspex.Controls.Templates;
 using Perspex.Controls.Utils;
 using Perspex.Metadata;
-using Perspex.Styling;
 
 namespace Perspex.Controls
 {
@@ -33,19 +31,19 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Items"/> property.
         /// </summary>
-        public static readonly PerspexProperty<IEnumerable> ItemsProperty =
+        public static readonly DirectProperty<ItemsControl, IEnumerable> ItemsProperty =
             PerspexProperty.RegisterDirect<ItemsControl, IEnumerable>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
 
         /// <summary>
         /// Defines the <see cref="ItemsPanel"/> property.
         /// </summary>
-        public static readonly PerspexProperty<ITemplate<IPanel>> ItemsPanelProperty =
+        public static readonly StyledProperty<ITemplate<IPanel>> ItemsPanelProperty =
             PerspexProperty.Register<ItemsControl, ITemplate<IPanel>>(nameof(ItemsPanel), DefaultPanel);
 
         /// <summary>
         /// Defines the <see cref="MemberSelector"/> property.
         /// </summary>
-        public static readonly PerspexProperty<IMemberSelector> MemberSelectorProperty =
+        public static readonly StyledProperty<IMemberSelector> MemberSelectorProperty =
             PerspexProperty.Register<ItemsControl, IMemberSelector>(nameof(MemberSelector));
 
         private IEnumerable _items = new PerspexList<object>();

+ 1 - 1
src/Perspex.Controls/ListBoxItem.cs

@@ -13,7 +13,7 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="IsSelected"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> IsSelectedProperty =
+        public static readonly StyledProperty<bool> IsSelectedProperty =
             PerspexProperty.Register<ListBoxItem, bool>(nameof(IsSelected));
 
         /// <summary>

+ 5 - 5
src/Perspex.Controls/MenuItem.cs

@@ -21,31 +21,31 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Command"/> property.
         /// </summary>
-        public static readonly PerspexProperty<ICommand> CommandProperty =
+        public static readonly StyledProperty<ICommand> CommandProperty =
             Button.CommandProperty.AddOwner<MenuItem>();
 
         /// <summary>
         /// Defines the <see cref="HotKey"/> property.
         /// </summary>
-        public static readonly PerspexProperty<KeyGesture> HotKeyProperty =
+        public static readonly StyledProperty<KeyGesture> HotKeyProperty =
             HotKeyManager.HotKeyProperty.AddOwner<MenuItem>();
 
         /// <summary>
         /// Defines the <see cref="CommandParameter"/> property.
         /// </summary>
-        public static readonly PerspexProperty<object> CommandParameterProperty =
+        public static readonly StyledProperty<object> CommandParameterProperty =
             Button.CommandParameterProperty.AddOwner<MenuItem>();
 
         /// <summary>
         /// Defines the <see cref="Icon"/> property.
         /// </summary>
-        public static readonly PerspexProperty<object> IconProperty =
+        public static readonly StyledProperty<object> IconProperty =
             PerspexProperty.Register<MenuItem, object>(nameof(Icon));
 
         /// <summary>
         /// Defines the <see cref="IsSelected"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> IsSelectedProperty =
+        public static readonly StyledProperty<bool> IsSelectedProperty =
             ListBoxItem.IsSelectedProperty.AddOwner<MenuItem>();
 
         /// <summary>

+ 4 - 5
src/Perspex.Controls/Presenters/ScrollContentPresenter.cs

@@ -20,7 +20,7 @@ namespace Perspex.Controls.Presenters
         /// <summary>
         /// Defines the <see cref="Extent"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Size> ExtentProperty =
+        public static readonly DirectProperty<ScrollContentPresenter, Size> ExtentProperty =
             ScrollViewer.ExtentProperty.AddOwner<ScrollContentPresenter>(
                 o => o.Extent,
                 (o, v) => o.Extent = v);
@@ -28,7 +28,7 @@ namespace Perspex.Controls.Presenters
         /// <summary>
         /// Defines the <see cref="Offset"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Vector> OffsetProperty =
+        public static readonly DirectProperty<ScrollContentPresenter, Vector> OffsetProperty =
             ScrollViewer.OffsetProperty.AddOwner<ScrollContentPresenter>(
                 o => o.Offset,
                 (o, v) => o.Offset = v);
@@ -36,7 +36,7 @@ namespace Perspex.Controls.Presenters
         /// <summary>
         /// Defines the <see cref="Viewport"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Size> ViewportProperty =
+        public static readonly DirectProperty<ScrollContentPresenter, Size> ViewportProperty =
             ScrollViewer.ViewportProperty.AddOwner<ScrollContentPresenter>(
                 o => o.Viewport,
                 (o, v) => o.Viewport = v);
@@ -44,7 +44,7 @@ namespace Perspex.Controls.Presenters
         /// <summary>
         /// Defines the <see cref="CanScrollHorizontally"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> CanScrollHorizontallyProperty =
+        public static readonly StyledProperty<bool> CanScrollHorizontallyProperty =
             PerspexProperty.Register<ScrollContentPresenter, bool>("CanScrollHorizontally", true);
 
         private Size _extent;
@@ -59,7 +59,6 @@ namespace Perspex.Controls.Presenters
         static ScrollContentPresenter()
         {
             ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
-            OffsetProperty.OverrideValidation<ScrollContentPresenter>(ValidateOffset);
             AffectsArrange(OffsetProperty);
         }
 

+ 1 - 1
src/Perspex.Controls/Primitives/HeaderedContentControl.cs

@@ -11,7 +11,7 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="Header"/> property.
         /// </summary>
-        public static readonly PerspexProperty<object> HeaderProperty =
+        public static readonly StyledProperty<object> HeaderProperty =
             PerspexProperty.Register<ContentControl, object>("Header");
 
         /// <summary>

+ 0 - 2
src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs

@@ -1,8 +1,6 @@
 // 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.Linq;
 using Perspex.Controls.Mixins;
 using Perspex.Controls.Presenters;
 

+ 0 - 1
src/Perspex.Controls/Primitives/PopupRoot.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.Collections;
 using Perspex.Controls.Platform;
 using Perspex.Controls.Presenters;
 using Perspex.Interactivity;

+ 3 - 3
src/Perspex.Controls/Primitives/RangeBase.cs

@@ -14,7 +14,7 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="Minimum"/> property.
         /// </summary>
-        public static readonly PerspexProperty<double> MinimumProperty =
+        public static readonly DirectProperty<RangeBase, double> MinimumProperty =
             PerspexProperty.RegisterDirect<RangeBase, double>(
                 nameof(Minimum),
                 o => o.Minimum,
@@ -23,7 +23,7 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="Maximum"/> property.
         /// </summary>
-        public static readonly PerspexProperty<double> MaximumProperty =
+        public static readonly DirectProperty<RangeBase, double> MaximumProperty =
             PerspexProperty.RegisterDirect<RangeBase, double>(
                 nameof(Maximum),
                 o => o.Maximum,
@@ -32,7 +32,7 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="Value"/> property.
         /// </summary>
-        public static readonly PerspexProperty<double> ValueProperty =
+        public static readonly DirectProperty<RangeBase, double> ValueProperty =
             PerspexProperty.RegisterDirect<RangeBase, double>(
                 nameof(Value),
                 o => o.Value,

+ 3 - 3
src/Perspex.Controls/Primitives/ScrollBar.cs

@@ -16,19 +16,19 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="ViewportSize"/> property.
         /// </summary>
-        public static readonly PerspexProperty<double> ViewportSizeProperty =
+        public static readonly StyledProperty<double> ViewportSizeProperty =
             PerspexProperty.Register<ScrollBar, double>(nameof(ViewportSize), defaultValue: double.NaN);
 
         /// <summary>
         /// Defines the <see cref="Visibility"/> property.
         /// </summary>
-        public static readonly PerspexProperty<ScrollBarVisibility> VisibilityProperty =
+        public static readonly StyledProperty<ScrollBarVisibility> VisibilityProperty =
             PerspexProperty.Register<ScrollBar, ScrollBarVisibility>(nameof(Visibility));
 
         /// <summary>
         /// Defines the <see cref="Orientation"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Orientation> OrientationProperty =
+        public static readonly StyledProperty<Orientation> OrientationProperty =
             PerspexProperty.Register<ScrollBar, Orientation>(nameof(Orientation));
 
         /// <summary>

+ 4 - 4
src/Perspex.Controls/Primitives/SelectingItemsControl.cs

@@ -40,7 +40,7 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="SelectedIndex"/> property.
         /// </summary>
-        public static readonly PerspexProperty<int> SelectedIndexProperty =
+        public static readonly DirectProperty<SelectingItemsControl, int> SelectedIndexProperty =
             PerspexProperty.RegisterDirect<SelectingItemsControl, int>(
                 nameof(SelectedIndex),
                 o => o.SelectedIndex,
@@ -49,7 +49,7 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="SelectedItem"/> property.
         /// </summary>
-        public static readonly PerspexProperty<object> SelectedItemProperty =
+        public static readonly DirectProperty<SelectingItemsControl, object> SelectedItemProperty =
             PerspexProperty.RegisterDirect<SelectingItemsControl, object>(
                 nameof(SelectedItem),
                 o => o.SelectedItem,
@@ -58,7 +58,7 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="SelectedItems"/> property.
         /// </summary>
-        protected static readonly PerspexProperty<IList> SelectedItemsProperty =
+        protected static readonly DirectProperty<SelectingItemsControl, IList> SelectedItemsProperty =
             PerspexProperty.RegisterDirect<SelectingItemsControl, IList>(
                 nameof(SelectedItems),
                 o => o.SelectedItems,
@@ -67,7 +67,7 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="SelectionMode"/> property.
         /// </summary>
-        protected static readonly PerspexProperty<SelectionMode> SelectionModeProperty =
+        protected static readonly StyledProperty<SelectionMode> SelectionModeProperty =
             PerspexProperty.Register<SelectingItemsControl, SelectionMode>(
                 nameof(SelectionMode));
 

+ 8 - 9
src/Perspex.Controls/Primitives/TemplatedControl.cs

@@ -4,7 +4,6 @@
 using System;
 using System.Linq;
 using System.Reactive.Linq;
-using Perspex.Controls.Presenters;
 using Perspex.Controls.Templates;
 using Perspex.Data;
 using Perspex.Interactivity;
@@ -24,49 +23,49 @@ namespace Perspex.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="Background"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Brush> BackgroundProperty =
+        public static readonly StyledProperty<Brush> BackgroundProperty =
             Border.BackgroundProperty.AddOwner<TemplatedControl>();
 
         /// <summary>
         /// Defines the <see cref="BorderBrush"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Brush> BorderBrushProperty =
+        public static readonly StyledProperty<Brush> BorderBrushProperty =
             Border.BorderBrushProperty.AddOwner<TemplatedControl>();
 
         /// <summary>
         /// Defines the <see cref="BorderThickness"/> property.
         /// </summary>
-        public static readonly PerspexProperty<double> BorderThicknessProperty =
+        public static readonly StyledProperty<double> BorderThicknessProperty =
             Border.BorderThicknessProperty.AddOwner<TemplatedControl>();
 
         /// <summary>
         /// Defines the <see cref="FontFamily"/> property.
         /// </summary>
-        public static readonly PerspexProperty<string> FontFamilyProperty =
+        public static readonly StyledProperty<string> FontFamilyProperty =
             TextBlock.FontFamilyProperty.AddOwner<TemplatedControl>();
 
         /// <summary>
         /// Defines the <see cref="FontSize"/> property.
         /// </summary>
-        public static readonly PerspexProperty<double> FontSizeProperty =
+        public static readonly StyledProperty<double> FontSizeProperty =
             TextBlock.FontSizeProperty.AddOwner<TemplatedControl>();
 
         /// <summary>
         /// Defines the <see cref="FontStyle"/> property.
         /// </summary>
-        public static readonly PerspexProperty<FontStyle> FontStyleProperty =
+        public static readonly StyledProperty<FontStyle> FontStyleProperty =
             TextBlock.FontStyleProperty.AddOwner<TemplatedControl>();
 
         /// <summary>
         /// Defines the <see cref="Foreground"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Brush> ForegroundProperty =
+        public static readonly StyledProperty<Brush> ForegroundProperty =
             TextBlock.ForegroundProperty.AddOwner<TemplatedControl>();
 
         /// <summary>
         /// Defines the <see cref="Padding"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Thickness> PaddingProperty =
+        public static readonly StyledProperty<Thickness> PaddingProperty =
             Decorator.PaddingProperty.AddOwner<TemplatedControl>();
 
         /// <summary>

+ 12 - 12
src/Perspex.Controls/ScrollViewer.cs

@@ -19,13 +19,13 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="CanScrollHorizontally"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> CanScrollHorizontallyProperty =
+        public static readonly AttachedProperty<bool> CanScrollHorizontallyProperty =
             PerspexProperty.RegisterAttached<ScrollViewer, Control, bool>(nameof(CanScrollHorizontally), true);
 
         /// <summary>
         /// Defines the <see cref="Extent"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Size> ExtentProperty =
+        public static readonly DirectProperty<ScrollViewer, Size> ExtentProperty =
             PerspexProperty.RegisterDirect<ScrollViewer, Size>(nameof(Extent), 
                 o => o.Extent,
                 (o, v) => o.Extent = v);
@@ -33,7 +33,7 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Offset"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Vector> OffsetProperty =
+        public static readonly DirectProperty<ScrollViewer, Vector> OffsetProperty =
             PerspexProperty.RegisterDirect<ScrollViewer, Vector>(
                 nameof(Offset),
                 o => o.Offset,
@@ -42,7 +42,7 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Viewport"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Size> ViewportProperty =
+        public static readonly DirectProperty<ScrollViewer, Size> ViewportProperty =
             PerspexProperty.RegisterDirect<ScrollViewer, Size>(nameof(Viewport), 
                 o => o.Viewport,
                 (o, v) => o.Viewport = v);
@@ -54,7 +54,7 @@ namespace Perspex.Controls
         /// There is no C# accessor for this property as it is intended to be bound to by a 
         /// <see cref="ScrollContentPresenter"/> in the control's template.
         /// </remarks>
-        public static readonly PerspexProperty<double> HorizontalScrollBarMaximumProperty =
+        public static readonly StyledProperty<double> HorizontalScrollBarMaximumProperty =
             PerspexProperty.Register<ScrollViewer, double>("HorizontalScrollBarMaximum");
 
         /// <summary>
@@ -64,7 +64,7 @@ namespace Perspex.Controls
         /// There is no C# accessor for this property as it is intended to be bound to by a 
         /// <see cref="ScrollContentPresenter"/> in the control's template.
         /// </remarks>
-        public static readonly PerspexProperty<double> HorizontalScrollBarValueProperty =
+        public static readonly StyledProperty<double> HorizontalScrollBarValueProperty =
             PerspexProperty.Register<ScrollViewer, double>("HorizontalScrollBarValue");
 
         /// <summary>
@@ -74,7 +74,7 @@ namespace Perspex.Controls
         /// There is no C# accessor for this property as it is intended to be bound to by a 
         /// <see cref="ScrollContentPresenter"/> in the control's template.
         /// </remarks>
-        public static readonly PerspexProperty<double> HorizontalScrollBarViewportSizeProperty =
+        public static readonly StyledProperty<double> HorizontalScrollBarViewportSizeProperty =
             PerspexProperty.Register<ScrollViewer, double>("HorizontalScrollBarViewportSize");
 
         /// <summary>
@@ -84,7 +84,7 @@ namespace Perspex.Controls
         /// There is no C# accessor for this property as it is intended to be bound to by a 
         /// <see cref="ScrollContentPresenter"/> in the control's template.
         /// </remarks>
-        public static readonly PerspexProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
+        public static readonly AttachedProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
             PerspexProperty.RegisterAttached<ScrollBar, Control, ScrollBarVisibility>(
                 nameof(HorizontalScrollBarVisibility),
                 ScrollBarVisibility.Auto);
@@ -96,7 +96,7 @@ namespace Perspex.Controls
         /// There is no C# accessor for this property as it is intended to be bound to by a 
         /// <see cref="ScrollContentPresenter"/> in the control's template.
         /// </remarks>
-        public static readonly PerspexProperty<double> VerticalScrollBarMaximumProperty =
+        public static readonly StyledProperty<double> VerticalScrollBarMaximumProperty =
             PerspexProperty.Register<ScrollViewer, double>("VerticalScrollBarMaximum");
 
         /// <summary>
@@ -106,7 +106,7 @@ namespace Perspex.Controls
         /// There is no C# accessor for this property as it is intended to be bound to by a 
         /// <see cref="ScrollContentPresenter"/> in the control's template.
         /// </remarks>
-        public static readonly PerspexProperty<double> VerticalScrollBarValueProperty =
+        public static readonly StyledProperty<double> VerticalScrollBarValueProperty =
             PerspexProperty.Register<ScrollViewer, double>("VerticalScrollBarValue");
 
         /// <summary>
@@ -116,13 +116,13 @@ namespace Perspex.Controls
         /// There is no C# accessor for this property as it is intended to be bound to by a 
         /// <see cref="ScrollContentPresenter"/> in the control's template.
         /// </remarks>
-        public static readonly PerspexProperty<double> VerticalScrollBarViewportSizeProperty =
+        public static readonly StyledProperty<double> VerticalScrollBarViewportSizeProperty =
             PerspexProperty.Register<ScrollViewer, double>("VerticalScrollBarViewportSize");
 
         /// <summary>
         /// Defines the <see cref="VerticalScrollBarVisibility"/> property.
         /// </summary>
-        public static readonly PerspexProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty =
+        public static readonly AttachedProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty =
             PerspexProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>(
                 nameof(VerticalScrollBarVisibility), 
                 ScrollBarVisibility.Auto);

+ 12 - 9
src/Perspex.Controls/TextBlock.cs

@@ -18,13 +18,16 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Background"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Brush> BackgroundProperty =
+        public static readonly StyledProperty<Brush> BackgroundProperty =
             Border.BackgroundProperty.AddOwner<TextBlock>();
 
+        // TODO: Define these attached properties elswhere (e.g. on a Text class) and AddOwner
+        // them into TextBlock.
+
         /// <summary>
         /// Defines the <see cref="FontFamily"/> property.
         /// </summary>
-        public static readonly PerspexProperty<string> FontFamilyProperty =
+        public static readonly AttachedProperty<string> FontFamilyProperty =
             PerspexProperty.RegisterAttached<TextBlock, Control, string>(
                 nameof(FontFamily),
                 defaultValue: "Courier New",
@@ -33,7 +36,7 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="FontSize"/> property.
         /// </summary>
-        public static readonly PerspexProperty<double> FontSizeProperty =
+        public static readonly AttachedProperty<double> FontSizeProperty =
             PerspexProperty.RegisterAttached<TextBlock, Control, double>(
                 nameof(FontSize),
                 defaultValue: 12,
@@ -42,7 +45,7 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="FontStyle"/> property.
         /// </summary>
-        public static readonly PerspexProperty<FontStyle> FontStyleProperty =
+        public static readonly AttachedProperty<FontStyle> FontStyleProperty =
             PerspexProperty.RegisterAttached<TextBlock, Control, FontStyle>(
                 nameof(FontStyle),
                 inherits: true);
@@ -50,7 +53,7 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="FontWeight"/> property.
         /// </summary>
-        public static readonly PerspexProperty<FontWeight> FontWeightProperty =
+        public static readonly AttachedProperty<FontWeight> FontWeightProperty =
             PerspexProperty.RegisterAttached<TextBlock, Control, FontWeight>(
                 nameof(FontWeight),
                 inherits: true,
@@ -59,7 +62,7 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Foreground"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Brush> ForegroundProperty =
+        public static readonly AttachedProperty<Brush> ForegroundProperty =
             PerspexProperty.RegisterAttached<TextBlock, Control, Brush>(
                 nameof(Foreground),
                 new SolidColorBrush(0xff000000),
@@ -68,19 +71,19 @@ namespace Perspex.Controls
         /// <summary>
         /// Defines the <see cref="Text"/> property.
         /// </summary>
-        public static readonly PerspexProperty<string> TextProperty =
+        public static readonly StyledProperty<string> TextProperty =
             PerspexProperty.Register<TextBlock, string>(nameof(Text), defaultBindingMode: BindingMode.TwoWay);
 
         /// <summary>
         /// Defines the <see cref="TextAlignment"/> property.
         /// </summary>
-        public static readonly PerspexProperty<TextAlignment> TextAlignmentProperty =
+        public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
             PerspexProperty.Register<TextBlock, TextAlignment>(nameof(TextAlignment));
 
         /// <summary>
         /// Defines the <see cref="TextWrapping"/> property.
         /// </summary>
-        public static readonly PerspexProperty<TextWrapping> TextWrappingProperty =
+        public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
             PerspexProperty.Register<TextBlock, TextWrapping>(nameof(TextWrapping));
 
         /// <summary>

+ 11 - 10
src/Perspex.Controls/TextBox.cs

@@ -20,34 +20,35 @@ namespace Perspex.Controls
 {
     public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
     {
-        public static readonly PerspexProperty<bool> AcceptsReturnProperty =
+        public static readonly StyledProperty<bool> AcceptsReturnProperty =
             PerspexProperty.Register<TextBox, bool>("AcceptsReturn");
 
-        public static readonly PerspexProperty<bool> AcceptsTabProperty =
+        public static readonly StyledProperty<bool> AcceptsTabProperty =
             PerspexProperty.Register<TextBox, bool>("AcceptsTab");
 
-        public static readonly PerspexProperty<int> CaretIndexProperty =
+        // TODO: Should CaretIndex, SelectionStart/End and Text be direct properties?
+        public static readonly StyledProperty<int> CaretIndexProperty =
             PerspexProperty.Register<TextBox, int>("CaretIndex", validate: ValidateCaretIndex);
 
-        public static readonly PerspexProperty<int> SelectionStartProperty =
+        public static readonly StyledProperty<int> SelectionStartProperty =
             PerspexProperty.Register<TextBox, int>("SelectionStart", validate: ValidateCaretIndex);
 
-        public static readonly PerspexProperty<int> SelectionEndProperty =
+        public static readonly StyledProperty<int> SelectionEndProperty =
             PerspexProperty.Register<TextBox, int>("SelectionEnd", validate: ValidateCaretIndex);
 
-        public static readonly PerspexProperty<string> TextProperty =
+        public static readonly StyledProperty<string> TextProperty =
             TextBlock.TextProperty.AddOwner<TextBox>();
 
-        public static readonly PerspexProperty<TextAlignment> TextAlignmentProperty =
+        public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
             TextBlock.TextAlignmentProperty.AddOwner<TextBox>();
 
-        public static readonly PerspexProperty<TextWrapping> TextWrappingProperty =
+        public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
             TextBlock.TextWrappingProperty.AddOwner<TextBox>();
 
-        public static readonly PerspexProperty<string> WatermarkProperty =
+        public static readonly StyledProperty<string> WatermarkProperty =
             PerspexProperty.Register<TextBox, string>("Watermark");
 
-        public static readonly PerspexProperty<bool> UseFloatingWatermarkProperty =
+        public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
             PerspexProperty.Register<TextBox, bool>("UseFloatingWatermark");
 
         struct UndoRedoState : IEquatable<UndoRedoState>

+ 1 - 1
src/Perspex.HtmlRenderer/HtmlControl.cs

@@ -85,7 +85,7 @@ namespace Perspex.Controls.Html
         public static readonly PerspexProperty TextProperty =
             PropertyHelper.Register<HtmlControl, string>("Text", null, OnPerspexProperty_valueChanged);
 
-        public static readonly PerspexProperty<Brush> BackgroundProperty =
+        public static readonly StyledProperty<Brush> BackgroundProperty =
             Border.BackgroundProperty.AddOwner<HtmlControl>();
 
         public static readonly PerspexProperty BorderThicknessProperty =

+ 7 - 7
src/Perspex.Input/InputElement.cs

@@ -18,43 +18,43 @@ namespace Perspex.Input
         /// <summary>
         /// Defines the <see cref="Focusable"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> FocusableProperty =
+        public static readonly StyledProperty<bool> FocusableProperty =
             PerspexProperty.Register<InputElement, bool>(nameof(Focusable));
 
         /// <summary>
         /// Defines the <see cref="IsEnabled"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> IsEnabledProperty =
+        public static readonly StyledProperty<bool> IsEnabledProperty =
             PerspexProperty.Register<InputElement, bool>(nameof(IsEnabled), true);
 
         /// <summary>
         /// Defines the <see cref="IsEnabledCore"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> IsEnabledCoreProperty =
+        public static readonly StyledProperty<bool> IsEnabledCoreProperty =
             PerspexProperty.Register<InputElement, bool>("IsEnabledCore", true);
 
         /// <summary>
         /// Gets or sets associated mouse cursor.
         /// </summary>
-        public static readonly PerspexProperty<Cursor> CursorProperty =
+        public static readonly StyledProperty<Cursor> CursorProperty =
             PerspexProperty.Register<InputElement, Cursor>("Cursor", null, true);
 
         /// <summary>
         /// Defines the <see cref="IsFocused"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> IsFocusedProperty =
+        public static readonly DirectProperty<InputElement, bool> IsFocusedProperty =
             PerspexProperty.RegisterDirect<InputElement, bool>("IsFocused", o => o.IsFocused);
 
         /// <summary>
         /// Defines the <see cref="IsHitTestVisible"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> IsHitTestVisibleProperty =
+        public static readonly StyledProperty<bool> IsHitTestVisibleProperty =
             PerspexProperty.Register<InputElement, bool>("IsHitTestVisible", true);
 
         /// <summary>
         /// Defines the <see cref="IsPointerOver"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> IsPointerOverProperty =
+        public static readonly DirectProperty<InputElement, bool> IsPointerOverProperty =
             PerspexProperty.RegisterDirect<InputElement, bool>("IsPointerOver", o => o.IsPointerOver);
 
         /// <summary>

+ 3 - 3
src/Perspex.Input/KeyboardNavigation.cs

@@ -15,7 +15,7 @@ namespace Perspex.Input
         /// The DirectionalNavigation attached property defines how pressing arrow keys causes
         /// focus to be navigated between the children of the container.
         /// </remarks>
-        public static readonly PerspexProperty<KeyboardNavigationMode> DirectionalNavigationProperty =
+        public static readonly AttachedProperty<KeyboardNavigationMode> DirectionalNavigationProperty =
             PerspexProperty.RegisterAttached<InputElement, KeyboardNavigationMode>(
                 "DirectionalNavigation",
                 typeof(KeyboardNavigation),
@@ -28,7 +28,7 @@ namespace Perspex.Input
         /// The TabNavigation attached property defines how pressing the Tab key causes focus to
         /// be navigated between the children of the container.
         /// </remarks>
-        public static readonly PerspexProperty<KeyboardNavigationMode> TabNavigationProperty =
+        public static readonly AttachedProperty<KeyboardNavigationMode> TabNavigationProperty =
             PerspexProperty.RegisterAttached<InputElement, KeyboardNavigationMode>(
                 "TabNavigation",
                 typeof(KeyboardNavigation));
@@ -41,7 +41,7 @@ namespace Perspex.Input
         /// attached property set to <see cref="KeyboardNavigationMode.Once"/>, this property
         /// defines to which child the focus should move.
         /// </remarks>
-        public static readonly PerspexProperty<IInputElement> TabOnceActiveElementProperty =
+        public static readonly AttachedProperty<IInputElement> TabOnceActiveElementProperty =
             PerspexProperty.RegisterAttached<InputElement, IInputElement>(
                 "TabOnceActiveElement",
                 typeof(KeyboardNavigation));

+ 8 - 8
src/Perspex.SceneGraph/Visual.cs

@@ -32,49 +32,49 @@ namespace Perspex
         /// <summary>
         /// Defines the <see cref="Bounds"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Rect> BoundsProperty =
+        public static readonly DirectProperty<Visual, Rect> BoundsProperty =
             PerspexProperty.RegisterDirect<Visual, Rect>(nameof(Bounds), o => o.Bounds);
 
         /// <summary>
         /// Defines the <see cref="ClipToBounds"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> ClipToBoundsProperty =
+        public static readonly StyledProperty<bool> ClipToBoundsProperty =
             PerspexProperty.Register<Visual, bool>(nameof(ClipToBounds));
 
         /// <summary>
         /// Defines the <see cref="IsVisibleProperty"/> property.
         /// </summary>
-        public static readonly PerspexProperty<bool> IsVisibleProperty =
+        public static readonly StyledProperty<bool> IsVisibleProperty =
             PerspexProperty.Register<Visual, bool>(nameof(IsVisible), true);
 
         /// <summary>
         /// Defines the <see cref="Opacity"/> property.
         /// </summary>
-        public static readonly PerspexProperty<double> OpacityProperty =
+        public static readonly StyledProperty<double> OpacityProperty =
             PerspexProperty.Register<Visual, double>(nameof(Opacity), 1);
 
         /// <summary>
         /// Defines the <see cref="RenderTransform"/> property.
         /// </summary>
-        public static readonly PerspexProperty<Transform> RenderTransformProperty =
+        public static readonly StyledProperty<Transform> RenderTransformProperty =
             PerspexProperty.Register<Visual, Transform>(nameof(RenderTransform));
 
         /// <summary>
         /// Defines the <see cref="TransformOrigin"/> property.
         /// </summary>
-        public static readonly PerspexProperty<RelativePoint> TransformOriginProperty =
+        public static readonly StyledProperty<RelativePoint> TransformOriginProperty =
             PerspexProperty.Register<Visual, RelativePoint>(nameof(TransformOrigin), defaultValue: RelativePoint.Center);
 
         /// <summary>
         /// Defines the <see cref="IVisual.VisualParent"/> property.
         /// </summary>
-        public static readonly PerspexProperty<IVisual> VisualParentProperty =
+        public static readonly DirectProperty<Visual, IVisual> VisualParentProperty =
             PerspexProperty.RegisterDirect<Visual, IVisual>("VisualParent", o => o._visualParent);
 
         /// <summary>
         /// Defines the <see cref="ZIndex"/> property.
         /// </summary>
-        public static readonly PerspexProperty<int> ZIndexProperty =
+        public static readonly StyledProperty<int> ZIndexProperty =
             PerspexProperty.Register<Visual, int>(nameof(ZIndex));
 
         /// <summary>

+ 1 - 1
src/Perspex.Styling/Styling/Setter.cs

@@ -112,7 +112,7 @@ namespace Perspex.Styling
 
             if (mode == BindingMode.Default)
             {
-                mode = property.DefaultBindingMode;
+                mode = property.GetMetadata(control.GetType()).DefaultBindingMode;
             }
 
             control.Bind(

+ 26 - 0
tests/Perspex.Base.UnitTests/AttachedPropertyTests.cs

@@ -0,0 +1,26 @@
+// 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 Xunit;
+
+namespace Perspex.Base.UnitTests
+{
+    public class AttachedPropertyTests
+    {
+        [Fact]
+        public void IsAttached_Returns_True()
+        {
+            var property = new AttachedProperty<string>(
+                "Foo",
+                typeof(Class1),
+                false,
+                new StyledPropertyMetadata(null));
+
+            Assert.True(property.IsAttached);
+        }
+
+        private class Class1 : PerspexObject
+        {
+        }
+    }
+}

+ 87 - 0
tests/Perspex.Base.UnitTests/DirectPropertyTests.cs

@@ -0,0 +1,87 @@
+// 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;
+using Xunit;
+
+namespace Perspex.Base.UnitTests
+{
+    public class DirectPropertyTests
+    {
+        [Fact]
+        public void Initialized_Observable_Fired()
+        {
+            bool invoked = false;
+
+            Class1.FooProperty.Initialized.Subscribe(x =>
+            {
+                Assert.Equal(PerspexProperty.UnsetValue, x.OldValue);
+                Assert.Equal("foo", x.NewValue);
+                Assert.Equal(BindingPriority.Unset, x.Priority);
+                invoked = true;
+            });
+
+            var target = new Class1();
+
+            Assert.True(invoked);
+        }
+
+        [Fact]
+        public void IsDirect_Property_Returns_True()
+        {
+            var target = new DirectProperty<Class1, string>("test", o => null);
+
+            Assert.True(target.IsDirect);
+        }
+
+        [Fact]
+        public void AddOwnered_Property_Should_Equal_Original()
+        {
+            var p1 = Class1.FooProperty;
+            var p2 = p1.AddOwner<Class2>(o => null, (o, v) => { });
+
+            Assert.NotSame(p1, p2);
+            Assert.True(p1.Equals(p2));
+            Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
+            Assert.True(p1 == p2);
+        }
+
+        [Fact]
+        public void AddOwnered_Property_Should_Have_OwnerType_Set()
+        {
+            var p1 = Class1.FooProperty;
+            var p2 = p1.AddOwner<Class2>(o => null, (o, v) => { });
+
+            Assert.Equal(typeof(Class2), p2.OwnerType);
+        }
+
+        [Fact]
+        public void AddOwnered_Properties_Should_Share_Observables()
+        {
+            var p1 = Class1.FooProperty;
+            var p2 = p1.AddOwner<Class2>(o => null, (o, v) => { });
+
+            Assert.Same(p1.Changed, p2.Changed);
+            Assert.Same(p1.Initialized, p2.Initialized);
+        }
+
+        private class Class1 : PerspexObject
+        {
+            public static readonly DirectProperty<Class1, string> FooProperty =
+                PerspexProperty.RegisterDirect<Class1, string>("Foo", o => o.Foo, (o, v) => o.Foo = v);
+
+            private string _foo = "foo";
+
+            public string Foo
+            {
+                get { return _foo; }
+                set { SetAndRaise(FooProperty, ref _foo, value); }
+            }
+        }
+
+        private class Class2 : PerspexObject
+        {
+        }
+    }
+}

+ 3 - 0
tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj

@@ -81,6 +81,7 @@
     <Compile Include="Collections\PerspexDictionaryTests.cs" />
     <Compile Include="Collections\PerspexListTests.cs" />
     <Compile Include="Collections\PropertyChangedTracker.cs" />
+    <Compile Include="AttachedPropertyTests.cs" />
     <Compile Include="PerspexObjectTests_Direct.cs" />
     <Compile Include="PerspexObjectTests_GetSubject.cs" />
     <Compile Include="PerspexObjectTests_GetObservable.cs" />
@@ -91,6 +92,8 @@
     <Compile Include="PerspexObjectTests_SetValue.cs" />
     <Compile Include="PerspexObjectTests_GetValue.cs" />
     <Compile Include="PerspexObjectTests_Metadata.cs" />
+    <Compile Include="DirectPropertyTests.cs" />
+    <Compile Include="StyledPropertyTests.cs" />
     <Compile Include="PerspexPropertyTests.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="PriorityValueTests.cs" />

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

@@ -160,7 +160,7 @@ namespace Perspex.Base.UnitTests
 
         private class Class3 : Class1
         {
-            public static readonly PerspexProperty<string> AttachedProperty =
+            public static readonly StyledProperty<string> AttachedProperty =
                 AttachedOwner.AttachedProperty.AddOwner<Class3>();
         }
 
@@ -170,7 +170,7 @@ namespace Perspex.Base.UnitTests
 
         private class AttachedOwner
         {
-            public static readonly PerspexProperty<string> AttachedProperty =
+            public static readonly AttachedProperty<string> AttachedProperty =
                 PerspexProperty.RegisterAttached<AttachedOwner, Class1, string>("Attached");
         }
     }

+ 135 - 139
tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs

@@ -12,88 +12,71 @@ namespace Perspex.Base.UnitTests
         [Fact]
         public void Constructor_Sets_Properties()
         {
-            PerspexProperty<string> target = new PerspexProperty<string>(
-                "test",
-                typeof(Class1),
-                "Foo",
-                false,
-                BindingMode.OneWay,
-                null);
+            var target = new TestProperty<string>("test", typeof(Class1));
 
             Assert.Equal("test", target.Name);
             Assert.Equal(typeof(string), target.PropertyType);
             Assert.Equal(typeof(Class1), target.OwnerType);
-            Assert.Equal(false, target.Inherits);
         }
 
         [Fact]
         public void Name_Cannot_Contain_Periods()
         {
-            Assert.Throws<ArgumentException>(() => new PerspexProperty<string>(
-                "Foo.Bar",
-                typeof(Class1),
-                "Foo",
-                false,
-                BindingMode.OneWay,
-                null));
+            Assert.Throws<ArgumentException>(() => new TestProperty<string>("Foo.Bar", typeof(Class1)));
         }
 
         [Fact]
-        public void GetDefaultValue_Returns_Registered_Value()
+        public void GetMetadata_Returns_Supplied_Value()
         {
-            PerspexProperty<string> target = new PerspexProperty<string>(
-                "test",
-                typeof(Class1),
-                "Foo",
-                false,
-                BindingMode.OneWay,
-                null);
-
-            Assert.Equal("Foo", target.GetDefaultValue<Class1>());
+            var metadata = new PropertyMetadata();
+            var target = new TestProperty<string>("test", typeof(Class1), metadata);
+
+            Assert.Same(metadata, target.GetMetadata<Class1>());
         }
 
         [Fact]
-        public void GetDefaultValue_Returns_Registered_Value_For_Not_Overridden_Class()
+        public void GetMetadata_Returns_Supplied_Value_For_Derived_Class()
         {
-            PerspexProperty<string> target = new PerspexProperty<string>(
-                "test",
-                typeof(Class1),
-                "Foo",
-                false,
-                BindingMode.OneWay,
-                null);
-
-            Assert.Equal("Foo", target.GetDefaultValue<Class2>());
+            var metadata = new PropertyMetadata();
+            var target = new TestProperty<string>("test", typeof(Class1), metadata);
+
+            Assert.Same(metadata, target.GetMetadata<Class2>());
         }
 
         [Fact]
-        public void GetDefaultValue_Returns_Registered_Value_For_Unrelated_Class()
+        public void GetMetadata_Returns_Supplied_Value_For_Unrelated_Class()
         {
-            PerspexProperty<string> target = new PerspexProperty<string>(
-                "test",
-                typeof(Class3),
-                "Foo",
-                false,
-                BindingMode.OneWay,
-                null);
-
-            Assert.Equal("Foo", target.GetDefaultValue<Class2>());
+            var metadata = new PropertyMetadata();
+            var target = new TestProperty<string>("test", typeof(Class3), metadata);
+
+            Assert.Same(metadata, target.GetMetadata<Class2>());
         }
 
         [Fact]
-        public void GetDefaultValue_Returns_Overridden_Value()
+        public void GetMetadata_Returns_Overridden_Value()
         {
-            PerspexProperty<string> target = new PerspexProperty<string>(
-                "test",
-                typeof(Class1),
-                "Foo",
-                false,
-                BindingMode.OneWay,
-                null);
+            var metadata = new PropertyMetadata();
+            var overridden = new PropertyMetadata();
+            var target = new TestProperty<string>("test", typeof(Class1), metadata);
 
-            target.OverrideDefaultValue(typeof(Class2), "Bar");
+            target.OverrideMetadata<Class2>(overridden);
 
-            Assert.Equal("Bar", target.GetDefaultValue<Class2>());
+            Assert.Same(overridden, target.GetMetadata<Class2>());
+        }
+
+        [Fact]
+        public void OverrideMetadata_Should_Merge_Values()
+        {
+            var metadata = new PropertyMetadata(BindingMode.TwoWay);
+            var notify = (Action<IPerspexObject, bool>)((a, b) => { });
+            var overridden = new PropertyMetadata(notifyingCallback: notify);
+            var target = new TestProperty<string>("test", typeof(Class1), metadata);
+
+            target.OverrideMetadata<Class2>(overridden);
+
+            var result = target.GetMetadata<Class2>();
+            Assert.Equal(BindingMode.TwoWay, result.DefaultBindingMode);
+            Assert.Equal(notify, result.NotifyingCallback);
         }
 
         [Fact]
@@ -126,22 +109,22 @@ namespace Perspex.Base.UnitTests
             Assert.Equal("newvalue", value);
         }
 
-        [Fact]
-        public void IsDirect_Property_Set_On_Direct_PerspexProperty()
-        {
-            PerspexProperty<string> target = new PerspexProperty<string>(
-                "test",
-                typeof(Class1),
-                o => null,
-                (o, v) => { });
+        ////[Fact]
+        ////public void IsDirect_Property_Set_On_Direct_PerspexProperty()
+        ////{
+        ////    PerspexProperty<string> target = new PerspexProperty<string>(
+        ////        "test",
+        ////        typeof(Class1),
+        ////        o => null,
+        ////        (o, v) => { });
 
-            Assert.True(target.IsDirect);
-        }
+        ////    Assert.True(target.IsDirect);
+        ////}
 
         [Fact]
         public void Property_Equals_Should_Handle_Null()
         {
-            var p1 = new PerspexProperty<string>("p1", typeof(Class1));
+            var p1 = new TestProperty<string>("p1", typeof(Class1));
 
             Assert.NotEqual(p1, null);
             Assert.NotEqual(null, p1);
@@ -151,80 +134,93 @@ namespace Perspex.Base.UnitTests
             Assert.True((PerspexProperty)null == (PerspexProperty)null);
         }
 
-        [Fact]
-        public void AddOwnered_Property_Should_Equal_Original()
-        {
-            var p1 = new PerspexProperty<string>("p1", typeof(Class1));
-            var p2 = p1.AddOwner<Class3>();
-
-            Assert.Equal(p1, p2);
-            Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
-            Assert.True(p1 == p2);
-        }
-
-        [Fact]
-        public void AddOwnered_Property_Should_Have_OwnerType_Set()
-        {
-            var p1 = new PerspexProperty<string>("p1", typeof(Class1));
-            var p2 = p1.AddOwner<Class3>();
-
-            Assert.Equal(typeof(Class3), p2.OwnerType);
-        }
-
-        [Fact]
-        public void AddOwnered_Properties_Should_Share_Observables()
-        {
-            var p1 = new PerspexProperty<string>("p1", typeof(Class1));
-            var p2 = p1.AddOwner<Class3>();
-
-            Assert.Same(p1.Changed, p2.Changed);
-            Assert.Same(p1.Initialized, p2.Initialized);
-        }
-
-        [Fact]
-        public void AddOwnered_Direct_Property_Should_Equal_Original()
-        {
-            var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o,v) => { });
-            var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
-
-            Assert.Equal(p1, p2);
-            Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
-            Assert.True(p1 == p2);
-        }
-
-        [Fact]
-        public void AddOwnered_Direct_Property_Should_Have_OwnerType_Set()
-        {
-            var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o, v) => { });
-            var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
-
-            Assert.Equal(typeof(Class3), p2.OwnerType);
-        }
-
-        [Fact]
-        public void AddOwnered_Direct_Properties_Should_Share_Observables()
-        {
-            var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o, v) => { });
-            var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
-
-            Assert.Same(p1.Changed, p2.Changed);
-            Assert.Same(p1.Initialized, p2.Initialized);
-        }
-
-        [Fact]
-        public void AddOwner_With_Getter_And_Setter_On_Standard_Property_Should_Throw()
-        {
-            var p1 = new PerspexProperty<string>("p1", typeof(Class1));
-
-            Assert.Throws<InvalidOperationException>(() => p1.AddOwner<Class3>(o => null, (o, v) => { }));
-        }
-
-        [Fact]
-        public void AddOwner_On_Direct_Property_Without_Getter_Or_Setter_Should_Throw()
-        {
-            var p1 = new PerspexProperty<string>("e1", typeof(Class1), o => null, (o, v) => { });
+        ////[Fact]
+        ////public void AddOwnered_Property_Should_Equal_Original()
+        ////{
+        ////    var p1 = new PerspexProperty<string>("p1", typeof(Class1));
+        ////    var p2 = p1.AddOwner<Class3>();
+
+        ////    Assert.Equal(p1, p2);
+        ////    Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
+        ////    Assert.True(p1 == p2);
+        ////}
+
+        ////[Fact]
+        ////public void AddOwnered_Property_Should_Have_OwnerType_Set()
+        ////{
+        ////    var p1 = new PerspexProperty<string>("p1", typeof(Class1));
+        ////    var p2 = p1.AddOwner<Class3>();
+
+        ////    Assert.Equal(typeof(Class3), p2.OwnerType);
+        ////}
+
+        ////[Fact]
+        ////public void AddOwnered_Properties_Should_Share_Observables()
+        ////{
+        ////    var p1 = new PerspexProperty<string>("p1", typeof(Class1));
+        ////    var p2 = p1.AddOwner<Class3>();
+
+        ////    Assert.Same(p1.Changed, p2.Changed);
+        ////    Assert.Same(p1.Initialized, p2.Initialized);
+        ////}
+
+        ////[Fact]
+        ////public void AddOwnered_Direct_Property_Should_Equal_Original()
+        ////{
+        ////    var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o,v) => { });
+        ////    var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
+
+        ////    Assert.Equal(p1, p2);
+        ////    Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
+        ////    Assert.True(p1 == p2);
+        ////}
+
+        ////[Fact]
+        ////public void AddOwnered_Direct_Property_Should_Have_OwnerType_Set()
+        ////{
+        ////    var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o, v) => { });
+        ////    var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
+
+        ////    Assert.Equal(typeof(Class3), p2.OwnerType);
+        ////}
+
+        ////[Fact]
+        ////public void AddOwnered_Direct_Properties_Should_Share_Observables()
+        ////{
+        ////    var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o, v) => { });
+        ////    var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
+
+        ////    Assert.Same(p1.Changed, p2.Changed);
+        ////    Assert.Same(p1.Initialized, p2.Initialized);
+        ////}
+
+        ////[Fact]
+        ////public void AddOwner_With_Getter_And_Setter_On_Standard_Property_Should_Throw()
+        ////{
+        ////    var p1 = new PerspexProperty<string>("p1", typeof(Class1));
+
+        ////    Assert.Throws<InvalidOperationException>(() => p1.AddOwner<Class3>(o => null, (o, v) => { }));
+        ////}
+
+        ////[Fact]
+        ////public void AddOwner_On_Direct_Property_Without_Getter_Or_Setter_Should_Throw()
+        ////{
+        ////    var p1 = new PerspexProperty<string>("e1", typeof(Class1), o => null, (o, v) => { });
+
+        ////    Assert.Throws<InvalidOperationException>(() => p1.AddOwner<Class3>());
+        ////}
+
+        private class TestProperty<TValue> : PerspexProperty<TValue>
+        {
+            public TestProperty(string name, Type ownerType, PropertyMetadata metadata = null)
+                : base(name, ownerType, metadata ?? new PropertyMetadata())
+            {
+            }
 
-            Assert.Throws<InvalidOperationException>(() => p1.AddOwner<Class3>());
+            public void OverrideMetadata<T>(PropertyMetadata metadata)
+            {
+                OverrideMetadata(typeof(T), metadata);
+            }
         }
 
         private class Class1 : PerspexObject

+ 48 - 0
tests/Perspex.Base.UnitTests/StyledPropertyTests.cs

@@ -0,0 +1,48 @@
+// 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 Xunit;
+
+namespace Perspex.Base.UnitTests
+{
+    public class StyledPropertyTests
+    {
+        [Fact]
+        public void AddOwnered_Property_Should_Equal_Original()
+        {
+            var p1 = new StyledProperty<string>(
+                "p1", 
+                typeof(Class1), 
+                false,
+                new StyledPropertyMetadata(null));
+            var p2 = p1.AddOwner<Class2>();
+
+            Assert.Equal(p1, p2);
+            Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
+            Assert.True(p1 == p2);
+        }
+
+        [Fact]
+        public void AddOwnered_Property_Should_Be_Same()
+        {
+            var p1 = new StyledProperty<string>(
+                "p1",
+                typeof(Class1),
+                false,
+                new StyledPropertyMetadata(null));
+            var p2 = p1.AddOwner<Class2>();
+
+            Assert.Same(p1, p2);
+        }
+
+        private class Class1 : PerspexObject
+        {
+            public static readonly PerspexProperty<string> FooProperty =
+                PerspexProperty.Register<Class1, string>("Foo", "default");
+        }
+
+        private class Class2 : PerspexObject
+        {
+        }
+    }
+}