Browse Source

Refactored binding somewhat.

- Simplified PerpsexObject interface to a simplified single interface,
IPerspexObject
- Moved GetObservable etc to extension method
Steven Kirk 10 years ago
parent
commit
30a756fb83
39 changed files with 337 additions and 590 deletions
  1. 1 1
      samples/TestApplicationShared/GalleryStyle.cs
  2. 7 42
      src/Markup/Perspex.Markup.Xaml/Data/Binding.cs
  3. 2 2
      src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs
  4. 3 3
      src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs
  5. 2 2
      src/Perspex.Animation/Animate.cs
  6. 0 95
      src/Perspex.Base/IObservablePropertyBag.cs
  7. 42 18
      src/Perspex.Base/IPerspexObject.cs
  8. 1 2
      src/Perspex.Base/Perspex.Base.csproj
  9. 4 89
      src/Perspex.Base/PerspexObject.cs
  10. 139 1
      src/Perspex.Base/PerspexObjectExtensions.cs
  11. 2 2
      src/Perspex.Controls/Presenters/ScrollContentPresenter.cs
  12. 4 4
      src/Perspex.Controls/Presenters/TextPresenter.cs
  13. 1 1
      src/Perspex.Controls/Primitives/AccessText.cs
  14. 4 4
      src/Perspex.Controls/Primitives/ScrollBar.cs
  15. 1 1
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  16. 1 1
      src/Perspex.Controls/Primitives/Track.cs
  17. 1 1
      src/Perspex.Controls/RadioButton.cs
  18. 5 5
      src/Perspex.Controls/ScrollViewer.cs
  19. 4 4
      src/Perspex.Controls/TextBlock.cs
  20. 2 2
      src/Perspex.Controls/TextBox.cs
  21. 2 2
      src/Perspex.Controls/TopLevel.cs
  22. 1 1
      src/Perspex.Diagnostics/DevTools.cs
  23. 1 1
      src/Perspex.Diagnostics/Views/ControlDetailsView.cs
  24. 1 1
      src/Perspex.Diagnostics/Views/LogicalTreeView.cs
  25. 1 1
      src/Perspex.Diagnostics/Views/VisualTreeView.cs
  26. 2 2
      src/Perspex.SceneGraph/Animation/CrossFade.cs
  27. 1 1
      src/Perspex.SceneGraph/Media/MatrixTransform.cs
  28. 1 1
      src/Perspex.SceneGraph/Media/RotateTransform.cs
  29. 2 2
      src/Perspex.SceneGraph/Media/TranslateTransform.cs
  30. 1 1
      src/Perspex.Styling/Styling/IStyleable.cs
  31. 1 8
      src/Perspex.Styling/Styling/ITemplatedControl.cs
  32. 1 1
      src/Perspex.Styling/Styling/Selectors.cs
  33. 56 68
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs
  34. 10 30
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
  35. 1 4
      tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs
  36. 7 46
      tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs
  37. 7 45
      tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs
  38. 8 45
      tests/Perspex.Styling.UnitTests/TestControlBase.cs
  39. 7 50
      tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs

+ 1 - 1
samples/TestApplicationShared/GalleryStyle.cs

@@ -23,7 +23,7 @@ namespace TestApplication
                 {
                     Setters = new[]
                     {
-                        new Setter (TemplatedControl.TemplateProperty, new FuncControlTemplate<TabControl> (TabControlTemplate))
+                        new Setter (TemplatedControl.TemplateProperty, new FuncControlTemplate<TabControl>(TabControlTemplate))
                     }
                 },
 

+ 7 - 42
src/Markup/Perspex.Markup.Xaml/Data/Binding.cs

@@ -55,7 +55,7 @@ namespace Perspex.Markup.Xaml.Data
         /// </summary>
         /// <param name="instance">The target instance.</param>
         /// <param name="property">The target property.</param>
-        public void Bind(IObservablePropertyBag instance, PerspexProperty property)
+        public void Bind(IPerspexObject instance, PerspexProperty property)
         {
             Contract.Requires<ArgumentNullException>(instance != null);
             Contract.Requires<ArgumentNullException>(property != null);
@@ -67,7 +67,8 @@ namespace Perspex.Markup.Xaml.Data
 
             if (subject != null)
             {
-                Bind(instance, property, subject);
+                var mode = Mode == BindingMode.Default ? property.DefaultBindingMode : Mode;
+                instance.Bind(property, subject, mode, Priority);
             }
         }
 
@@ -81,7 +82,7 @@ namespace Perspex.Markup.Xaml.Data
         /// </param>
         /// <returns>An <see cref="ISubject{object}"/>.</returns>
         public ISubject<object> CreateSubject(
-            IObservablePropertyBag target,
+            IPerspexObject target,
             Type targetType,
             bool targetIsDataContext = false)
         {
@@ -125,42 +126,6 @@ namespace Perspex.Markup.Xaml.Data
                 ConverterParameter);
         }
 
-        /// <summary>
-        /// Applies a binding subject to a property on an instance.
-        /// </summary>
-        /// <param name="target">The target instance.</param>
-        /// <param name="property">The target property.</param>
-        /// <param name="subject">The binding subject.</param>
-        internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject<object> subject)
-        {
-            Contract.Requires<ArgumentNullException>(target != null);
-            Contract.Requires<ArgumentNullException>(property != null);
-            Contract.Requires<ArgumentNullException>(subject != null);
-
-            var mode = Mode == BindingMode.Default ?
-                property.DefaultBindingMode : Mode;
-
-            switch (mode)
-            {
-                case BindingMode.Default:
-                case BindingMode.OneWay:
-                    target.Bind(property, subject, Priority);
-                    break;
-                case BindingMode.TwoWay:
-                    target.BindTwoWay(property, subject, Priority);
-                    break;
-                case BindingMode.OneTime:
-                    target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
-                    {
-                        subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority));
-                    });
-                    break;
-                case BindingMode.OneWayToSource:
-                    target.GetObservable(property).Subscribe(subject);
-                    break;
-            }
-        }
-
         private static PathInfo ParsePath(string path)
         {
             var result = new PathInfo();
@@ -209,7 +174,7 @@ namespace Perspex.Markup.Xaml.Data
         }
 
         private ExpressionObserver CreateDataContextSubject(
-            IObservablePropertyBag target,
+            IPerspexObject target,
             string path,
             bool targetIsDataContext)
         {
@@ -231,7 +196,7 @@ namespace Perspex.Markup.Xaml.Data
             {
                 return new ExpressionObserver(
                     target.GetObservable(Visual.VisualParentProperty)
-                          .OfType<IObservablePropertyBag>()
+                          .OfType<IPerspexObject>()
                           .Select(x => x.GetObservable(Control.DataContextProperty))
                           .Switch(),
                     path);
@@ -239,7 +204,7 @@ namespace Perspex.Markup.Xaml.Data
         }
 
         private ExpressionObserver CreateTemplatedParentSubject(
-            IObservablePropertyBag target,
+            IPerspexObject target,
             string path)
         {
             Contract.Requires<ArgumentNullException>(target != null);

+ 2 - 2
src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs

@@ -16,7 +16,7 @@ namespace Perspex.Markup.Xaml.Data
         /// </summary>
         /// <param name="instance">The target instance.</param>
         /// <param name="property">The target property.</param>
-        void Bind(IObservablePropertyBag instance, PerspexProperty property);
+        void Bind(IPerspexObject instance, PerspexProperty property);
 
         /// <summary>
         /// Creates a subject that can be used to get and set the value of the binding.
@@ -28,7 +28,7 @@ namespace Perspex.Markup.Xaml.Data
         /// </param>
         /// <returns>An <see cref="ISubject{object}"/>.</returns>
         ISubject<object> CreateSubject(
-            IObservablePropertyBag target,
+            IPerspexObject target,
             Type targetType,
             bool targetIsDataContext = false);
     }

+ 3 - 3
src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs

@@ -48,7 +48,7 @@ namespace Perspex.Markup.Xaml.Data
         /// </summary>
         /// <param name="instance">The target instance.</param>
         /// <param name="property">The target property.</param>
-        public void Bind(IObservablePropertyBag instance, PerspexProperty property)
+        public void Bind(IPerspexObject instance, PerspexProperty property)
         {
             var subject = CreateSubject(instance, property.PropertyType);
 
@@ -68,7 +68,7 @@ namespace Perspex.Markup.Xaml.Data
         /// </param>
         /// <returns>An <see cref="ISubject{object}"/>.</returns>
         public ISubject<object> CreateSubject(
-            IObservablePropertyBag target, 
+            IPerspexObject target, 
             Type targetType,
             bool targetIsDataContext = false)
         {
@@ -91,7 +91,7 @@ namespace Perspex.Markup.Xaml.Data
         /// <param name="target">The target instance.</param>
         /// <param name="property">The target property.</param>
         /// <param name="subject">The binding subject.</param>
-        internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject<object> subject)
+        internal void Bind(IPerspexObject target, PerspexProperty property, ISubject<object> subject)
         {
             var mode = Mode == BindingMode.Default ?
                 property.DefaultBindingMode : Mode;

+ 2 - 2
src/Perspex.Animation/Animate.cs

@@ -96,7 +96,7 @@ namespace Perspex.Animation
         /// <param name="duration">The duration of the animation.</param>
         /// <returns>An <see cref="Animation"/> that can be used to track or stop the animation.</returns>
         public static Animation Property(
-            IObservablePropertyBag target,
+            IPerspexObject target,
             PerspexProperty property,
             object start,
             object finish,
@@ -119,7 +119,7 @@ namespace Perspex.Animation
         /// <param name="duration">The duration of the animation.</param>
         /// <returns>An <see cref="Animation"/> that can be used to track or stop the animation.</returns>
         public static Animation<T> Property<T>(
-            IObservablePropertyBag target,
+            IPerspexObject target,
             PerspexProperty<T> property,
             T start,
             T finish,

+ 0 - 95
src/Perspex.Base/IObservablePropertyBag.cs

@@ -1,95 +0,0 @@
-// 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.Reactive.Subjects;
-
-namespace Perspex
-{
-    /// <summary>
-    /// Interface for getting/setting <see cref="PerspexProperty"/> bindings on an object.
-    /// </summary>
-    public interface IObservablePropertyBag : IPropertyBag
-    {
-        /// <summary>
-        /// Binds a <see cref="PerspexProperty"/> to an observable.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <param name="source">The observable.</param>
-        /// <param name="priority">The priority of the binding.</param>
-        /// <returns>
-        /// A disposable which can be used to terminate the binding.
-        /// </returns>
-        IDisposable Bind(
-            PerspexProperty property,
-            IObservable<object> source,
-            BindingPriority priority = BindingPriority.LocalValue);
-
-        /// <summary>
-        /// Binds a <see cref="PerspexProperty"/> to an observable.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="source">The observable.</param>
-        /// <param name="priority">The priority of the binding.</param>
-        /// <returns>
-        /// A disposable which can be used to terminate the binding.
-        /// </returns>
-        IDisposable Bind<T>(
-            PerspexProperty<T> property,
-            IObservable<T> source,
-            BindingPriority priority = BindingPriority.LocalValue);
-
-        /// <summary>
-        /// Initiates a two-way binding between <see cref="PerspexProperty"/>s.
-        /// </summary>
-        /// <param name="property">The property on this object.</param>
-        /// <param name="source">The source object.</param>
-        /// <param name="sourceProperty">The property on the source object.</param>
-        /// <param name="priority">The priority of the binding.</param>
-        /// <returns>
-        /// A disposable which can be used to terminate the binding.
-        /// </returns>
-        /// <remarks>
-        /// The binding is first carried out from <paramref name="source"/> to this.
-        /// </remarks>
-        IDisposable BindTwoWay(
-            PerspexProperty property,
-            PerspexObject source,
-            PerspexProperty sourceProperty,
-            BindingPriority priority = BindingPriority.LocalValue);
-
-        /// <summary>
-        /// Initiates a two-way binding between a <see cref="PerspexProperty"/> and an 
-        /// <see cref="ISubject{Object}"/>.
-        /// </summary>
-        /// <param name="property">The property on this object.</param>
-        /// <param name="source">The subject to bind to.</param>
-        /// <param name="priority">The priority of the binding.</param>
-        /// <returns>
-        /// A disposable which can be used to terminate the binding.
-        /// </returns>
-        /// <remarks>
-        /// The binding is first carried out from <paramref name="source"/> to this.
-        /// </remarks>
-        IDisposable BindTwoWay(
-            PerspexProperty property,
-            ISubject<object> source,
-            BindingPriority priority = BindingPriority.LocalValue);
-
-        /// <summary>
-        /// Gets an observable for a <see cref="PerspexProperty"/>.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <returns>An observable.</returns>
-        IObservable<object> GetObservable(PerspexProperty property);
-
-        /// <summary>
-        /// Gets an observable for a <see cref="PerspexProperty"/>.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <returns>An observable.</returns>
-        IObservable<T> GetObservable<T>(PerspexProperty<T> property);
-    }
-}

+ 42 - 18
src/Perspex.Base/IPropertyBag.cs → src/Perspex.Base/IPerspexObject.cs

@@ -1,23 +1,19 @@
 // Copyright (c) The Perspex Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
+
 namespace Perspex
 {
     /// <summary>
     /// Interface for getting/setting <see cref="PerspexProperty"/> values on an object.
     /// </summary>
-    public interface IPropertyBag
+    public interface IPerspexObject
     {
         /// <summary>
-        /// Gets the object that inherited <see cref="PerspexProperty"/> values are inherited from.
+        /// Raised when a <see cref="PerspexProperty"/> value changes on this object.
         /// </summary>
-        IPropertyBag InheritanceParent { get; }
-
-        /// <summary>
-        /// Clears a <see cref="PerspexProperty"/>'s local value.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        void ClearValue(PerspexProperty property);
+        event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
 
         /// <summary>
         /// Gets a <see cref="PerspexProperty"/> value.
@@ -34,13 +30,6 @@ namespace Perspex
         /// <returns>The value.</returns>
         T GetValue<T>(PerspexProperty<T> property);
 
-        /// <summary>
-        /// Checks whether a <see cref="PerspexProperty"/> is registered on this object.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <returns>True if the property is registered, otherwise false.</returns>
-        bool IsRegistered(PerspexProperty property);
-
         /// <summary>
         /// Checks whether a <see cref="PerspexProperty"/> is set on this object.
         /// </summary>
@@ -54,7 +43,10 @@ namespace Perspex
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
         /// <param name="priority">The priority of the value.</param>
-        void SetValue(PerspexProperty property, object value, BindingPriority priority = BindingPriority.LocalValue);
+        void SetValue(
+            PerspexProperty property, 
+            object value, 
+            BindingPriority priority = BindingPriority.LocalValue);
 
         /// <summary>
         /// Sets a <see cref="PerspexProperty"/> value.
@@ -63,6 +55,38 @@ namespace Perspex
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
         /// <param name="priority">The priority of the value.</param>
-        void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue);
+        void SetValue<T>(
+            PerspexProperty<T> property,
+            T value,
+            BindingPriority priority = BindingPriority.LocalValue);
+
+        /// <summary>
+        /// Binds a <see cref="PerspexProperty"/> to an observable.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <param name="source">The observable.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>
+        /// A disposable which can be used to terminate the binding.
+        /// </returns>
+        IDisposable Bind(
+            PerspexProperty property,
+            IObservable<object> source,
+            BindingPriority priority = BindingPriority.LocalValue);
+
+        /// <summary>
+        /// Binds a <see cref="PerspexProperty"/> to an observable.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="source">The observable.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>
+        /// A disposable which can be used to terminate the binding.
+        /// </returns>
+        IDisposable Bind<T>(
+            PerspexProperty<T> property,
+            IObservable<T> source,
+            BindingPriority priority = BindingPriority.LocalValue);
     }
 }

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

@@ -45,8 +45,7 @@
     <Compile Include="BindingDescriptor.cs" />
     <Compile Include="Collections\PerspexDictionary.cs" />
     <Compile Include="Diagnostics\PerspexObjectExtensions.cs" />
-    <Compile Include="IObservablePropertyBag.cs" />
-    <Compile Include="IPropertyBag.cs" />
+    <Compile Include="IPerspexObject.cs" />
     <Compile Include="Metadata\ContentAttribute.cs" />
     <Compile Include="PerspexDisposable.cs" />
     <Compile Include="PerspexLocator.cs" />

+ 4 - 89
src/Perspex.Base/PerspexObject.cs

@@ -23,7 +23,7 @@ namespace Perspex
     /// <remarks>
     /// This class is analogous to DependencyObject in WPF.
     /// </remarks>
-    public class PerspexObject : IObservablePropertyBag, INotifyPropertyChanged
+    public class PerspexObject : IPerspexObject, INotifyPropertyChanged
     {
         /// <summary>
         /// The parent object that inherited values are inherited from.
@@ -89,11 +89,6 @@ namespace Perspex
             remove { _inpcChanged -= value; }
         }
 
-        /// <summary>
-        /// Gets the object that inherited <see cref="PerspexProperty"/> values are inherited from.
-        /// </summary>
-        IPropertyBag IPropertyBag.InheritanceParent => InheritanceParent;
-
         /// <summary>
         /// Gets or sets the parent object that inherited <see cref="PerspexProperty"/> values
         /// are inherited from.
@@ -188,7 +183,7 @@ namespace Perspex
                         SetValue(binding.Property, sourceBinding.Source.GetValue(sourceBinding.Property), binding.Priority);
                         break;
                     case BindingMode.OneWayToSource:
-                        sourceBinding.Source.Bind(sourceBinding.Property, GetObservable(binding.Property), binding.Priority);
+                        sourceBinding.Source.Bind(sourceBinding.Property, this.GetObservable(binding.Property), binding.Priority);
                         break;
                     case BindingMode.TwoWay:
                         BindTwoWay(binding.Property, sourceBinding.Source, sourceBinding.Property);
@@ -223,81 +218,6 @@ namespace Perspex
             SetValue(property, PerspexProperty.UnsetValue);
         }
 
-        /// <summary>
-        /// Gets an observable for a <see cref="PerspexProperty"/>.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <returns>An observable.</returns>
-        public IObservable<object> GetObservable(PerspexProperty property)
-        {
-            Contract.Requires<ArgumentNullException>(property != null);
-
-            return new PerspexObservable<object>(
-                observer =>
-                {
-                    EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
-                    {
-                        if (e.Property == property)
-                        {
-                            observer.OnNext(e.NewValue);
-                        }
-                    };
-
-                    observer.OnNext(GetValue(property));
-
-                    PropertyChanged += handler;
-
-                    return Disposable.Create(() =>
-                    {
-                        PropertyChanged -= handler;
-                    });
-                },
-                GetDescription(property));
-        }
-
-        /// <summary>
-        /// Gets an observable for a <see cref="PerspexProperty"/>.
-        /// </summary>
-        /// <typeparam name="T">The property type.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <returns>An observable.</returns>
-        public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
-        {
-            Contract.Requires<ArgumentNullException>(property != null);
-
-            return GetObservable((PerspexProperty)property).Cast<T>();
-        }
-
-        /// <summary>
-        /// Gets an observable for a <see cref="PerspexProperty"/>.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <returns>An observable which when subscribed pushes the old and new values of the
-        /// property each time it is changed.</returns>
-        public IObservable<Tuple<T, T>> GetObservableWithHistory<T>(PerspexProperty<T> property)
-        {
-            return new PerspexObservable<Tuple<T, T>>(
-                observer =>
-                {
-                    EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
-                    {
-                        if (e.Property == property)
-                        {
-                            observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
-                        }
-                    };
-
-                    PropertyChanged += handler;
-
-                    return Disposable.Create(() =>
-                    {
-                        PropertyChanged -= handler;
-                    });
-                },
-                GetDescription(property));
-        }
-
         /// <summary>
         /// Gets a <see cref="PerspexProperty"/> value.
         /// </summary>
@@ -589,7 +509,7 @@ namespace Perspex
 
             return new CompositeDisposable(
                 Bind(property, source.GetObservable(sourceProperty)),
-                source.Bind(sourceProperty, GetObservable(property)));
+                source.Bind(sourceProperty, this.GetObservable(property)));
         }
 
         /// <summary>
@@ -619,7 +539,7 @@ namespace Perspex
 
             return new CompositeDisposable(
                 Bind(property, source),
-                GetObservable(property).Subscribe(source));
+                this.GetObservable(property).Subscribe(source));
         }
 
         /// <summary>
@@ -638,11 +558,6 @@ namespace Perspex
         }
 
         /// <inheritdoc/>
-        bool IPropertyBag.IsRegistered(PerspexProperty property)
-        {
-            return PerspexPropertyRegistry.Instance.IsRegistered(this, property);
-        }
-
         /// <summary>
         /// Gets all priority values set on the object.
         /// </summary>

+ 139 - 1
src/Perspex.Base/PerspexObjectExtensions.cs

@@ -2,8 +2,10 @@
 // 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.Disposables;
 using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Perspex.Reactive;
 
 namespace Perspex
 {
@@ -12,6 +14,131 @@ namespace Perspex
     /// </summary>
     public static class PerspexObjectExtensions
     {
+        /// <summary>
+        /// Gets an observable for a <see cref="PerspexProperty"/>.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        /// <param name="property">The property.</param>
+        /// <returns>An observable.</returns>
+        public static IObservable<object> GetObservable(this IPerspexObject o, PerspexProperty property)
+        {
+            Contract.Requires<ArgumentNullException>(o != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+
+            return new PerspexObservable<object>(
+                observer =>
+                {
+                    EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
+                    {
+                        if (e.Property == property)
+                        {
+                            observer.OnNext(e.NewValue);
+                        }
+                    };
+
+                    observer.OnNext(o.GetValue(property));
+
+                    o.PropertyChanged += handler;
+
+                    return Disposable.Create(() =>
+                    {
+                        o.PropertyChanged -= handler;
+                    });
+                },
+                GetDescription(o, property));
+        }
+
+        /// <summary>
+        /// Gets an observable for a <see cref="PerspexProperty"/>.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        /// <typeparam name="T">The property type.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <returns>An observable.</returns>
+        public static IObservable<T> GetObservable<T>(this IPerspexObject o, PerspexProperty<T> property)
+        {
+            Contract.Requires<ArgumentNullException>(o != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+
+            return o.GetObservable((PerspexProperty)property).Cast<T>();
+        }
+
+        /// <summary>
+        /// Gets an observable for a <see cref="PerspexProperty"/>.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <returns>
+        /// An observable which when subscribed pushes the old and new values of the property each
+        /// time it is changed.
+        /// </returns>
+        public static IObservable<Tuple<T, T>> GetObservableWithHistory<T>(
+            this IPerspexObject o, 
+            PerspexProperty<T> property)
+        {
+            Contract.Requires<ArgumentNullException>(o != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+
+            return new PerspexObservable<Tuple<T, T>>(
+                observer =>
+                {
+                    EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
+                    {
+                        if (e.Property == property)
+                        {
+                            observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
+                        }
+                    };
+
+                    o.PropertyChanged += handler;
+
+                    return Disposable.Create(() =>
+                    {
+                        o.PropertyChanged -= handler;
+                    });
+                },
+                GetDescription(o, property));
+        }
+
+        /// <summary>
+        /// Binds a property to a subject according to a <see cref="BindingMode"/>.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        /// <param name="property">The property to bind.</param>
+        /// <param name="source">The binding source.</param>
+        /// <param name="mode">The binding mode.</param>
+        /// <param name="priority">The binding priority.</param>
+        /// <returns></returns>
+        public static IDisposable Bind(
+            this IPerspexObject o, 
+            PerspexProperty property,
+            ISubject<object> source,
+            BindingMode mode,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            Contract.Requires<ArgumentNullException>(o != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+            Contract.Requires<ArgumentNullException>(source != null);
+
+            switch (mode)
+            {
+                case BindingMode.Default:
+                case BindingMode.OneWay:
+                    return o.Bind(property, source, priority);
+                case BindingMode.TwoWay:
+                    return new CompositeDisposable(
+                        o.Bind(property, source, priority),
+                        o.GetObservable(property).Subscribe(source));
+                case BindingMode.OneTime:
+                    return source.Take(1).Subscribe(x => o.SetValue(property, x, priority));
+                case BindingMode.OneWayToSource:
+                    return o.GetObservable(property).Subscribe(source);
+                default:
+                    throw new ArgumentException("Invalid binding mode.");
+            }
+        }
+
         /// <summary>
         /// Subscribes to a property changed notifications for changes that originate from a
         /// <typeparamref name="TTarget"/>.
@@ -52,6 +179,17 @@ namespace Perspex
             return observable.Subscribe(e => SubscribeAdapter(e, handler));
         }
 
+        /// <summary>
+        /// Gets a description of a property that van be used in observables.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        /// <param name="property">The property</param>
+        /// <returns>The description.</returns>
+        private static string GetDescription(IPerspexObject o, PerspexProperty property)
+        {
+            return $"{o.GetType().Name}.{property.Name}";
+        }
+
         /// <summary>
         /// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{PerspexPropertyChangedEventArgs},
         /// Func{TTarget, Action{PerspexPropertyChangedEventArgs}})"/>.

+ 2 - 2
src/Perspex.Controls/Presenters/ScrollContentPresenter.cs

@@ -61,7 +61,7 @@ namespace Perspex.Controls.Presenters
         {
             AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested);
 
-            GetObservable(ChildProperty).Subscribe(ChildChanged);
+            this.GetObservable(ChildProperty).Subscribe(ChildChanged);
         }
 
         /// <summary>
@@ -236,7 +236,7 @@ namespace Perspex.Controls.Presenters
             {
                 scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
                 _scrollableSubscription = new CompositeDisposable(
-                    GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
+                    this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
                     Disposable.Create(() => scrollable.InvalidateScroll = null));
                 UpdateFromScrollable(scrollable);
             }

+ 4 - 4
src/Perspex.Controls/Presenters/TextPresenter.cs

@@ -33,15 +33,15 @@ namespace Perspex.Controls.Presenters
             _caretTimer.Interval = TimeSpan.FromMilliseconds(500);
             _caretTimer.Tick += CaretTimerTick;
 
-            _canScrollHorizontally = GetObservable(TextWrappingProperty)
+            _canScrollHorizontally = this.GetObservable(TextWrappingProperty)
                 .Select(x => x == TextWrapping.NoWrap);
 
             Observable.Merge(
-                GetObservable(SelectionStartProperty),
-                GetObservable(SelectionEndProperty))
+                this.GetObservable(SelectionStartProperty),
+                this.GetObservable(SelectionEndProperty))
                 .Subscribe(_ => InvalidateFormattedText());
 
-            GetObservable(CaretIndexProperty)
+            this.GetObservable(CaretIndexProperty)
                 .Subscribe(CaretIndexChanged);
         }
 

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

@@ -37,7 +37,7 @@ namespace Perspex.Controls.Primitives
         /// </summary>
         public AccessText()
         {
-            GetObservable(TextProperty).Subscribe(TextChanged);
+            this.GetObservable(TextProperty).Subscribe(TextChanged);
         }
 
         /// <summary>

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

@@ -36,10 +36,10 @@ namespace Perspex.Controls.Primitives
         public ScrollBar()
         {
             var isVisible = Observable.Merge(
-                GetObservable(MinimumProperty).Select(_ => Unit.Default),
-                GetObservable(MaximumProperty).Select(_ => Unit.Default),
-                GetObservable(ViewportSizeProperty).Select(_ => Unit.Default),
-                GetObservable(VisibilityProperty).Select(_ => Unit.Default))
+                this.GetObservable(MinimumProperty).Select(_ => Unit.Default),
+                this.GetObservable(MaximumProperty).Select(_ => Unit.Default),
+                this.GetObservable(ViewportSizeProperty).Select(_ => Unit.Default),
+                this.GetObservable(VisibilityProperty).Select(_ => Unit.Default))
                 .Select(_ => CalculateIsVisible());
             Bind(IsVisibleProperty, isVisible, BindingPriority.Style);
         }

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

@@ -231,7 +231,7 @@ namespace Perspex.Controls.Primitives
             // If the binding is a template binding, then complete when the Template changes.
             if (source.Priority == BindingPriority.TemplatedParent)
             {
-                var templateChanged = GetObservable(TemplateProperty).Skip(1);
+                var templateChanged = this.GetObservable(TemplateProperty).Skip(1);
 
                 result.SourceObservable = result.Source.GetObservable(result.Property)
                     .TakeUntil(templateChanged);

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

@@ -38,7 +38,7 @@ namespace Perspex.Controls.Primitives
 
         public Track()
         {
-            GetObservableWithHistory(ThumbProperty).Subscribe(val =>
+            this.GetObservableWithHistory(ThumbProperty).Subscribe(val =>
             {
                 if (val.Item1 != null)
                 {

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

@@ -12,7 +12,7 @@ namespace Perspex.Controls
     {
         public RadioButton()
         {
-            GetObservable(IsCheckedProperty).Subscribe(IsCheckedChanged);
+            this.GetObservable(IsCheckedProperty).Subscribe(IsCheckedChanged);
         }
 
         protected override void Toggle()

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

@@ -135,8 +135,8 @@ namespace Perspex.Controls
         public ScrollViewer()
         {
             var extentAndViewport = Observable.CombineLatest(
-                GetObservable(ExtentProperty),
-                GetObservable(ViewportProperty))
+                this.GetObservable(ExtentProperty),
+                this.GetObservable(ViewportProperty))
                 .Select(x => new { Extent = x[0], Viewport = x[1] });
 
             Bind(
@@ -155,15 +155,15 @@ namespace Perspex.Controls
                 VerticalScrollBarMaximumProperty,
                 extentAndViewport.Select(x => Max(x.Extent.Height - x.Viewport.Height, 0)));
 
-            GetObservable(OffsetProperty).Subscribe(x =>
+            this.GetObservable(OffsetProperty).Subscribe(x =>
             {
                 SetValue(HorizontalScrollBarValueProperty, x.X);
                 SetValue(VerticalScrollBarValueProperty, x.Y);
             });
 
             var scrollBarOffset = Observable.CombineLatest(
-                GetObservable(HorizontalScrollBarValueProperty),
-                GetObservable(VerticalScrollBarValueProperty))
+                this.GetObservable(HorizontalScrollBarValueProperty),
+                this.GetObservable(VerticalScrollBarValueProperty))
                 .Select(x => new Vector(x[0], x[1]))
                 .Subscribe(x => Offset = x);
         }

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

@@ -106,10 +106,10 @@ namespace Perspex.Controls
         public TextBlock()
         {
             Observable.Merge(
-                GetObservable(TextProperty).Select(_ => Unit.Default),
-                GetObservable(TextAlignmentProperty).Select(_ => Unit.Default),
-                GetObservable(FontSizeProperty).Select(_ => Unit.Default),
-                GetObservable(FontStyleProperty).Select(_ => Unit.Default))
+                this.GetObservable(TextProperty).Select(_ => Unit.Default),
+                this.GetObservable(TextAlignmentProperty).Select(_ => Unit.Default),
+                this.GetObservable(FontSizeProperty).Select(_ => Unit.Default),
+                this.GetObservable(FontStyleProperty).Select(_ => Unit.Default))
                 .Subscribe(_ =>
                 {
                     InvalidateFormattedText();

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

@@ -73,7 +73,7 @@ namespace Perspex.Controls
 
         public TextBox()
         {
-            var canScrollHorizontally = GetObservable(AcceptsReturnProperty)
+            var canScrollHorizontally = this.GetObservable(AcceptsReturnProperty)
                 .Select(x => !x);
 
             Bind(
@@ -81,7 +81,7 @@ namespace Perspex.Controls
                 canScrollHorizontally,
                 BindingPriority.Style);
 
-            var horizontalScrollBarVisibility = GetObservable(AcceptsReturnProperty)
+            var horizontalScrollBarVisibility = this.GetObservable(AcceptsReturnProperty)
                 .Select(x => x ? ScrollBarVisibility.Auto : ScrollBarVisibility.Hidden);
 
             Bind(

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

@@ -115,8 +115,8 @@ namespace Perspex.Controls
             _accessKeyHandler?.SetOwner(this);
             styler?.ApplyStyles(this);
 
-            GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.ClientSize = x);
-            GetObservable(PointerOverElementProperty)
+            this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.ClientSize = x);
+            this.GetObservable(PointerOverElementProperty)
                 .Select(
                     x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
                 .Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor));

+ 1 - 1
src/Perspex.Diagnostics/DevTools.cs

@@ -21,7 +21,7 @@ namespace Perspex.Diagnostics
         public DevTools()
         {
             _viewModel = new DevToolsViewModel();
-            GetObservable(RootProperty).Subscribe(x => _viewModel.Root = x);
+            this.GetObservable(RootProperty).Subscribe(x => _viewModel.Root = x);
 
             InitializeComponent();
         }

+ 1 - 1
src/Perspex.Diagnostics/Views/ControlDetailsView.cs

@@ -19,7 +19,7 @@ namespace Perspex.Diagnostics.Views
         public ControlDetailsView()
         {
             InitializeComponent();
-            GetObservable(DataContextProperty)
+            this.GetObservable(DataContextProperty)
                 .Subscribe(x => ViewModel = (ControlDetailsViewModel)x);
         }
 

+ 1 - 1
src/Perspex.Diagnostics/Views/LogicalTreeView.cs

@@ -20,7 +20,7 @@ namespace Perspex.Diagnostics.Views
         public LogicalTreeView()
         {
             InitializeComponent();
-            GetObservable(DataContextProperty)
+            this.GetObservable(DataContextProperty)
                 .Subscribe(x => ViewModel = (LogicalTreeViewModel)x);
         }
 

+ 1 - 1
src/Perspex.Diagnostics/Views/VisualTreeView.cs

@@ -21,7 +21,7 @@ namespace Perspex.Diagnostics.Views
         public VisualTreeView()
         {
             InitializeComponent();
-            GetObservable(DataContextProperty)
+            this.GetObservable(DataContextProperty)
                 .Subscribe(x => ViewModel = (VisualTreeViewModel)x);
         }
 

+ 2 - 2
src/Perspex.SceneGraph/Animation/CrossFade.cs

@@ -58,7 +58,7 @@ namespace Perspex.Animation
             if (from != null)
             {
                 tasks.Add(Animate.Property(
-                    (IObservablePropertyBag)from,
+                    (IPerspexObject)from,
                     Visual.OpacityProperty,
                     from.Opacity,
                     0,
@@ -72,7 +72,7 @@ namespace Perspex.Animation
                 to.IsVisible = true;
 
                 tasks.Add(Animate.Property(
-                    (IObservablePropertyBag)to,
+                    (IPerspexObject)to,
                     Visual.OpacityProperty,
                     0,
                     1,

+ 1 - 1
src/Perspex.SceneGraph/Media/MatrixTransform.cs

@@ -21,7 +21,7 @@ namespace Perspex.Media
         /// </summary>
         public MatrixTransform()
         {
-            GetObservable(MatrixProperty).Subscribe(_ => RaiseChanged());
+            this.GetObservable(MatrixProperty).Subscribe(_ => RaiseChanged());
         }
 
         /// <summary>

+ 1 - 1
src/Perspex.SceneGraph/Media/RotateTransform.cs

@@ -21,7 +21,7 @@ namespace Perspex.Media
         /// </summary>
         public RotateTransform()
         {
-            GetObservable(AngleProperty).Subscribe(_ => RaiseChanged());
+            this.GetObservable(AngleProperty).Subscribe(_ => RaiseChanged());
         }
 
         /// <summary>

+ 2 - 2
src/Perspex.SceneGraph/Media/TranslateTransform.cs

@@ -27,8 +27,8 @@ namespace Perspex.Media
         /// </summary>
         public TranslateTransform()
         {
-            GetObservable(XProperty).Subscribe(_ => RaiseChanged());
-            GetObservable(YProperty).Subscribe(_ => RaiseChanged());
+            this.GetObservable(XProperty).Subscribe(_ => RaiseChanged());
+            this.GetObservable(YProperty).Subscribe(_ => RaiseChanged());
         }
 
         /// <summary>

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

@@ -10,7 +10,7 @@ namespace Perspex.Styling
     /// <summary>
     /// Interface for styleable elements.
     /// </summary>
-    public interface IStyleable : IObservablePropertyBag, INamed
+    public interface IStyleable : IPerspexObject, INamed
     {
         /// <summary>
         /// Raised when the control's style should be removed.

+ 1 - 8
src/Perspex.Styling/Styling/ITemplatedControl.cs

@@ -5,14 +5,7 @@ using System;
 
 namespace Perspex.Styling
 {
-    public interface ITemplatedControl
+    public interface ITemplatedControl : IPerspexObject
     {
-        /// <summary>
-        /// Gets an observable for a <see cref="PerspexProperty"/>.
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="property">The property to get the observable for.</param>
-        /// <returns>The observable.</returns>
-        IObservable<T> GetObservable<T>(PerspexProperty<T> property);
     }
 }

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

@@ -240,7 +240,7 @@ namespace Perspex.Styling
 
         private static SelectorMatch MatchPropertyEquals(IStyleable x, PerspexProperty property, object value)
         {
-            if (!x.IsRegistered(property))
+            if (!PerspexPropertyRegistry.Instance.IsRegistered(x, property))
             {
                 return SelectorMatch.False;
             }

+ 56 - 68
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs

@@ -1,14 +1,11 @@
 // 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.Globalization;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
 using Moq;
 using Perspex.Controls;
 using Perspex.Markup.Data;
 using Perspex.Markup.Xaml.Data;
+using ReactiveUI;
 using Xunit;
 
 namespace Perspex.Markup.Xaml.UnitTests.Data
@@ -18,101 +15,101 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
         [Fact]
         public void OneWay_Binding_Should_Be_Set_Up()
         {
-            var target = CreateTarget();
+            var source = new Source { Foo = "foo" };
+            var target = new TextBlock { DataContext = source };
             var binding = new Binding
             {
                 Path = "Foo",
                 Mode = BindingMode.OneWay,
             };
 
-            binding.Bind(target.Object, TextBox.TextProperty);
+            binding.Bind(target, TextBox.TextProperty);
 
-            target.Verify(x => x.Bind(
-                TextBox.TextProperty, 
-                It.IsAny<IObservable<object>>(), 
-                BindingPriority.LocalValue));
+            Assert.Equal("foo", target.Text);
+            source.Foo = "bar";
+            Assert.Equal("bar", target.Text);
+            target.Text = "baz";
+            Assert.Equal("bar", source.Foo);
         }
 
         [Fact]
         public void TwoWay_Binding_Should_Be_Set_Up()
         {
-            var target = CreateTarget();
+            var source = new Source { Foo = "foo" };
+            var target = new TextBlock { DataContext = source };
             var binding = new Binding
             {
                 Path = "Foo",
                 Mode = BindingMode.TwoWay,
             };
 
-            binding.Bind(target.Object, TextBox.TextProperty);
+            binding.Bind(target, TextBox.TextProperty);
 
-            target.Verify(x => x.BindTwoWay(
-                TextBox.TextProperty,
-                It.IsAny<ISubject<object>>(),
-                BindingPriority.LocalValue));
+            Assert.Equal("foo", target.Text);
+            source.Foo = "bar";
+            Assert.Equal("bar", target.Text);
+            target.Text = "baz";
+            Assert.Equal("baz", source.Foo);
         }
 
         [Fact]
         public void OneTime_Binding_Should_Be_Set_Up()
         {
-            var dataContext = new BehaviorSubject<object>(null);
-            var expression = new BehaviorSubject<object>(null);
-            var target = CreateTarget(dataContext: dataContext);
+            var source = new Source { Foo = "foo" };
+            var target = new TextBlock { DataContext = source };
             var binding = new Binding
             {
                 Path = "Foo",
                 Mode = BindingMode.OneTime,
             };
 
-            binding.Bind(target.Object, TextBox.TextProperty, expression);
+            binding.Bind(target, TextBox.TextProperty);
 
-            target.Verify(x => x.SetValue(
-                (PerspexProperty)TextBox.TextProperty, 
-                null, 
-                BindingPriority.LocalValue));
-            target.ResetCalls();
-
-            expression.OnNext("foo");
-            dataContext.OnNext(1);
-
-            target.Verify(x => x.SetValue(
-                (PerspexProperty)TextBox.TextProperty,
-                "foo",
-                BindingPriority.LocalValue));
+            Assert.Equal("foo", target.Text);
+            source.Foo = "bar";
+            Assert.Equal("foo", target.Text);
+            target.Text = "baz";
+            Assert.Equal("bar", source.Foo);
         }
 
         [Fact]
         public void OneWayToSource_Binding_Should_Be_Set_Up()
         {
-            var textObservable = new Mock<IObservable<string>>();
-            var expression = new Mock<ISubject<object>>();
-            var target = CreateTarget(text: textObservable.Object);
+            var source = new Source { Foo = "foo" };
+            var target = new TextBlock { DataContext = source, Text = "bar" };
             var binding = new Binding
             {
                 Path = "Foo",
                 Mode = BindingMode.OneWayToSource,
             };
 
-            binding.Bind(target.Object, TextBox.TextProperty, expression.Object);
+            binding.Bind(target, TextBox.TextProperty);
 
-            textObservable.Verify(x => x.Subscribe(expression.Object));
+            Assert.Equal("bar", source.Foo);
+            target.Text = "baz";
+            Assert.Equal("baz", source.Foo);
+            source.Foo = "quz";
+            Assert.Equal("baz", target.Text);
         }
 
         [Fact]
         public void Default_BindingMode_Should_Be_Used()
         {
-            var target = CreateTarget(null);
+            // Default for TextBox.Text is two-way.
+            var source = new Source { Foo = "foo" };
+            var target = new TextBlock { DataContext = source };
             var binding = new Binding
             {
                 Path = "Foo",
             };
 
-            binding.Bind(target.Object, TextBox.TextProperty);
+            binding.Bind(target, TextBox.TextProperty);
 
-            // Default for TextBox.Text is two-way.
-            target.Verify(x => x.BindTwoWay(
-                TextBox.TextProperty,
-                It.IsAny<ISubject<object>>(),
-                BindingPriority.LocalValue));
+            Assert.Equal("foo", target.Text);
+            source.Foo = "bar";
+            Assert.Equal("bar", target.Text);
+            target.Text = "baz";
+            Assert.Equal("baz", source.Foo);
         }
 
         [Fact]
@@ -164,13 +161,13 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
         [Fact]
         public void Should_Use_DefaultValueConverter_When_No_Converter_Specified()
         {
-            var target = CreateTarget(null);
+            var target = new TextBlock(); ;
             var binding = new Binding
             {
                 Path = "Foo",
             };
 
-            var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
+            var result = binding.CreateSubject(target, TextBox.TextProperty.PropertyType);
 
             Assert.IsType<DefaultValueConverter>(((ExpressionSubject)result).Converter);
         }
@@ -178,7 +175,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
         [Fact]
         public void Should_Use_Supplied_Converter()
         {
-            var target = CreateTarget(null);
+            var target = new TextBlock();
             var converter = new Mock<IValueConverter>();
             var binding = new Binding
             {
@@ -186,7 +183,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
                 Path = "Foo",
             };
 
-            var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
+            var result = binding.CreateSubject(target, TextBox.TextProperty.PropertyType);
 
             Assert.Same(converter.Object, ((ExpressionSubject)result).Converter);
         }
@@ -194,7 +191,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
         [Fact]
         public void Should_Pass_ConverterParameter_To_Supplied_Converter()
         {
-            var target = CreateTarget();
+            var target = new TextBlock();
             var converter = new Mock<IValueConverter>();
             var binding = new Binding
             {
@@ -203,7 +200,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
                 Path = "Bar",
             };
 
-            var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
+            var result = binding.CreateSubject(target, TextBox.TextProperty.PropertyType);
 
             Assert.Same("foo", ((ExpressionSubject)result).ConverterParameter);
         }
@@ -261,24 +258,15 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
             Assert.Equal(2, vm.Bar);
         }
 
-        private Mock<IObservablePropertyBag> CreateTarget(object dataContext)
-        {
-            return CreateTarget(dataContext: Observable.Never<object>().StartWith(dataContext));
-        }
-
-        private Mock<IObservablePropertyBag> CreateTarget(
-            IObservable<object> dataContext = null,
-            IObservable<string> text = null)
+        public class Source : ReactiveObject
         {
-            var result = new Mock<IObservablePropertyBag>();
-
-            dataContext = dataContext ?? Observable.Never<object>().StartWith((object)null);
-            text = text ?? Observable.Never<string>().StartWith((string)null);
+            private string _foo;
 
-            result.Setup(x => x.GetObservable(Control.DataContextProperty)).Returns(dataContext);
-            result.Setup(x => x.GetObservable((PerspexProperty)Control.DataContextProperty)).Returns(dataContext);
-            result.Setup(x => x.GetObservable((PerspexProperty)TextBox.TextProperty)).Returns(text);
-            return result;
+            public string Foo
+            {
+                get { return _foo; }
+                set { this.RaiseAndSetIfChanged(ref _foo, value); }
+            }
         }
 
         private class OldDataContextViewModel
@@ -297,7 +285,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
 
             public OldDataContextTest()
             {
-                Bind(BarProperty, GetObservable(FooProperty));
+                Bind(BarProperty, this.GetObservable(FooProperty));
             }
         }
     }

+ 10 - 30
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs

@@ -48,46 +48,26 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
 
             binding.Bind(target.Object, TextBox.TextProperty);
 
-            target.Verify(x => x.BindTwoWay(
+            target.Verify(x => x.Bind(
                 TextBox.TextProperty,
                 It.IsAny<ISubject<object>>(),
                 BindingPriority.TemplatedParent));
         }
 
-        [Fact]
-        public void OneWayToSource_Binding_Should_Be_Set_Up()
-        {
-            var textObservable = new Mock<IObservable<string>>();
-            var expression = new Mock<ISubject<object>>();
-            var target = CreateTarget(text: textObservable.Object);
-            var binding = new Binding
-            {
-                Path = "Foo",
-                Mode = BindingMode.OneWayToSource,
-            };
-
-            binding.Bind(target.Object, TextBox.TextProperty, expression.Object);
-
-            textObservable.Verify(x => x.Subscribe(expression.Object));
-        }
-
-        private Mock<IObservablePropertyBag> CreateTarget(ITemplatedControl templatedParent)
+        private Mock<IPerspexObject> CreateTarget(ITemplatedControl templatedParent)
         {
-            return CreateTarget(templatedParent: Observable.Never<ITemplatedControl>().StartWith(templatedParent));
+            return CreateTarget(templatedParent: templatedParent);
         }
 
-        private Mock<IObservablePropertyBag> CreateTarget(
-            IObservable<ITemplatedControl> templatedParent = null,
-            IObservable<string> text = null)
+        private Mock<IPerspexObject> CreateTarget(
+            ITemplatedControl templatedParent = null,
+            string text = null)
         {
-            var result = new Mock<IObservablePropertyBag>();
-
-            templatedParent = templatedParent ?? Observable.Never<ITemplatedControl>().StartWith((ITemplatedControl)null);
-            text = text ?? Observable.Never<string>().StartWith((string)null);
+            var result = new Mock<IPerspexObject>();
 
-            result.Setup(x => x.GetObservable(Control.TemplatedParentProperty)).Returns(templatedParent);
-            result.Setup(x => x.GetObservable((PerspexProperty)Control.TemplatedParentProperty)).Returns(templatedParent);
-            result.Setup(x => x.GetObservable((PerspexProperty)TextBox.TextProperty)).Returns(text);
+            result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent);
+            result.Setup(x => x.GetValue((PerspexProperty)Control.TemplatedParentProperty)).Returns(templatedParent);
+            result.Setup(x => x.GetValue((PerspexProperty)TextBox.TextProperty)).Returns(text);
             return result;
         }
     }

+ 1 - 4
tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs

@@ -6,7 +6,6 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Reactive.Linq;
-using System.Reactive.Subjects;
 using Moq;
 using Perspex.Controls;
 using Perspex.Markup.Xaml.Data;
@@ -31,10 +30,8 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
                 }
             };
 
-            var target = new Mock<IObservablePropertyBag>();
+            var target = new Mock<IPerspexObject>();
             target.Setup(x => x.GetValue(Control.DataContextProperty)).Returns(source);
-            target.Setup(x => x.GetObservable(Control.DataContextProperty)).Returns(
-                Observable.Never<object>().StartWith(source));
 
             var subject = binding.CreateSubject(target.Object, typeof(string));
             var result = await subject.Take(1);

+ 7 - 46
tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs

@@ -6,11 +6,8 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Reactive;
 using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using System.Threading.Tasks;
 using Perspex.Collections;
 using Perspex.Controls;
-using Perspex.Styling;
 using Xunit;
 
 namespace Perspex.Styling.UnitTests
@@ -80,6 +77,8 @@ namespace Perspex.Styling.UnitTests
                 Classes = new Classes();
             }
 
+            public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
+
             public Classes Classes { get; }
 
             public string Name { get; set; }
@@ -96,42 +95,29 @@ namespace Perspex.Styling.UnitTests
 
             IObservable<Unit> IStyleable.StyleDetach { get; }
 
-            public IPropertyBag InheritanceParent
-            {
-                get
-                {
-                    throw new NotImplementedException();
-                }
-            }
-
             IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
 
-            public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
-            {
-                throw new NotImplementedException();
-            }
-
-            public void SetValue(PerspexProperty property, object value, BindingPriority priority)
+            public object GetValue(PerspexProperty property)
             {
                 throw new NotImplementedException();
             }
 
-            public IObservable<object> GetObservable(PerspexProperty property)
+            public T GetValue<T>(PerspexProperty<T> property)
             {
                 throw new NotImplementedException();
             }
 
-            public bool IsRegistered(PerspexProperty property)
+            public void SetValue(PerspexProperty property, object value, BindingPriority priority)
             {
                 throw new NotImplementedException();
             }
 
-            public void ClearValue(PerspexProperty property)
+            public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
             {
                 throw new NotImplementedException();
             }
 
-            public object GetValue(PerspexProperty property)
+            public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
             {
                 throw new NotImplementedException();
             }
@@ -145,31 +131,6 @@ namespace Perspex.Styling.UnitTests
             {
                 throw new NotImplementedException();
             }
-
-            public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
-            {
-                throw new NotImplementedException();
-            }
-
-            public T GetValue<T>(PerspexProperty<T> property)
-            {
-                throw new NotImplementedException();
-            }
-
-            public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IDisposable BindTwoWay(PerspexProperty property, ISubject<object> source, BindingPriority priority = BindingPriority.LocalValue)
-            {
-                throw new NotImplementedException();
-            }
         }
 
         public class TestLogical1 : TestLogical

+ 7 - 45
tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs

@@ -5,11 +5,9 @@ using System;
 using System.Linq;
 using System.Reactive;
 using System.Reactive.Linq;
-using System.Reactive.Subjects;
 using System.Threading.Tasks;
 using Perspex.Collections;
 using Perspex.Controls;
-using Perspex.Styling;
 using Xunit;
 
 namespace Perspex.Styling.UnitTests
@@ -110,6 +108,8 @@ namespace Perspex.Styling.UnitTests
                 Classes = new Classes();
             }
 
+            public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
+
             public Classes Classes { get; }
 
             public string Name { get; set; }
@@ -124,44 +124,31 @@ namespace Perspex.Styling.UnitTests
 
             public ITemplatedControl TemplatedParent { get; }
 
-            public IPropertyBag InheritanceParent
-            {
-                get
-                {
-                    throw new NotImplementedException();
-                }
-            }
-
             IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
 
             IObservable<Unit> IStyleable.StyleDetach { get; }
 
-            public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
-            {
-                throw new NotImplementedException();
-            }
-
-            public void SetValue(PerspexProperty property, object value, BindingPriority priority)
+            public object GetValue(PerspexProperty property)
             {
                 throw new NotImplementedException();
             }
 
-            public IObservable<object> GetObservable(PerspexProperty property)
+            public T GetValue<T>(PerspexProperty<T> property)
             {
                 throw new NotImplementedException();
             }
 
-            public bool IsRegistered(PerspexProperty property)
+            public void SetValue(PerspexProperty property, object value, BindingPriority priority)
             {
                 throw new NotImplementedException();
             }
 
-            public void ClearValue(PerspexProperty property)
+            public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
             {
                 throw new NotImplementedException();
             }
 
-            public object GetValue(PerspexProperty property)
+            public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
             {
                 throw new NotImplementedException();
             }
@@ -175,31 +162,6 @@ namespace Perspex.Styling.UnitTests
             {
                 throw new NotImplementedException();
             }
-
-            public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
-            {
-                throw new NotImplementedException();
-            }
-
-            public T GetValue<T>(PerspexProperty<T> property)
-            {
-                throw new NotImplementedException();
-            }
-
-            public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IDisposable BindTwoWay(PerspexProperty property, ISubject<object> source, BindingPriority priority = BindingPriority.LocalValue)
-            {
-                throw new NotImplementedException();
-            }
         }
 
         public class TestLogical1 : TestLogical

+ 8 - 45
tests/Perspex.Styling.UnitTests/TestControlBase.cs

@@ -3,7 +3,6 @@
 
 using System;
 using System.Reactive;
-using System.Reactive.Subjects;
 using Perspex.Collections;
 using Perspex.Controls;
 
@@ -17,6 +16,8 @@ namespace Perspex.Styling.UnitTests
             SubscribeCheckObservable = new TestObservable();
         }
 
+        public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
+
         public string Name { get; set; }
 
         public virtual Classes Classes { get; set; }
@@ -31,79 +32,41 @@ namespace Perspex.Styling.UnitTests
             set;
         }
 
-        public IPropertyBag InheritanceParent
-        {
-            get
-            {
-                throw new NotImplementedException();
-            }
-        }
-
         IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
 
         IObservable<Unit> IStyleable.StyleDetach { get; }
 
-        public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
-        {
-            throw new NotImplementedException();
-        }
-
-        public void SetValue(PerspexProperty property, object value, BindingPriority priority)
-        {
-            throw new NotImplementedException();
-        }
-
-        public IObservable<object> GetObservable(PerspexProperty property)
-        {
-            throw new NotImplementedException();
-        }
-
-        public bool IsRegistered(PerspexProperty property)
-        {
-            throw new NotImplementedException();
-        }
-
-        public void ClearValue(PerspexProperty property)
-        {
-            throw new NotImplementedException();
-        }
-
         public object GetValue(PerspexProperty property)
         {
             throw new NotImplementedException();
         }
 
-        public bool IsSet(PerspexProperty property)
-        {
-            throw new NotImplementedException();
-        }
-
-        public IDisposable Bind<T>(PerspexProperty<T> property, IObservable<T> source, BindingPriority priority = BindingPriority.LocalValue)
+        public T GetValue<T>(PerspexProperty<T> property)
         {
             throw new NotImplementedException();
         }
 
-        public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
+        public void SetValue(PerspexProperty property, object value, BindingPriority priority)
         {
             throw new NotImplementedException();
         }
 
-        public T GetValue<T>(PerspexProperty<T> property)
+        public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
         {
             throw new NotImplementedException();
         }
 
-        public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
+        public bool IsSet(PerspexProperty property)
         {
             throw new NotImplementedException();
         }
 
-        public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
+        public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
         {
             throw new NotImplementedException();
         }
 
-        public IDisposable BindTwoWay(PerspexProperty property, ISubject<object> source, BindingPriority priority = BindingPriority.LocalValue)
+        public IDisposable Bind<T>(PerspexProperty<T> property, IObservable<T> source, BindingPriority priority = BindingPriority.LocalValue)
         {
             throw new NotImplementedException();
         }

+ 7 - 50
tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs

@@ -2,9 +2,7 @@
 // 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;
-using System.Reactive.Subjects;
 using Perspex.Collections;
 using Perspex.Controls;
 
@@ -12,6 +10,8 @@ namespace Perspex.Styling.UnitTests
 {
     public abstract class TestTemplatedControl : ITemplatedControl, IStyleable
     {
+        public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
+
         public abstract Classes Classes
         {
             get;
@@ -32,29 +32,16 @@ namespace Perspex.Styling.UnitTests
             get;
         }
 
-        public abstract IEnumerable<IVisual> VisualChildren
-        {
-            get;
-        }
-
-        public IPropertyBag InheritanceParent
-        {
-            get
-            {
-                throw new NotImplementedException();
-            }
-        }
-
         IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
 
         IObservable<Unit> IStyleable.StyleDetach { get; }
 
-        public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
+        public object GetValue(PerspexProperty property)
         {
             throw new NotImplementedException();
         }
 
-        public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
+        public T GetValue<T>(PerspexProperty<T> property)
         {
             throw new NotImplementedException();
         }
@@ -64,27 +51,12 @@ namespace Perspex.Styling.UnitTests
             throw new NotImplementedException();
         }
 
-        public IObservable<object> GetObservable(PerspexProperty property)
-        {
-            throw new NotImplementedException();
-        }
-
-        public bool IsRegistered(PerspexProperty property)
-        {
-            throw new NotImplementedException();
-        }
-
-        public void ClearValue(PerspexProperty property)
-        {
-            throw new NotImplementedException();
-        }
-
-        public object GetValue(PerspexProperty property)
+        public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
         {
             throw new NotImplementedException();
         }
 
-        public bool IsSet(PerspexProperty property)
+        public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
         {
             throw new NotImplementedException();
         }
@@ -94,22 +66,7 @@ namespace Perspex.Styling.UnitTests
             throw new NotImplementedException();
         }
 
-        public T GetValue<T>(PerspexProperty<T> property)
-        {
-            throw new NotImplementedException();
-        }
-
-        public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
-        {
-            throw new NotImplementedException();
-        }
-
-        public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
-        {
-            throw new NotImplementedException();
-        }
-
-        public IDisposable BindTwoWay(PerspexProperty property, ISubject<object> source, BindingPriority priority = BindingPriority.LocalValue)
+        public bool IsSet(PerspexProperty property)
         {
             throw new NotImplementedException();
         }