From 30a756fb8305a4997bc2533260d5cda8c8d44d24 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 17 Jan 2016 22:23:33 +0100 Subject: [PATCH 01/10] Refactored binding somewhat. - Simplified PerpsexObject interface to a simplified single interface, IPerspexObject - Moved GetObservable etc to extension method --- samples/TestApplicationShared/GalleryStyle.cs | 2 +- .../Perspex.Markup.Xaml/Data/Binding.cs | 49 +----- .../Perspex.Markup.Xaml/Data/IXamlBinding.cs | 4 +- .../Perspex.Markup.Xaml/Data/MultiBinding.cs | 6 +- src/Perspex.Animation/Animate.cs | 4 +- src/Perspex.Base/IObservablePropertyBag.cs | 95 ------------ .../{IPropertyBag.cs => IPerspexObject.cs} | 60 +++++--- src/Perspex.Base/Perspex.Base.csproj | 3 +- src/Perspex.Base/PerspexObject.cs | 93 +----------- src/Perspex.Base/PerspexObjectExtensions.cs | 140 +++++++++++++++++- .../Presenters/ScrollContentPresenter.cs | 4 +- .../Presenters/TextPresenter.cs | 8 +- src/Perspex.Controls/Primitives/AccessText.cs | 2 +- src/Perspex.Controls/Primitives/ScrollBar.cs | 8 +- .../Primitives/TemplatedControl.cs | 2 +- src/Perspex.Controls/Primitives/Track.cs | 2 +- src/Perspex.Controls/RadioButton.cs | 2 +- src/Perspex.Controls/ScrollViewer.cs | 10 +- src/Perspex.Controls/TextBlock.cs | 8 +- src/Perspex.Controls/TextBox.cs | 4 +- src/Perspex.Controls/TopLevel.cs | 4 +- src/Perspex.Diagnostics/DevTools.cs | 2 +- .../Views/ControlDetailsView.cs | 2 +- .../Views/LogicalTreeView.cs | 2 +- .../Views/VisualTreeView.cs | 2 +- src/Perspex.SceneGraph/Animation/CrossFade.cs | 4 +- .../Media/MatrixTransform.cs | 2 +- .../Media/RotateTransform.cs | 2 +- .../Media/TranslateTransform.cs | 4 +- src/Perspex.Styling/Styling/IStyleable.cs | 2 +- .../Styling/ITemplatedControl.cs | 9 +- src/Perspex.Styling/Styling/Selectors.cs | 2 +- .../Data/BindingTests.cs | 124 +++++++--------- .../Data/BindingTests_TemplatedParent.cs | 40 ++--- .../Data/MultiBindingTests.cs | 5 +- .../SelectorTests_Child.cs | 53 +------ .../SelectorTests_Descendent.cs | 52 +------ .../TestControlBase.cs | 53 +------ .../TestTemplatedControl.cs | 57 +------ 39 files changed, 337 insertions(+), 590 deletions(-) delete mode 100644 src/Perspex.Base/IObservablePropertyBag.cs rename src/Perspex.Base/{IPropertyBag.cs => IPerspexObject.cs} (57%) diff --git a/samples/TestApplicationShared/GalleryStyle.cs b/samples/TestApplicationShared/GalleryStyle.cs index b29e22d2ec..38b29deab9 100644 --- a/samples/TestApplicationShared/GalleryStyle.cs +++ b/samples/TestApplicationShared/GalleryStyle.cs @@ -23,7 +23,7 @@ namespace TestApplication { Setters = new[] { - new Setter (TemplatedControl.TemplateProperty, new FuncControlTemplate (TabControlTemplate)) + new Setter (TemplatedControl.TemplateProperty, new FuncControlTemplate(TabControlTemplate)) } }, diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs index 00ed242855..f7546b3db8 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs @@ -55,7 +55,7 @@ namespace Perspex.Markup.Xaml.Data /// /// The target instance. /// The target property. - public void Bind(IObservablePropertyBag instance, PerspexProperty property) + public void Bind(IPerspexObject instance, PerspexProperty property) { Contract.Requires(instance != null); Contract.Requires(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 /// /// An . public ISubject CreateSubject( - IObservablePropertyBag target, + IPerspexObject target, Type targetType, bool targetIsDataContext = false) { @@ -125,42 +126,6 @@ namespace Perspex.Markup.Xaml.Data ConverterParameter); } - /// - /// Applies a binding subject to a property on an instance. - /// - /// The target instance. - /// The target property. - /// The binding subject. - internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject subject) - { - Contract.Requires(target != null); - Contract.Requires(property != null); - Contract.Requires(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() + .OfType() .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(target != null); diff --git a/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs index e72fb5eccc..144302a9a7 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs @@ -16,7 +16,7 @@ namespace Perspex.Markup.Xaml.Data /// /// The target instance. /// The target property. - void Bind(IObservablePropertyBag instance, PerspexProperty property); + void Bind(IPerspexObject instance, PerspexProperty property); /// /// 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 /// /// An . ISubject CreateSubject( - IObservablePropertyBag target, + IPerspexObject target, Type targetType, bool targetIsDataContext = false); } diff --git a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs index 4ea1feb9a2..41f67a0467 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs @@ -48,7 +48,7 @@ namespace Perspex.Markup.Xaml.Data /// /// The target instance. /// The target property. - 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 /// /// An . public ISubject CreateSubject( - IObservablePropertyBag target, + IPerspexObject target, Type targetType, bool targetIsDataContext = false) { @@ -91,7 +91,7 @@ namespace Perspex.Markup.Xaml.Data /// The target instance. /// The target property. /// The binding subject. - internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject subject) + internal void Bind(IPerspexObject target, PerspexProperty property, ISubject subject) { var mode = Mode == BindingMode.Default ? property.DefaultBindingMode : Mode; diff --git a/src/Perspex.Animation/Animate.cs b/src/Perspex.Animation/Animate.cs index 85db3e71b7..d076c23953 100644 --- a/src/Perspex.Animation/Animate.cs +++ b/src/Perspex.Animation/Animate.cs @@ -96,7 +96,7 @@ namespace Perspex.Animation /// The duration of the animation. /// An that can be used to track or stop the animation. public static Animation Property( - IObservablePropertyBag target, + IPerspexObject target, PerspexProperty property, object start, object finish, @@ -119,7 +119,7 @@ namespace Perspex.Animation /// The duration of the animation. /// An that can be used to track or stop the animation. public static Animation Property( - IObservablePropertyBag target, + IPerspexObject target, PerspexProperty property, T start, T finish, diff --git a/src/Perspex.Base/IObservablePropertyBag.cs b/src/Perspex.Base/IObservablePropertyBag.cs deleted file mode 100644 index 451cb63a89..0000000000 --- a/src/Perspex.Base/IObservablePropertyBag.cs +++ /dev/null @@ -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 -{ - /// - /// Interface for getting/setting bindings on an object. - /// - public interface IObservablePropertyBag : IPropertyBag - { - /// - /// Binds a to an observable. - /// - /// The property. - /// The observable. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - IDisposable Bind( - PerspexProperty property, - IObservable source, - BindingPriority priority = BindingPriority.LocalValue); - - /// - /// Binds a to an observable. - /// - /// The type of the property. - /// The property. - /// The observable. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - IDisposable Bind( - PerspexProperty property, - IObservable source, - BindingPriority priority = BindingPriority.LocalValue); - - /// - /// Initiates a two-way binding between s. - /// - /// The property on this object. - /// The source object. - /// The property on the source object. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - /// - /// The binding is first carried out from to this. - /// - IDisposable BindTwoWay( - PerspexProperty property, - PerspexObject source, - PerspexProperty sourceProperty, - BindingPriority priority = BindingPriority.LocalValue); - - /// - /// Initiates a two-way binding between a and an - /// . - /// - /// The property on this object. - /// The subject to bind to. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - /// - /// The binding is first carried out from to this. - /// - IDisposable BindTwoWay( - PerspexProperty property, - ISubject source, - BindingPriority priority = BindingPriority.LocalValue); - - /// - /// Gets an observable for a . - /// - /// The property. - /// An observable. - IObservable GetObservable(PerspexProperty property); - - /// - /// Gets an observable for a . - /// - /// The type of the property. - /// The property. - /// An observable. - IObservable GetObservable(PerspexProperty property); - } -} \ No newline at end of file diff --git a/src/Perspex.Base/IPropertyBag.cs b/src/Perspex.Base/IPerspexObject.cs similarity index 57% rename from src/Perspex.Base/IPropertyBag.cs rename to src/Perspex.Base/IPerspexObject.cs index fe29c349ef..47ccd7b9ff 100644 --- a/src/Perspex.Base/IPropertyBag.cs +++ b/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 { /// /// Interface for getting/setting values on an object. /// - public interface IPropertyBag + public interface IPerspexObject { /// - /// Gets the object that inherited values are inherited from. + /// Raised when a value changes on this object. /// - IPropertyBag InheritanceParent { get; } - - /// - /// Clears a 's local value. - /// - /// The property. - void ClearValue(PerspexProperty property); + event EventHandler PropertyChanged; /// /// Gets a value. @@ -34,13 +30,6 @@ namespace Perspex /// The value. T GetValue(PerspexProperty property); - /// - /// Checks whether a is registered on this object. - /// - /// The property. - /// True if the property is registered, otherwise false. - bool IsRegistered(PerspexProperty property); - /// /// Checks whether a is set on this object. /// @@ -54,7 +43,10 @@ namespace Perspex /// The property. /// The value. /// The priority of the value. - void SetValue(PerspexProperty property, object value, BindingPriority priority = BindingPriority.LocalValue); + void SetValue( + PerspexProperty property, + object value, + BindingPriority priority = BindingPriority.LocalValue); /// /// Sets a value. @@ -63,6 +55,38 @@ namespace Perspex /// The property. /// The value. /// The priority of the value. - void SetValue(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue); + void SetValue( + PerspexProperty property, + T value, + BindingPriority priority = BindingPriority.LocalValue); + + /// + /// Binds a to an observable. + /// + /// The property. + /// The observable. + /// The priority of the binding. + /// + /// A disposable which can be used to terminate the binding. + /// + IDisposable Bind( + PerspexProperty property, + IObservable source, + BindingPriority priority = BindingPriority.LocalValue); + + /// + /// Binds a to an observable. + /// + /// The type of the property. + /// The property. + /// The observable. + /// The priority of the binding. + /// + /// A disposable which can be used to terminate the binding. + /// + IDisposable Bind( + PerspexProperty property, + IObservable source, + BindingPriority priority = BindingPriority.LocalValue); } } \ No newline at end of file diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index b0f6d928ff..b08bce7e2f 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -45,8 +45,7 @@ - - + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index b95758a7e7..eeb67a2482 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -23,7 +23,7 @@ namespace Perspex /// /// This class is analogous to DependencyObject in WPF. /// - public class PerspexObject : IObservablePropertyBag, INotifyPropertyChanged + public class PerspexObject : IPerspexObject, INotifyPropertyChanged { /// /// The parent object that inherited values are inherited from. @@ -89,11 +89,6 @@ namespace Perspex remove { _inpcChanged -= value; } } - /// - /// Gets the object that inherited values are inherited from. - /// - IPropertyBag IPropertyBag.InheritanceParent => InheritanceParent; - /// /// Gets or sets the parent object that inherited 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); } - /// - /// Gets an observable for a . - /// - /// The property. - /// An observable. - public IObservable GetObservable(PerspexProperty property) - { - Contract.Requires(property != null); - - return new PerspexObservable( - observer => - { - EventHandler handler = (s, e) => - { - if (e.Property == property) - { - observer.OnNext(e.NewValue); - } - }; - - observer.OnNext(GetValue(property)); - - PropertyChanged += handler; - - return Disposable.Create(() => - { - PropertyChanged -= handler; - }); - }, - GetDescription(property)); - } - - /// - /// Gets an observable for a . - /// - /// The property type. - /// The property. - /// An observable. - public IObservable GetObservable(PerspexProperty property) - { - Contract.Requires(property != null); - - return GetObservable((PerspexProperty)property).Cast(); - } - - /// - /// Gets an observable for a . - /// - /// The type of the property. - /// The property. - /// An observable which when subscribed pushes the old and new values of the - /// property each time it is changed. - public IObservable> GetObservableWithHistory(PerspexProperty property) - { - return new PerspexObservable>( - observer => - { - EventHandler 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)); - } - /// /// Gets a value. /// @@ -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))); } /// @@ -619,7 +539,7 @@ namespace Perspex return new CompositeDisposable( Bind(property, source), - GetObservable(property).Subscribe(source)); + this.GetObservable(property).Subscribe(source)); } /// @@ -638,11 +558,6 @@ namespace Perspex } /// - bool IPropertyBag.IsRegistered(PerspexProperty property) - { - return PerspexPropertyRegistry.Instance.IsRegistered(this, property); - } - /// /// Gets all priority values set on the object. /// diff --git a/src/Perspex.Base/PerspexObjectExtensions.cs b/src/Perspex.Base/PerspexObjectExtensions.cs index 7ba3fd33d4..52c8042ac8 100644 --- a/src/Perspex.Base/PerspexObjectExtensions.cs +++ b/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 /// public static class PerspexObjectExtensions { + /// + /// Gets an observable for a . + /// + /// The object. + /// The property. + /// An observable. + public static IObservable GetObservable(this IPerspexObject o, PerspexProperty property) + { + Contract.Requires(o != null); + Contract.Requires(property != null); + + return new PerspexObservable( + observer => + { + EventHandler 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)); + } + + /// + /// Gets an observable for a . + /// + /// The object. + /// The property type. + /// The property. + /// An observable. + public static IObservable GetObservable(this IPerspexObject o, PerspexProperty property) + { + Contract.Requires(o != null); + Contract.Requires(property != null); + + return o.GetObservable((PerspexProperty)property).Cast(); + } + + /// + /// Gets an observable for a . + /// + /// The object. + /// The type of the property. + /// The property. + /// + /// An observable which when subscribed pushes the old and new values of the property each + /// time it is changed. + /// + public static IObservable> GetObservableWithHistory( + this IPerspexObject o, + PerspexProperty property) + { + Contract.Requires(o != null); + Contract.Requires(property != null); + + return new PerspexObservable>( + observer => + { + EventHandler 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)); + } + + /// + /// Binds a property to a subject according to a . + /// + /// The object. + /// The property to bind. + /// The binding source. + /// The binding mode. + /// The binding priority. + /// + public static IDisposable Bind( + this IPerspexObject o, + PerspexProperty property, + ISubject source, + BindingMode mode, + BindingPriority priority = BindingPriority.LocalValue) + { + Contract.Requires(o != null); + Contract.Requires(property != null); + Contract.Requires(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."); + } + } + /// /// Subscribes to a property changed notifications for changes that originate from a /// . @@ -52,6 +179,17 @@ namespace Perspex return observable.Subscribe(e => SubscribeAdapter(e, handler)); } + /// + /// Gets a description of a property that van be used in observables. + /// + /// The object. + /// The property + /// The description. + private static string GetDescription(IPerspexObject o, PerspexProperty property) + { + return $"{o.GetType().Name}.{property.Name}"; + } + /// /// Observer method for . diff --git a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs index 1ea8b3ef41..d8cc99105f 100644 --- a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/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); } /// @@ -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); } diff --git a/src/Perspex.Controls/Presenters/TextPresenter.cs b/src/Perspex.Controls/Presenters/TextPresenter.cs index 8c25fc5a4f..c923340bd3 100644 --- a/src/Perspex.Controls/Presenters/TextPresenter.cs +++ b/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); } diff --git a/src/Perspex.Controls/Primitives/AccessText.cs b/src/Perspex.Controls/Primitives/AccessText.cs index 9ad2640534..38dab95803 100644 --- a/src/Perspex.Controls/Primitives/AccessText.cs +++ b/src/Perspex.Controls/Primitives/AccessText.cs @@ -37,7 +37,7 @@ namespace Perspex.Controls.Primitives /// public AccessText() { - GetObservable(TextProperty).Subscribe(TextChanged); + this.GetObservable(TextProperty).Subscribe(TextChanged); } /// diff --git a/src/Perspex.Controls/Primitives/ScrollBar.cs b/src/Perspex.Controls/Primitives/ScrollBar.cs index 06961d569f..fc40ad3d18 100644 --- a/src/Perspex.Controls/Primitives/ScrollBar.cs +++ b/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); } diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index b6ce37fa88..8ec6cece63 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/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); diff --git a/src/Perspex.Controls/Primitives/Track.cs b/src/Perspex.Controls/Primitives/Track.cs index 4e343b65b3..6fe4f3811b 100644 --- a/src/Perspex.Controls/Primitives/Track.cs +++ b/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) { diff --git a/src/Perspex.Controls/RadioButton.cs b/src/Perspex.Controls/RadioButton.cs index 9f74de4940..2c254d67ba 100644 --- a/src/Perspex.Controls/RadioButton.cs +++ b/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() diff --git a/src/Perspex.Controls/ScrollViewer.cs b/src/Perspex.Controls/ScrollViewer.cs index 6391918325..bdd770617b 100644 --- a/src/Perspex.Controls/ScrollViewer.cs +++ b/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); } diff --git a/src/Perspex.Controls/TextBlock.cs b/src/Perspex.Controls/TextBlock.cs index 65ee4b5f70..1c5bde93ea 100644 --- a/src/Perspex.Controls/TextBlock.cs +++ b/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(); diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index 0bfc8d2eeb..3df18911fc 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/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( diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index 4dceab17be..3b4c8f2d66 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/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()) .Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor)); diff --git a/src/Perspex.Diagnostics/DevTools.cs b/src/Perspex.Diagnostics/DevTools.cs index f5c7e3fb1f..a9364d2566 100644 --- a/src/Perspex.Diagnostics/DevTools.cs +++ b/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(); } diff --git a/src/Perspex.Diagnostics/Views/ControlDetailsView.cs b/src/Perspex.Diagnostics/Views/ControlDetailsView.cs index 199ece2122..fdbe7fd54f 100644 --- a/src/Perspex.Diagnostics/Views/ControlDetailsView.cs +++ b/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); } diff --git a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs b/src/Perspex.Diagnostics/Views/LogicalTreeView.cs index 21038ad50f..c29c1ddff1 100644 --- a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs +++ b/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); } diff --git a/src/Perspex.Diagnostics/Views/VisualTreeView.cs b/src/Perspex.Diagnostics/Views/VisualTreeView.cs index 4b187d00a0..d8d6ca03ce 100644 --- a/src/Perspex.Diagnostics/Views/VisualTreeView.cs +++ b/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); } diff --git a/src/Perspex.SceneGraph/Animation/CrossFade.cs b/src/Perspex.SceneGraph/Animation/CrossFade.cs index 4f3486c06a..d035a9c83d 100644 --- a/src/Perspex.SceneGraph/Animation/CrossFade.cs +++ b/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, diff --git a/src/Perspex.SceneGraph/Media/MatrixTransform.cs b/src/Perspex.SceneGraph/Media/MatrixTransform.cs index 96477cec14..ea6e5c668a 100644 --- a/src/Perspex.SceneGraph/Media/MatrixTransform.cs +++ b/src/Perspex.SceneGraph/Media/MatrixTransform.cs @@ -21,7 +21,7 @@ namespace Perspex.Media /// public MatrixTransform() { - GetObservable(MatrixProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(MatrixProperty).Subscribe(_ => RaiseChanged()); } /// diff --git a/src/Perspex.SceneGraph/Media/RotateTransform.cs b/src/Perspex.SceneGraph/Media/RotateTransform.cs index d314ca28c1..b2beb9f284 100644 --- a/src/Perspex.SceneGraph/Media/RotateTransform.cs +++ b/src/Perspex.SceneGraph/Media/RotateTransform.cs @@ -21,7 +21,7 @@ namespace Perspex.Media /// public RotateTransform() { - GetObservable(AngleProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(AngleProperty).Subscribe(_ => RaiseChanged()); } /// diff --git a/src/Perspex.SceneGraph/Media/TranslateTransform.cs b/src/Perspex.SceneGraph/Media/TranslateTransform.cs index 03d60ab25b..72f1e6010f 100644 --- a/src/Perspex.SceneGraph/Media/TranslateTransform.cs +++ b/src/Perspex.SceneGraph/Media/TranslateTransform.cs @@ -27,8 +27,8 @@ namespace Perspex.Media /// public TranslateTransform() { - GetObservable(XProperty).Subscribe(_ => RaiseChanged()); - GetObservable(YProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(XProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(YProperty).Subscribe(_ => RaiseChanged()); } /// diff --git a/src/Perspex.Styling/Styling/IStyleable.cs b/src/Perspex.Styling/Styling/IStyleable.cs index e5a3f9db81..36b092ff1d 100644 --- a/src/Perspex.Styling/Styling/IStyleable.cs +++ b/src/Perspex.Styling/Styling/IStyleable.cs @@ -10,7 +10,7 @@ namespace Perspex.Styling /// /// Interface for styleable elements. /// - public interface IStyleable : IObservablePropertyBag, INamed + public interface IStyleable : IPerspexObject, INamed { /// /// Raised when the control's style should be removed. diff --git a/src/Perspex.Styling/Styling/ITemplatedControl.cs b/src/Perspex.Styling/Styling/ITemplatedControl.cs index a31d834e5a..27e328613b 100644 --- a/src/Perspex.Styling/Styling/ITemplatedControl.cs +++ b/src/Perspex.Styling/Styling/ITemplatedControl.cs @@ -5,14 +5,7 @@ using System; namespace Perspex.Styling { - public interface ITemplatedControl + public interface ITemplatedControl : IPerspexObject { - /// - /// Gets an observable for a . - /// - /// - /// The property to get the observable for. - /// The observable. - IObservable GetObservable(PerspexProperty property); } } diff --git a/src/Perspex.Styling/Styling/Selectors.cs b/src/Perspex.Styling/Styling/Selectors.cs index 7ed066f293..3c88cac0a6 100644 --- a/src/Perspex.Styling/Styling/Selectors.cs +++ b/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; } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs index 95675fef4b..2cd71d4ac9 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/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>(), - 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>(), - 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(null); - var expression = new BehaviorSubject(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>(); - var expression = new Mock>(); - 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>(), - 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(((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(); 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(); 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 CreateTarget(object dataContext) - { - return CreateTarget(dataContext: Observable.Never().StartWith(dataContext)); - } - - private Mock CreateTarget( - IObservable dataContext = null, - IObservable text = null) + public class Source : ReactiveObject { - var result = new Mock(); - - dataContext = dataContext ?? Observable.Never().StartWith((object)null); - text = text ?? Observable.Never().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)); } } } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs index af0b492e49..75804b0748 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs +++ b/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>(), BindingPriority.TemplatedParent)); } - [Fact] - public void OneWayToSource_Binding_Should_Be_Set_Up() - { - var textObservable = new Mock>(); - var expression = new Mock>(); - 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 CreateTarget(ITemplatedControl templatedParent) + private Mock CreateTarget(ITemplatedControl templatedParent) { - return CreateTarget(templatedParent: Observable.Never().StartWith(templatedParent)); + return CreateTarget(templatedParent: templatedParent); } - private Mock CreateTarget( - IObservable templatedParent = null, - IObservable text = null) + private Mock CreateTarget( + ITemplatedControl templatedParent = null, + string text = null) { - var result = new Mock(); - - templatedParent = templatedParent ?? Observable.Never().StartWith((ITemplatedControl)null); - text = text ?? Observable.Never().StartWith((string)null); + var result = new Mock(); - 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; } } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs index 23db6d4c39..9583c85525 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs +++ b/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(); + var target = new Mock(); target.Setup(x => x.GetValue(Control.DataContextProperty)).Returns(source); - target.Setup(x => x.GetObservable(Control.DataContextProperty)).Returns( - Observable.Never().StartWith(source)); var subject = binding.CreateSubject(target.Object, typeof(string)); var result = await subject.Take(1); diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index 8a579223b3..f9e0f1090e 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs +++ b/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 PropertyChanged; + public Classes Classes { get; } public string Name { get; set; } @@ -96,42 +95,29 @@ namespace Perspex.Styling.UnitTests IObservable IStyleable.StyleDetach { get; } - public IPropertyBag InheritanceParent - { - get - { - throw new NotImplementedException(); - } - } - IPerspexReadOnlyList IStyleable.Classes => Classes; - public IDisposable Bind(PerspexProperty property, IObservable 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 GetObservable(PerspexProperty property) + public T GetValue(PerspexProperty 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(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue) { throw new NotImplementedException(); } - public object GetValue(PerspexProperty property) + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) { throw new NotImplementedException(); } @@ -145,31 +131,6 @@ namespace Perspex.Styling.UnitTests { throw new NotImplementedException(); } - - public IObservable GetObservable(PerspexProperty property) - { - throw new NotImplementedException(); - } - - public T GetValue(PerspexProperty property) - { - throw new NotImplementedException(); - } - - public void SetValue(PerspexProperty 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 source, BindingPriority priority = BindingPriority.LocalValue) - { - throw new NotImplementedException(); - } } public class TestLogical1 : TestLogical diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index f7ce2beaa9..e5477ebee3 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/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 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 IStyleable.Classes => Classes; IObservable IStyleable.StyleDetach { get; } - public IDisposable Bind(PerspexProperty property, IObservable 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 GetObservable(PerspexProperty property) + public T GetValue(PerspexProperty 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(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue) { throw new NotImplementedException(); } - public object GetValue(PerspexProperty property) + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) { throw new NotImplementedException(); } @@ -175,31 +162,6 @@ namespace Perspex.Styling.UnitTests { throw new NotImplementedException(); } - - public IObservable GetObservable(PerspexProperty property) - { - throw new NotImplementedException(); - } - - public T GetValue(PerspexProperty property) - { - throw new NotImplementedException(); - } - - public void SetValue(PerspexProperty 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 source, BindingPriority priority = BindingPriority.LocalValue) - { - throw new NotImplementedException(); - } } public class TestLogical1 : TestLogical diff --git a/tests/Perspex.Styling.UnitTests/TestControlBase.cs b/tests/Perspex.Styling.UnitTests/TestControlBase.cs index 82ab942a09..6743d2e468 100644 --- a/tests/Perspex.Styling.UnitTests/TestControlBase.cs +++ b/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 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 IStyleable.Classes => Classes; IObservable IStyleable.StyleDetach { get; } - public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) - { - throw new NotImplementedException(); - } - - public void SetValue(PerspexProperty property, object value, BindingPriority priority) - { - throw new NotImplementedException(); - } - - public IObservable 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(PerspexProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) + public T GetValue(PerspexProperty property) { throw new NotImplementedException(); } - public IObservable GetObservable(PerspexProperty property) + public void SetValue(PerspexProperty property, object value, BindingPriority priority) { throw new NotImplementedException(); } - public T GetValue(PerspexProperty property) + public void SetValue(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue) { throw new NotImplementedException(); } - public void SetValue(PerspexProperty 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 source, BindingPriority priority) { throw new NotImplementedException(); } - public IDisposable BindTwoWay(PerspexProperty property, ISubject source, BindingPriority priority = BindingPriority.LocalValue) + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) { throw new NotImplementedException(); } diff --git a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs index 1348b1d662..f84a7d7ad6 100644 --- a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs +++ b/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 PropertyChanged; + public abstract Classes Classes { get; @@ -32,29 +32,16 @@ namespace Perspex.Styling.UnitTests get; } - public abstract IEnumerable VisualChildren - { - get; - } - - public IPropertyBag InheritanceParent - { - get - { - throw new NotImplementedException(); - } - } - IPerspexReadOnlyList IStyleable.Classes => Classes; IObservable IStyleable.StyleDetach { get; } - public IObservable GetObservable(PerspexProperty property) + public object GetValue(PerspexProperty property) { throw new NotImplementedException(); } - public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) + public T GetValue(PerspexProperty property) { throw new NotImplementedException(); } @@ -64,27 +51,12 @@ namespace Perspex.Styling.UnitTests throw new NotImplementedException(); } - public IObservable 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(PerspexProperty property, T value, BindingPriority priority = BindingPriority.LocalValue) { throw new NotImplementedException(); } - public bool IsSet(PerspexProperty property) + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) { throw new NotImplementedException(); } @@ -94,22 +66,7 @@ namespace Perspex.Styling.UnitTests throw new NotImplementedException(); } - public T GetValue(PerspexProperty property) - { - throw new NotImplementedException(); - } - - public void SetValue(PerspexProperty 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 source, BindingPriority priority = BindingPriority.LocalValue) + public bool IsSet(PerspexProperty property) { throw new NotImplementedException(); } From 36543720edd2afe8b867ae623d1f623034b57a17 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 17 Jan 2016 23:31:06 +0100 Subject: [PATCH 02/10] Added IPerspexObject.GetSubject extension method. --- src/Perspex.Base/PerspexObjectExtensions.cs | 81 +++++++++++++++++++ .../PerspexObjectTests_GetSubject.cs | 49 +++++++++++ 2 files changed, 130 insertions(+) create mode 100644 tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs diff --git a/src/Perspex.Base/PerspexObjectExtensions.cs b/src/Perspex.Base/PerspexObjectExtensions.cs index 52c8042ac8..e91ea6de0a 100644 --- a/src/Perspex.Base/PerspexObjectExtensions.cs +++ b/src/Perspex.Base/PerspexObjectExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -101,6 +102,37 @@ namespace Perspex GetDescription(o, property)); } + /// + /// Gets a subject for a . + /// + /// The property type. + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject GetSubject( + this IPerspexObject o, + PerspexProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + // TODO: Subject.Create is not yet in stable Rx : once it is, remove the + // AnonymousSubject classes from this file and use Subject.Create. + var output = new Subject(); + var result = new AnonymousSubject( + Observer.Create( + x => output.OnNext(x), + e => output.OnError(e), + () => output.OnCompleted()), + o.GetObservable(property)); + o.Bind(property, output, priority); + return result; + } + /// /// Binds a property to a subject according to a . /// @@ -209,5 +241,54 @@ namespace Perspex handler(target)(e); } } + + class AnonymousSubject : ISubject + { + private readonly IObserver _observer; + private readonly IObservable _observable; + + public AnonymousSubject(IObserver observer, IObservable observable) + { + _observer = observer; + _observable = observable; + } + + public void OnCompleted() + { + _observer.OnCompleted(); + } + + public void OnError(Exception error) + { + if (error == null) + throw new ArgumentNullException("error"); + + _observer.OnError(error); + } + + public void OnNext(T value) + { + _observer.OnNext(value); + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + throw new ArgumentNullException("observer"); + + // + // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence. + // + return _observable.Subscribe/*Unsafe*/(observer); + } + } + + class AnonymousSubject : AnonymousSubject, ISubject + { + public AnonymousSubject(IObserver observer, IObservable observable) + : base(observer, observable) + { + } + } } } diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs new file mode 100644 index 0000000000..2d2bf97ac6 --- /dev/null +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs @@ -0,0 +1,49 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using Xunit; + +namespace Perspex.Base.UnitTests +{ + public class PerspexObjectTests_GetSubject + { + [Fact] + public void GetSubject_Returns_Values() + { + var source = new Class1 { Foo = "foo" }; + var target = source.GetSubject(Class1.FooProperty); + var result = new List(); + + target.Subscribe(x => result.Add(x)); + source.Foo = "bar"; + source.Foo = "baz"; + + Assert.Equal(new[] { "foo", "bar", "baz" }, result); + } + + [Fact] + public void GetSubject_Sets_Values() + { + var source = new Class1 { Foo = "foo" }; + var target = source.GetSubject(Class1.FooProperty); + + target.OnNext("bar"); + Assert.Equal("bar", source.Foo); + } + + private class Class1 : PerspexObject + { + public static readonly PerspexProperty FooProperty = + PerspexProperty.Register("Foo", "foodefault"); + + public string Foo + { + get { return GetValue(FooProperty); } + set { SetValue(FooProperty, value); } + } + } + } +} From 1841e7ad7a642e3d23cf2a0605b949181da31514 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 18 Jan 2016 15:46:41 +0100 Subject: [PATCH 03/10] Start allowing Setters to contain bindings. This is needed so that things like the following can be done: (At the moment we have TreeDataTemplate.IsExpanded but this isn't good enough: it only supports setting IsExpanded on creation of the item with no binding.) With this commit, setters can now contain bindings, but they aren't yet applied correctly. As part of this commit, classes related to binding have been moved to the Perspex.Data namespace. --- samples/TestApplicationShared/MainWindow.cs | 1 + .../Context/PerspexWiringContext.cs | 4 +- .../Context/PerspexXamlMemberValuePlugin.cs | 53 ++++++++------- .../Perspex.Markup.Xaml/Data/Binding.cs | 41 +++--------- .../Perspex.Markup.Xaml/Data/IXamlBinding.cs | 35 ---------- .../Perspex.Markup.Xaml/Data/MultiBinding.cs | 22 +++---- .../MarkupExtensions/BindingExtension.cs | 1 + .../TemplateBindingExtension.cs | 1 + .../Perspex.Markup.Xaml.csproj | 1 - .../Perspex.Markup.Xaml/PerspexXamlLoader.cs | 25 +++++++ .../Templates/TreeDataTemplate.cs | 2 + src/Perspex.Animation/Animatable.cs | 1 + src/Perspex.Animation/Animate.cs | 1 + .../Data/AssignBindingAttribute.cs | 20 ++++++ src/Perspex.Base/Data/BindingMode.cs | 36 ++++++++++ .../{ => Data}/BindingPriority.cs | 2 +- src/Perspex.Base/Data/IBinding.cs | 33 ++++++++++ .../IndexerDescriptor.cs} | 45 ++----------- .../Diagnostics/PerspexObjectExtensions.cs | 2 + .../Diagnostics/PerspexPropertyValue.cs | 2 + src/Perspex.Base/IPerspexObject.cs | 1 + src/Perspex.Base/Perspex.Base.csproj | 7 +- src/Perspex.Base/PerspexObject.cs | 11 ++-- src/Perspex.Base/PerspexObjectExtensions.cs | 3 +- src/Perspex.Base/PerspexProperty.cs | 19 +++--- .../PerspexPropertyChangedEventArgs.cs | 2 + src/Perspex.Base/PerspexProperty`1.cs | 1 + src/Perspex.Controls/Control.cs | 1 + src/Perspex.Controls/ControlExtensions.cs | 31 +++++++++ src/Perspex.Controls/Primitives/ScrollBar.cs | 1 + .../Primitives/SelectingItemsControl.cs | 1 + .../Primitives/TemplatedControl.cs | 3 +- src/Perspex.Controls/TextBlock.cs | 1 + src/Perspex.Controls/TextBox.cs | 1 + src/Perspex.Diagnostics/Debug.cs | 1 + .../ViewModels/PropertyDetails.cs | 1 + src/Perspex.SceneGraph/Visual.cs | 1 + src/Perspex.Styling/Perspex.Styling.csproj | 1 - .../Styling/ObservableSetter.cs | 65 ------------------- src/Perspex.Styling/Styling/Setter.cs | 2 + .../Perspex.Base.UnitTests.csproj | 1 + .../PerspexObjectTests_Binding.cs | 7 +- .../PerspexObjectTests_Direct.cs | 1 + .../PerspexObjectTests_SetValue.cs | 1 + .../PerspexPropertyTests.cs | 1 + .../SelectingItemsControlTests_Multiple.cs | 7 +- tests/Perspex.LeakTests/ControlTests.cs | 3 +- .../Data/BindingTests.cs | 26 ++++---- .../Data/BindingTests_ElementName.cs | 8 +-- .../Data/BindingTests_TemplatedParent.cs | 9 +-- .../Data/MultiBindingTests.cs | 2 +- .../Perspex.Markup.Xaml.UnitTests.csproj | 5 +- .../StyleTests.cs | 33 ++++++++++ .../Templates/TreeDataTemplateTests.cs | 34 ++++++++++ .../SelectorTests_Child.cs | 1 + .../SelectorTests_Descendent.cs | 1 + tests/Perspex.Styling.UnitTests/StyleTests.cs | 46 ------------- .../TestControlBase.cs | 1 + .../TestTemplatedControl.cs | 1 + 59 files changed, 362 insertions(+), 308 deletions(-) delete mode 100644 src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs create mode 100644 src/Perspex.Base/Data/AssignBindingAttribute.cs create mode 100644 src/Perspex.Base/Data/BindingMode.cs rename src/Perspex.Base/{ => Data}/BindingPriority.cs (98%) create mode 100644 src/Perspex.Base/Data/IBinding.cs rename src/Perspex.Base/{BindingDescriptor.cs => Data/IndexerDescriptor.cs} (70%) delete mode 100644 src/Perspex.Styling/Styling/ObservableSetter.cs create mode 100644 tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs create mode 100644 tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs diff --git a/samples/TestApplicationShared/MainWindow.cs b/samples/TestApplicationShared/MainWindow.cs index 4c27018c6f..4650df1667 100644 --- a/samples/TestApplicationShared/MainWindow.cs +++ b/samples/TestApplicationShared/MainWindow.cs @@ -11,6 +11,7 @@ using Perspex.Controls.Html; using Perspex.Controls.Primitives; using Perspex.Controls.Shapes; using Perspex.Controls.Templates; +using Perspex.Data; using Perspex.Diagnostics; using Perspex.Layout; using Perspex.Media; diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs index 040ef313ba..e42df57d59 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs @@ -15,6 +15,7 @@ using Perspex.Controls.Primitives; using Perspex.Controls.Templates; using Perspex.Input; using Perspex.Markup.Xaml.Converters; +using Perspex.Markup.Xaml.Data; using Perspex.Media; using Perspex.Media.Imaging; using Perspex.Metadata; @@ -58,9 +59,10 @@ namespace Perspex.Markup.Xaml.Context var forcedAssemblies = new[] { + typeof(Binding), typeof(Control), - typeof(Style), typeof(IValueConverter), + typeof(Style), }.Select(t => t.GetTypeInfo().Assembly); foreach (var nsa in diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index f6a33aa026..e9ce58c771 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs @@ -9,6 +9,8 @@ using System.Runtime.CompilerServices; using Glass; using OmniXaml.ObjectAssembler; using OmniXaml.Typing; +using Perspex.Controls; +using Perspex.Data; using Perspex.Markup.Xaml.Data; using Perspex.Styling; @@ -39,9 +41,9 @@ namespace Perspex.Markup.Xaml.Context public override void SetValue(object instance, object value) { - if (value is IXamlBinding) + if (value is IBinding) { - HandleBinding(instance, (IXamlBinding)value); + HandleBinding(instance, (IBinding)value); } else if (IsPerspexProperty) { @@ -68,35 +70,38 @@ namespace Perspex.Markup.Xaml.Context po.SetValue(pp, value); } - private void HandleBinding(object instance, IXamlBinding binding) + private void HandleBinding(object instance, IBinding binding) { - if (typeof(IXamlBinding).GetTypeInfo().IsAssignableFrom(_xamlMember.XamlType.UnderlyingType.GetTypeInfo())) + if (!(AssignBinding(instance, binding) || ApplyBinding(instance, binding))) { - var property = instance.GetType().GetRuntimeProperty(_xamlMember.Name); + throw new InvalidOperationException( + $"Cannot assign to '{_xamlMember.Name}' on '{instance.GetType()}"); + } + } - if (property == null || !property.CanWrite) - { - throw new InvalidOperationException( - $"Cannot assign to '{_xamlMember.Name}' on '{instance.GetType()}"); - } + private bool AssignBinding(object instance, IBinding binding) + { + var property = instance.GetType() + .GetRuntimeProperties() + .FirstOrDefault(x => x.Name == _xamlMember.Name); + if (property?.GetCustomAttribute() != null) + { property.SetValue(instance, binding); + return true; } - else - { - ApplyBinding(instance, binding); - } + + return false; } - private void ApplyBinding(object instance, IXamlBinding binding) + private bool ApplyBinding(object instance, IBinding binding) { - var perspexObject = instance as PerspexObject; + var targetControl = instance as IControl; var attached = _xamlMember as PerspexAttachableXamlMember; - if (perspexObject == null) + if (targetControl == null) { - throw new InvalidOperationException( - $"Cannot bind to an object of type '{instance.GetType()}"); + return false; } PerspexProperty property; @@ -105,7 +110,7 @@ namespace Perspex.Markup.Xaml.Context if (attached == null) { propertyName = _xamlMember.Name; - property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject) + property = PerspexPropertyRegistry.Instance.GetRegistered((PerspexObject)targetControl) .FirstOrDefault(x => x.Name == propertyName); } else @@ -115,18 +120,18 @@ namespace Perspex.Markup.Xaml.Context propertyName = attached.DeclaringType.UnderlyingType.Name + '.' + _xamlMember.Name; - property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject) + property = PerspexPropertyRegistry.Instance.GetRegistered((PerspexObject)targetControl) .Where(x => x.IsAttached && x.OwnerType == attached.DeclaringType.UnderlyingType) .FirstOrDefault(x => x.Name == _xamlMember.Name); } if (property == null) { - throw new InvalidOperationException( - $"Cannot find '{propertyName}' on '{instance.GetType()}"); + return false; } - binding.Bind(perspexObject, property); + targetControl.Bind(property, binding); + return true; } private bool ValueRequiresSpecialHandling(object value) diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs index f7546b3db8..55b80f5d40 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs @@ -6,6 +6,7 @@ using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; using Perspex.Controls; +using Perspex.Data; using Perspex.Markup.Data; namespace Perspex.Markup.Xaml.Data @@ -13,7 +14,7 @@ namespace Perspex.Markup.Xaml.Data /// /// A XAML binding. /// - public class Binding : IXamlBinding + public class Binding : IBinding { /// /// Gets or sets the to use. @@ -50,49 +51,23 @@ namespace Perspex.Markup.Xaml.Data /// public string Path { get; set; } - /// - /// Applies the binding to a property on an instance. - /// - /// The target instance. - /// The target property. - public void Bind(IPerspexObject instance, PerspexProperty property) - { - Contract.Requires(instance != null); - Contract.Requires(property != null); - - var subject = CreateSubject( - instance, - property.PropertyType, - property == Control.DataContextProperty); - - if (subject != null) - { - var mode = Mode == BindingMode.Default ? property.DefaultBindingMode : Mode; - instance.Bind(property, subject, mode, Priority); - } - } - /// /// Creates a subject that can be used to get and set the value of the binding. /// /// The target instance. - /// The type of the target property. - /// - /// Whether the target property is the DataContext property. - /// - /// An . + /// The target property. May be null. + /// An . public ISubject CreateSubject( - IPerspexObject target, - Type targetType, - bool targetIsDataContext = false) + IPerspexObject target, + PerspexProperty targetProperty) { Contract.Requires(target != null); - Contract.Requires(targetType != null); var pathInfo = ParsePath(Path); ValidateState(pathInfo); ExpressionObserver observer; + var targetIsDataContext = targetProperty == Control.DataContextProperty; if (pathInfo.ElementName != null || ElementName != null) { @@ -121,7 +96,7 @@ namespace Perspex.Markup.Xaml.Data return new ExpressionSubject( observer, - targetType, + targetProperty?.PropertyType ?? typeof(object), Converter ?? DefaultValueConverter.Instance, ConverterParameter); } diff --git a/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs deleted file mode 100644 index 144302a9a7..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs +++ /dev/null @@ -1,35 +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.Markup.Xaml.Data -{ - /// - /// Defines a binding that can be created in XAML markup. - /// - public interface IXamlBinding - { - /// - /// Applies the binding to a property on an instance. - /// - /// The target instance. - /// The target property. - void Bind(IPerspexObject instance, PerspexProperty property); - - /// - /// Creates a subject that can be used to get and set the value of the binding. - /// - /// The target instance. - /// The type of the target property. - /// - /// Whether the target property is the DataContext property. - /// - /// An . - ISubject CreateSubject( - IPerspexObject target, - Type targetType, - bool targetIsDataContext = false); - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs index 41f67a0467..ea3760cf83 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; using Perspex.Controls; +using Perspex.Data; using Perspex.Metadata; namespace Perspex.Markup.Xaml.Data @@ -15,13 +16,13 @@ namespace Perspex.Markup.Xaml.Data /// /// A XAML binding that calculates an aggregate value from multiple child . /// - public class MultiBinding : IXamlBinding + public class MultiBinding : IBinding { /// /// Gets the collection of child bindings. /// [Content] - public IList Bindings { get; set; } = new List(); + public IList Bindings { get; set; } = new List(); /// /// Gets or sets the to use. @@ -50,7 +51,7 @@ namespace Perspex.Markup.Xaml.Data /// The target property. public void Bind(IPerspexObject instance, PerspexProperty property) { - var subject = CreateSubject(instance, property.PropertyType); + var subject = CreateSubject(instance, property); if (subject != null) { @@ -62,23 +63,18 @@ namespace Perspex.Markup.Xaml.Data /// Creates a subject that can be used to get and set the value of the binding. /// /// The target instance. - /// The type of the target property. - /// - /// Whether the target property is the DataContext property. - /// - /// An . - public ISubject CreateSubject( - IPerspexObject target, - Type targetType, - bool targetIsDataContext = false) + /// The target property. + /// An . + public ISubject CreateSubject(IPerspexObject target, PerspexProperty targetProperty) { if (Converter == null) { throw new NotSupportedException("MultiBinding without Converter not currently supported."); } + var targetType = targetProperty?.PropertyType ?? typeof(object); var result = new BehaviorSubject(PerspexProperty.UnsetValue); - var children = Bindings.Select(x => x.CreateSubject(target, typeof(object))); + var children = Bindings.Select(x => x.CreateSubject(target, null)); var input = children.CombineLatest().Select(x => Converter.Convert(x, targetType, null, CultureInfo.CurrentUICulture)); input.Subscribe(result); diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 0bf319c913..e43ba9cb03 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using OmniXaml; +using Perspex.Data; using Perspex.Markup.Xaml.Data; namespace Perspex.Markup.Xaml.MarkupExtensions diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs index b3bd1c7c75..361b04c915 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using OmniXaml; +using Perspex.Data; using Perspex.Markup.Xaml.Data; namespace Perspex.Markup.Xaml.MarkupExtensions diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 69b6143bbc..d9cd4c6f63 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -42,7 +42,6 @@ - diff --git a/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs b/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs index c5282fc6ac..16eb793159 100644 --- a/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs +++ b/src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Text; using OmniXaml; using Perspex.Markup.Xaml.Context; using Perspex.Platform; @@ -39,6 +40,8 @@ namespace Perspex.Markup.Xaml /// The object to load the XAML into. public static void Load(object obj) { + Contract.Requires(obj != null); + var loader = new PerspexXamlLoader(); loader.Load(obj.GetType(), obj); } @@ -53,6 +56,8 @@ namespace Perspex.Markup.Xaml /// The loaded object. public object Load(Type type, object rootInstance = null) { + Contract.Requires(type != null); + // HACK: Currently Visual Studio is forcing us to change the extension of xaml files // in certain situations, so we try to load .xaml and if that's not found we try .paml. // Ideally we'd be able to use .xaml everywhere @@ -85,6 +90,8 @@ namespace Perspex.Markup.Xaml /// The loaded object. public object Load(Uri uri, object rootInstance = null) { + Contract.Requires(uri != null); + var assetLocator = PerspexLocator.Current.GetService(); if (assetLocator == null) @@ -99,6 +106,24 @@ namespace Perspex.Markup.Xaml } } + /// + /// Loads XAML from a string. + /// + /// The string containing the XAML. + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(string xaml, object rootInstance = null) + { + Contract.Requires(xaml != null); + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) + { + return Load(stream, rootInstance); + } + } + /// /// Gets the URI for a type. /// diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs index dc9143be1b..0ff1ec7a6d 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Reactive.Linq; using Perspex.Controls; using Perspex.Controls.Templates; +using Perspex.Data; using Perspex.Markup.Data; using Perspex.Markup.Xaml.Data; using Perspex.Metadata; @@ -19,6 +20,7 @@ namespace Perspex.Markup.Xaml.Templates [Content] public TemplateContent Content { get; set; } + [AssignBinding] public Binding ItemsSource { get; set; } public bool Match(object data) diff --git a/src/Perspex.Animation/Animatable.cs b/src/Perspex.Animation/Animatable.cs index c00755f4bc..8e77b28dfe 100644 --- a/src/Perspex.Animation/Animatable.cs +++ b/src/Perspex.Animation/Animatable.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Linq; +using Perspex.Data; namespace Perspex.Animation { diff --git a/src/Perspex.Animation/Animate.cs b/src/Perspex.Animation/Animate.cs index d076c23953..1f85ffaadf 100644 --- a/src/Perspex.Animation/Animate.cs +++ b/src/Perspex.Animation/Animate.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Linq; using System.Reactive.Linq; +using Perspex.Data; using Perspex.Threading; namespace Perspex.Animation diff --git a/src/Perspex.Base/Data/AssignBindingAttribute.cs b/src/Perspex.Base/Data/AssignBindingAttribute.cs new file mode 100644 index 0000000000..d44b66b58e --- /dev/null +++ b/src/Perspex.Base/Data/AssignBindingAttribute.cs @@ -0,0 +1,20 @@ +// 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.Data +{ + /// + /// Signifies that a binding can be assigned to a property. + /// + /// + /// Usually in markup, when a binding is set for a property that property will be bound. + /// Applying this attribute to a property indicates that the binding should be assigned to + /// the property rather than bound. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class AssignBindingAttribute : Attribute + { + } +} diff --git a/src/Perspex.Base/Data/BindingMode.cs b/src/Perspex.Base/Data/BindingMode.cs new file mode 100644 index 0000000000..885f58ed1a --- /dev/null +++ b/src/Perspex.Base/Data/BindingMode.cs @@ -0,0 +1,36 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Perspex.Data +{ + /// + /// Defines possible binding modes. + /// + public enum BindingMode + { + /// + /// Uses the default binding mode specified for the property. + /// + Default, + + /// + /// Binds one way from source to target. + /// + OneWay, + + /// + /// Binds two-way with the initial value coming from the target. + /// + TwoWay, + + /// + /// Updates the target when the application starts or when the data context changes. + /// + OneTime, + + /// + /// Binds one way from target to source. + /// + OneWayToSource, + } +} diff --git a/src/Perspex.Base/BindingPriority.cs b/src/Perspex.Base/Data/BindingPriority.cs similarity index 98% rename from src/Perspex.Base/BindingPriority.cs rename to src/Perspex.Base/Data/BindingPriority.cs index e26898d202..055b252fa3 100644 --- a/src/Perspex.Base/BindingPriority.cs +++ b/src/Perspex.Base/Data/BindingPriority.cs @@ -1,7 +1,7 @@ // Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -namespace Perspex +namespace Perspex.Data { /// /// The priority of a binding. diff --git a/src/Perspex.Base/Data/IBinding.cs b/src/Perspex.Base/Data/IBinding.cs new file mode 100644 index 0000000000..025ef7dfb7 --- /dev/null +++ b/src/Perspex.Base/Data/IBinding.cs @@ -0,0 +1,33 @@ +// 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.Reactive.Subjects; + +namespace Perspex.Data +{ + /// + /// Holds a binding that can be applied to a property on an object. + /// + public interface IBinding + { + /// + /// Gets the binding mode. + /// + BindingMode Mode { get; } + + /// + /// Gets the binding priority. + /// + BindingPriority Priority { get; } + + /// + /// Creates a subject that can be used to get and set the value of the binding. + /// + /// The target instance. + /// The target property. May be null. + /// An . + ISubject CreateSubject( + IPerspexObject target, + PerspexProperty targetProperty); + } +} diff --git a/src/Perspex.Base/BindingDescriptor.cs b/src/Perspex.Base/Data/IndexerDescriptor.cs similarity index 70% rename from src/Perspex.Base/BindingDescriptor.cs rename to src/Perspex.Base/Data/IndexerDescriptor.cs index c0c8f3406f..94563a5499 100644 --- a/src/Perspex.Base/BindingDescriptor.cs +++ b/src/Perspex.Base/Data/IndexerDescriptor.cs @@ -4,43 +4,12 @@ using System; using System.Reactive; -namespace Perspex +namespace Perspex.Data { /// - /// Defines possible binding modes. + /// Holds a description of a binding for 's [] operator. /// - public enum BindingMode - { - /// - /// Uses the default binding mode specified for the property. - /// - Default, - - /// - /// Binds one way from source to target. - /// - OneWay, - - /// - /// Binds two-way with the initial value coming from the target. - /// - TwoWay, - - /// - /// Updates the target when the application starts or when the data context changes. - /// - OneTime, - - /// - /// Binds one way from target to source. - /// - OneWayToSource, - } - - /// - /// Holds a description of a binding, usually for 's [] operator. - /// - public class BindingDescriptor : ObservableBase, IDescription + public class IndexerDescriptor : ObservableBase, IDescription { /// /// Gets or sets the binding mode. @@ -100,7 +69,7 @@ namespace Perspex /// /// The current binding. /// A two-way binding. - public static BindingDescriptor operator !(BindingDescriptor binding) + public static IndexerDescriptor operator !(IndexerDescriptor binding) { return binding.WithMode(BindingMode.TwoWay); } @@ -110,7 +79,7 @@ namespace Perspex /// /// The current binding. /// A two-way binding. - public static BindingDescriptor operator ~(BindingDescriptor binding) + public static IndexerDescriptor operator ~(IndexerDescriptor binding) { return binding.WithMode(BindingMode.TwoWay); } @@ -120,7 +89,7 @@ namespace Perspex /// /// The binding mode. /// The object that the method was called on. - public BindingDescriptor WithMode(BindingMode mode) + public IndexerDescriptor WithMode(BindingMode mode) { Mode = mode; return this; @@ -131,7 +100,7 @@ namespace Perspex /// /// The binding priority. /// The object that the method was called on. - public BindingDescriptor WithPriority(BindingPriority priority) + public IndexerDescriptor WithPriority(BindingPriority priority) { Priority = priority; return this; diff --git a/src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs b/src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs index cd7b95cf06..3efc5fbe98 100644 --- a/src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs +++ b/src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs @@ -1,6 +1,8 @@ // 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.Data; + namespace Perspex.Diagnostics { /// diff --git a/src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs b/src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs index 99f5111b44..5b1d46bcf1 100644 --- a/src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs +++ b/src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs @@ -1,6 +1,8 @@ // 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.Data; + namespace Perspex.Diagnostics { /// diff --git a/src/Perspex.Base/IPerspexObject.cs b/src/Perspex.Base/IPerspexObject.cs index 47ccd7b9ff..4290bfe792 100644 --- a/src/Perspex.Base/IPerspexObject.cs +++ b/src/Perspex.Base/IPerspexObject.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Perspex.Data; namespace Perspex { diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index b08bce7e2f..1ece57e81d 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -42,8 +42,11 @@ Properties\SharedAssemblyInfo.cs - + + + + @@ -55,7 +58,7 @@ - + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index eeb67a2482..5c4271a57d 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -8,8 +8,7 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; -using System.Reflection; -using Perspex.Reactive; +using Perspex.Data; using Perspex.Threading; using Perspex.Utilities; using Serilog; @@ -154,7 +153,7 @@ namespace Perspex /// Gets or sets a binding for a . /// /// The binding information. - public IObservable this[BindingDescriptor binding] + public IObservable this[IndexerDescriptor binding] { get { @@ -166,7 +165,7 @@ namespace Perspex var mode = (binding.Mode == BindingMode.Default) ? binding.Property.DefaultBindingMode : binding.Mode; - var sourceBinding = value as BindingDescriptor; + var sourceBinding = value as IndexerDescriptor; if (sourceBinding == null && mode > BindingMode.OneWay) { @@ -192,9 +191,9 @@ namespace Perspex } } - protected virtual BindingDescriptor CreateBindingDescriptor(BindingDescriptor source) + protected virtual IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source) { - return new BindingDescriptor + return new IndexerDescriptor { Mode = source.Mode, Priority = source.Priority, diff --git a/src/Perspex.Base/PerspexObjectExtensions.cs b/src/Perspex.Base/PerspexObjectExtensions.cs index e91ea6de0a..d7b2d5a748 100644 --- a/src/Perspex.Base/PerspexObjectExtensions.cs +++ b/src/Perspex.Base/PerspexObjectExtensions.cs @@ -6,6 +6,7 @@ using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; +using Perspex.Data; using Perspex.Reactive; namespace Perspex @@ -141,7 +142,7 @@ namespace Perspex /// The binding source. /// The binding mode. /// The binding priority. - /// + /// An which can be used to cancel the binding. public static IDisposable Bind( this IPerspexObject o, PerspexProperty property, diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs index 0a00b21219..27c80ca323 100644 --- a/src/Perspex.Base/PerspexProperty.cs +++ b/src/Perspex.Base/PerspexProperty.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Subjects; using System.Reflection; +using Perspex.Data; using Perspex.Utilities; namespace Perspex @@ -323,10 +324,10 @@ namespace Perspex /// indexer. /// /// The property. - /// A describing the binding. - public static BindingDescriptor operator !(PerspexProperty property) + /// A describing the binding. + public static IndexerDescriptor operator !(PerspexProperty property) { - return new BindingDescriptor + return new IndexerDescriptor { Priority = BindingPriority.LocalValue, Property = property, @@ -338,10 +339,10 @@ namespace Perspex /// indexer. /// /// The property. - /// A describing the binding. - public static BindingDescriptor operator ~(PerspexProperty property) + /// A describing the binding. + public static IndexerDescriptor operator ~(PerspexProperty property) { - return new BindingDescriptor + return new IndexerDescriptor { Priority = BindingPriority.TemplatedParent, Property = property, @@ -557,13 +558,13 @@ namespace Perspex /// Returns a binding accessor that can be passed to 's [] /// operator to initiate a binding. /// - /// A . + /// A . /// /// The ! and ~ operators are short forms of this. /// - public BindingDescriptor Bind() + public IndexerDescriptor Bind() { - return new BindingDescriptor + return new IndexerDescriptor { Property = this, }; diff --git a/src/Perspex.Base/PerspexPropertyChangedEventArgs.cs b/src/Perspex.Base/PerspexPropertyChangedEventArgs.cs index e16e2242fa..1136b5ce2a 100644 --- a/src/Perspex.Base/PerspexPropertyChangedEventArgs.cs +++ b/src/Perspex.Base/PerspexPropertyChangedEventArgs.cs @@ -1,6 +1,8 @@ // 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.Data; + namespace Perspex { /// diff --git a/src/Perspex.Base/PerspexProperty`1.cs b/src/Perspex.Base/PerspexProperty`1.cs index 6d97833f0a..dae476da31 100644 --- a/src/Perspex.Base/PerspexProperty`1.cs +++ b/src/Perspex.Base/PerspexProperty`1.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Perspex.Data; namespace Perspex { diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index bda72a8bfa..43eb87093c 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -11,6 +11,7 @@ using System.Reactive.Subjects; using Perspex.Collections; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; +using Perspex.Data; using Perspex.Input; using Perspex.Interactivity; using Perspex.LogicalTree; diff --git a/src/Perspex.Controls/ControlExtensions.cs b/src/Perspex.Controls/ControlExtensions.cs index 5948331652..db11bff51e 100644 --- a/src/Perspex.Controls/ControlExtensions.cs +++ b/src/Perspex.Controls/ControlExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Perspex.Data; using Perspex.LogicalTree; using Perspex.Styling; @@ -13,6 +14,36 @@ namespace Perspex.Controls /// public static class ControlExtensions { + /// + /// Binds a property on an to an . + /// + /// The object. + /// The property to bind. + /// The binding. + /// An which can be used to cancel the binding. + public static IDisposable Bind( + this IControl o, + PerspexProperty property, + IBinding binding) + { + Contract.Requires(o != null); + Contract.Requires(property != null); + Contract.Requires(binding != null); + + var mode = binding.Mode; + + if (mode == BindingMode.Default) + { + mode = property.DefaultBindingMode; + } + + return o.Bind( + property, + binding.CreateSubject(o, property), + mode, + binding.Priority); + } + /// /// Tries to being the control into view. /// diff --git a/src/Perspex.Controls/Primitives/ScrollBar.cs b/src/Perspex.Controls/Primitives/ScrollBar.cs index fc40ad3d18..6cc9903531 100644 --- a/src/Perspex.Controls/Primitives/ScrollBar.cs +++ b/src/Perspex.Controls/Primitives/ScrollBar.cs @@ -4,6 +4,7 @@ using System; using System.Reactive; using System.Reactive.Linq; +using Perspex.Data; namespace Perspex.Controls.Primitives { diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs index 53682a1ddf..56de4789e5 100644 --- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -8,6 +8,7 @@ using System.Collections.Specialized; using System.Linq; using Perspex.Collections; using Perspex.Controls.Generators; +using Perspex.Data; using Perspex.Input; using Perspex.Interactivity; using Perspex.Styling; diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index 8ec6cece63..7e26758054 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reactive.Linq; using Perspex.Controls.Presenters; using Perspex.Controls.Templates; +using Perspex.Data; using Perspex.Interactivity; using Perspex.Media; using Perspex.Styling; @@ -224,7 +225,7 @@ namespace Perspex.Controls.Primitives } } - protected sealed override BindingDescriptor CreateBindingDescriptor(BindingDescriptor source) + protected sealed override IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source) { var result = base.CreateBindingDescriptor(source); diff --git a/src/Perspex.Controls/TextBlock.cs b/src/Perspex.Controls/TextBlock.cs index 1c5bde93ea..96c47067a3 100644 --- a/src/Perspex.Controls/TextBlock.cs +++ b/src/Perspex.Controls/TextBlock.cs @@ -4,6 +4,7 @@ using System; using System.Reactive; using System.Reactive.Linq; +using Perspex.Data; using Perspex.Media; using Perspex.Metadata; diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index 3df18911fc..60d72113df 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/src/Perspex.Controls/TextBox.cs @@ -14,6 +14,7 @@ using Perspex.Input; using Perspex.Interactivity; using Perspex.Media; using Perspex.Metadata; +using Perspex.Data; namespace Perspex.Controls { diff --git a/src/Perspex.Diagnostics/Debug.cs b/src/Perspex.Diagnostics/Debug.cs index 1e07363a71..2977992f30 100644 --- a/src/Perspex.Diagnostics/Debug.cs +++ b/src/Perspex.Diagnostics/Debug.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Perspex.Controls; +using Perspex.Data; namespace Perspex.Diagnostics { diff --git a/src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs b/src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs index 171a8df25e..a5d1ef0fcf 100644 --- a/src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs +++ b/src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Perspex.Data; using ReactiveUI; namespace Perspex.Diagnostics.ViewModels diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index 4c1c41330f..ed9b661a7f 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reactive.Linq; using Perspex.Animation; using Perspex.Collections; +using Perspex.Data; using Perspex.Media; using Perspex.Platform; using Perspex.Rendering; diff --git a/src/Perspex.Styling/Perspex.Styling.csproj b/src/Perspex.Styling/Perspex.Styling.csproj index 2ad2057aa5..5f79ca5820 100644 --- a/src/Perspex.Styling/Perspex.Styling.csproj +++ b/src/Perspex.Styling/Perspex.Styling.csproj @@ -56,7 +56,6 @@ - diff --git a/src/Perspex.Styling/Styling/ObservableSetter.cs b/src/Perspex.Styling/Styling/ObservableSetter.cs deleted file mode 100644 index 74e9fb07c9..0000000000 --- a/src/Perspex.Styling/Styling/ObservableSetter.cs +++ /dev/null @@ -1,65 +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; - -namespace Perspex.Styling -{ - /// - /// A setter for a whose source is an observable. - /// - /// - /// A is used to set a value on a - /// depending on a condition. - /// - public class ObservableSetter : ISetter - { - /// - /// Initializes a new instance of the class. - /// - /// The property to set. - /// An observable which produces the value for the property. - public ObservableSetter(PerspexProperty property, IObservable source) - { - Property = property; - Source = source; - } - - /// - /// Gets or sets the property to set. - /// - public PerspexProperty Property - { - get; - set; - } - - /// - /// Gets or sets an observable which produces the value for the property. - /// - public IObservable Source - { - get; - set; - } - - /// - /// Applies the setter to the control. - /// - /// The style that is being applied. - /// The control. - /// An optional activator. - public void Apply(IStyle style, IStyleable control, IObservable activator) - { - if (activator == null) - { - control.Bind(Property, Source, BindingPriority.Style); - } - else - { - var binding = new StyleBinding(activator, Source, style.ToString()); - control.Bind(Property, binding, BindingPriority.StyleTrigger); - } - } - } -} diff --git a/src/Perspex.Styling/Styling/Setter.cs b/src/Perspex.Styling/Styling/Setter.cs index 88b7ac9cbc..0db359fa40 100644 --- a/src/Perspex.Styling/Styling/Setter.cs +++ b/src/Perspex.Styling/Styling/Setter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Perspex.Data; using Perspex.Metadata; namespace Perspex.Styling @@ -46,6 +47,7 @@ namespace Perspex.Styling /// Gets or sets the property value. /// [Content] + [AssignBinding] public object Value { get; diff --git a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj index be37a88865..f3cfdb7f10 100644 --- a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj +++ b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj @@ -82,6 +82,7 @@ + diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs index e54d4089a5..6788e509e2 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs @@ -4,6 +4,7 @@ using System; using System.Reactive.Linq; using System.Reactive.Subjects; +using Perspex.Data; using Xunit; namespace Perspex.Base.UnitTests @@ -234,7 +235,7 @@ namespace Perspex.Base.UnitTests { Class1 target1 = new Class1(); Class1 target2 = new Class1(); - BindingDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneWay); + IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneWay); target1.SetValue(Class1.FooProperty, "first"); target2[binding] = target1[!Class1.FooProperty]; @@ -248,7 +249,7 @@ namespace Perspex.Base.UnitTests { Class1 target1 = new Class1(); Class1 target2 = new Class1(); - BindingDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.TwoWay); + IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.TwoWay); target1.SetValue(Class1.FooProperty, "first"); target2[binding] = target1[!Class1.FooProperty]; @@ -264,7 +265,7 @@ namespace Perspex.Base.UnitTests { Class1 target1 = new Class1(); Class1 target2 = new Class1(); - BindingDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneTime); + IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneTime); target1.SetValue(Class1.FooProperty, "first"); target2[binding] = target1[!Class1.FooProperty]; diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs index 491c31b9ad..07a4758292 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reactive.Subjects; +using Perspex.Data; using Xunit; namespace Perspex.Base.UnitTests diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_SetValue.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_SetValue.cs index 7c7dce01aa..1037c5476a 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_SetValue.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_SetValue.cs @@ -2,6 +2,7 @@ // 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 diff --git a/tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs b/tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs index b37cb49323..f49cf3466d 100644 --- a/tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs +++ b/tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs @@ -2,6 +2,7 @@ // 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 diff --git a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index d19d4c4999..21cce03055 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -9,6 +9,7 @@ using Perspex.Collections; using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; +using Perspex.Data; using Perspex.Markup.Xaml.Data; using Xunit; @@ -418,8 +419,8 @@ namespace Perspex.Controls.UnitTests.Primitives }; // Bind Items and SelectedItems to the VM. - itemsBinding.Bind(target, TestSelector.ItemsProperty); - selectedItemsBinding.Bind(target, TestSelector.SelectedItemsProperty); + target.Bind(TestSelector.ItemsProperty, itemsBinding); + target.Bind(TestSelector.SelectedItemsProperty, selectedItemsBinding); // Set DataContext and SelectedIndex target.DataContext = vm; @@ -451,7 +452,7 @@ namespace Perspex.Controls.UnitTests.Primitives }; var itemsBinding = new Binding { Path = "Items" }; - itemsBinding.Bind(target, TestSelector.ItemsProperty); + target.Bind(TestSelector.ItemsProperty, itemsBinding); Assert.Same(data.Items, target.Items); diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index 78dc8d764e..a6275f452c 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -194,7 +194,8 @@ namespace Perspex.LeakTests Path = "Name" }; - binding.Bind((TextBox)window.Content, TextBox.TextProperty); + var textBox = (TextBox)window.Content; + textBox.Bind(TextBox.TextProperty, binding); // Do a layout and make sure that TextBox gets added to visual tree and its // Text property set. diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs index 2cd71d4ac9..64efaa07de 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -3,6 +3,7 @@ using Moq; using Perspex.Controls; +using Perspex.Data; using Perspex.Markup.Data; using Perspex.Markup.Xaml.Data; using ReactiveUI; @@ -23,7 +24,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Mode = BindingMode.OneWay, }; - binding.Bind(target, TextBox.TextProperty); + target.Bind(TextBox.TextProperty, binding); Assert.Equal("foo", target.Text); source.Foo = "bar"; @@ -43,7 +44,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Mode = BindingMode.TwoWay, }; - binding.Bind(target, TextBox.TextProperty); + target.Bind(TextBox.TextProperty, binding); Assert.Equal("foo", target.Text); source.Foo = "bar"; @@ -63,7 +64,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Mode = BindingMode.OneTime, }; - binding.Bind(target, TextBox.TextProperty); + target.Bind(TextBox.TextProperty, binding); Assert.Equal("foo", target.Text); source.Foo = "bar"; @@ -83,7 +84,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Mode = BindingMode.OneWayToSource, }; - binding.Bind(target, TextBox.TextProperty); + target.Bind(TextBox.TextProperty, binding); Assert.Equal("bar", source.Foo); target.Text = "baz"; @@ -103,7 +104,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Foo", }; - binding.Bind(target, TextBox.TextProperty); + target.Bind(TextBox.TextProperty, binding); Assert.Equal("foo", target.Text); source.Foo = "bar"; @@ -128,7 +129,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Header", }; - binding.Bind(parent.Child, Control.DataContextProperty); + parent.Child.Bind(Control.DataContextProperty, binding); Assert.Equal("Foo", parent.Child.DataContext); @@ -152,7 +153,8 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Foo", }; - binding.Bind(child, Control.DataContextProperty); + child.Bind(Control.DataContextProperty, binding); + Assert.Null(child.DataContext); parent.Child = child; Assert.Equal("foo", child.DataContext); @@ -167,7 +169,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Foo", }; - var result = binding.CreateSubject(target, TextBox.TextProperty.PropertyType); + var result = binding.CreateSubject(target, TextBox.TextProperty); Assert.IsType(((ExpressionSubject)result).Converter); } @@ -183,7 +185,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Foo", }; - var result = binding.CreateSubject(target, TextBox.TextProperty.PropertyType); + var result = binding.CreateSubject(target, TextBox.TextProperty); Assert.Same(converter.Object, ((ExpressionSubject)result).Converter); } @@ -200,7 +202,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Bar", }; - var result = binding.CreateSubject(target, TextBox.TextProperty.PropertyType); + var result = binding.CreateSubject(target, TextBox.TextProperty); Assert.Same("foo", ((ExpressionSubject)result).ConverterParameter); } @@ -236,8 +238,8 @@ namespace Perspex.Markup.Xaml.UnitTests.Data }; // Bind Foo and Bar to the VM. - fooBinding.Bind(target, OldDataContextTest.FooProperty); - barBinding.Bind(target, OldDataContextTest.BarProperty); + target.Bind(OldDataContextTest.FooProperty, fooBinding); + target.Bind(OldDataContextTest.BarProperty, barBinding); target.DataContext = vm; // Make sure the control's Foo and Bar properties are read from the VM diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs index c47d53d0a3..d1b929b4a9 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs @@ -38,7 +38,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Text", }; - binding.Bind(target, TextBlock.TextProperty); + target.Bind(TextBox.TextProperty, binding); Assert.Equal("foo", target.Text); } @@ -73,7 +73,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data ElementName = "source", }; - binding.Bind(target, ContentControl.ContentProperty); + target.Bind(ContentControl.ContentProperty, binding); Assert.Same(source, target.Content); } @@ -104,7 +104,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Text", }; - binding.Bind(target, TextBlock.TextProperty); + target.Bind(TextBox.TextProperty, binding); stackPanel.Children.Add(new TextBlock { @@ -140,7 +140,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data ElementName = "source", }; - binding.Bind(target, ContentControl.ContentProperty); + target.Bind(ContentControl.ContentProperty, binding); var source = new TextBlock { diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs index 75804b0748..b8ddd9a280 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs @@ -6,6 +6,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using Moq; using Perspex.Controls; +using Perspex.Data; using Perspex.Markup.Xaml.Data; using Perspex.Styling; using Xunit; @@ -26,7 +27,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Foo", }; - binding.Bind(target.Object, TextBox.TextProperty); + target.Object.Bind(TextBox.TextProperty, binding); target.Verify(x => x.Bind( TextBox.TextProperty, @@ -46,7 +47,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Foo", }; - binding.Bind(target.Object, TextBox.TextProperty); + target.Object.Bind(TextBox.TextProperty, binding); target.Verify(x => x.Bind( TextBox.TextProperty, @@ -59,11 +60,11 @@ namespace Perspex.Markup.Xaml.UnitTests.Data return CreateTarget(templatedParent: templatedParent); } - private Mock CreateTarget( + private Mock CreateTarget( ITemplatedControl templatedParent = null, string text = null) { - var result = new Mock(); + var result = new Mock(); result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent); result.Setup(x => x.GetValue((PerspexProperty)Control.TemplatedParentProperty)).Returns(templatedParent); diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs index 9583c85525..908bb16e37 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs @@ -33,7 +33,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data var target = new Mock(); target.Setup(x => x.GetValue(Control.DataContextProperty)).Returns(source); - var subject = binding.CreateSubject(target.Object, typeof(string)); + var subject = binding.CreateSubject(target.Object, null); var result = await subject.Take(1); Assert.Equal("1,2,3", result); diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj index 3883b7594e..8a79e9eb28 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -43,9 +43,6 @@ 4 - - ..\..\packages\Octokit.0.14.0\lib\net45\Octokit.dll - ..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll True @@ -104,7 +101,9 @@ + + diff --git a/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs new file mode 100644 index 0000000000..63c8751d82 --- /dev/null +++ b/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs @@ -0,0 +1,33 @@ +// 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.Linq; +using Moq; +using Perspex.Markup.Xaml.Data; +using Perspex.Platform; +using Perspex.Styling; +using Xunit; + +namespace Perspex.Markup.Xaml.UnitTests +{ + public class StyleTests + { + [Fact] + public void Binding_Should_Be_Assigned_To_Setter_Value_Instead_Of_Bound() + { + using (PerspexLocator.EnterScope()) + { + PerspexLocator.CurrentMutable + .Bind() + .ToConstant(Mock.Of()); + + var xaml = ""; + var loader = new PerspexXamlLoader(); + var style = (Style)loader.Load(xaml); + var setter = (Setter)(style.Setters.First()); + + Assert.IsType(setter.Value); + } + } + } +} diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs new file mode 100644 index 0000000000..5bf4cea9eb --- /dev/null +++ b/tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs @@ -0,0 +1,34 @@ +// 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.Linq; +using Moq; +using Perspex.Controls.Templates; +using Perspex.Markup.Xaml.Data; +using Perspex.Markup.Xaml.Templates; +using Perspex.Platform; +using Xunit; + +namespace Perspex.Markup.Xaml.UnitTests +{ + public class TreeDataTemplateTests + { + [Fact] + public void Binding_Should_Be_Assigned_To_ItemsSource_Instead_Of_Bound() + { + using (PerspexLocator.EnterScope()) + { + PerspexLocator.CurrentMutable + .Bind() + .ToConstant(Mock.Of()); + + var xaml = ""; + var loader = new PerspexXamlLoader(); + var templates = (DataTemplates)loader.Load(xaml); + var template = (TreeDataTemplate)(templates.First()); + + Assert.IsType(template.ItemsSource); + } + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index f9e0f1090e..427a806376 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs @@ -8,6 +8,7 @@ using System.Reactive; using System.Reactive.Linq; using Perspex.Collections; using Perspex.Controls; +using Perspex.Data; using Xunit; namespace Perspex.Styling.UnitTests diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index e5477ebee3..fab2f15a97 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -8,6 +8,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Perspex.Collections; using Perspex.Controls; +using Perspex.Data; using Xunit; namespace Perspex.Styling.UnitTests diff --git a/tests/Perspex.Styling.UnitTests/StyleTests.cs b/tests/Perspex.Styling.UnitTests/StyleTests.cs index f915f4169c..7066d2fe28 100644 --- a/tests/Perspex.Styling.UnitTests/StyleTests.cs +++ b/tests/Perspex.Styling.UnitTests/StyleTests.cs @@ -141,52 +141,6 @@ namespace Perspex.Styling.UnitTests Assert.Equal(new[] { "foodefault", "Foo", "Bar", "foodefault" }, values); } - [Fact] - public void Style_With_ObservableSetter_Should_Update_Value() - { - var source = new BehaviorSubject("Foo"); - - Style style = new Style(x => x.OfType()) - { - Setters = new[] - { - new ObservableSetter(Class1.FooProperty, source), - }, - }; - - var target = new Class1(); - - style.Attach(target, null); - - Assert.Equal("Foo", target.Foo); - } - - [Fact] - public void Style_With_ObservableSetter_Should_Update_And_Restore_Value() - { - var source = new BehaviorSubject("Foo"); - - var style = new Style(x => x.OfType().Class("foo")) - { - Setters = new[] - { - new ObservableSetter(Class1.FooProperty, source), - }, - }; - - var target = new Class1(); - - style.Attach(target, null); - - Assert.Equal("foodefault", target.Foo); - target.Classes.Add("foo"); - Assert.Equal("Foo", target.Foo); - source.OnNext("Bar"); - Assert.Equal("Bar", target.Foo); - target.Classes.Remove("foo"); - Assert.Equal("foodefault", target.Foo); - } - [Fact] public void Style_Should_Detach_When_Removed_From_Logical_Tree() { diff --git a/tests/Perspex.Styling.UnitTests/TestControlBase.cs b/tests/Perspex.Styling.UnitTests/TestControlBase.cs index 6743d2e468..3a0f0e5434 100644 --- a/tests/Perspex.Styling.UnitTests/TestControlBase.cs +++ b/tests/Perspex.Styling.UnitTests/TestControlBase.cs @@ -5,6 +5,7 @@ using System; using System.Reactive; using Perspex.Collections; using Perspex.Controls; +using Perspex.Data; namespace Perspex.Styling.UnitTests { diff --git a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs index f84a7d7ad6..847a124066 100644 --- a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs +++ b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs @@ -5,6 +5,7 @@ using System; using System.Reactive; using Perspex.Collections; using Perspex.Controls; +using Perspex.Data; namespace Perspex.Styling.UnitTests { From 5e0dd8fcd38fb85b45aa69d4cc3102edbcd01f06 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 18 Jan 2016 23:39:36 +0100 Subject: [PATCH 04/10] Support Setters with Bindings. However Styles with activators (e.g. "Foo.class") not yet supported. --- src/Perspex.Styling/Styling/Setter.cs | 46 +++++++++++++++++-- .../Perspex.Styling.UnitTests.csproj | 1 + .../Perspex.Styling.UnitTests/SetterTests.cs | 28 +++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 tests/Perspex.Styling.UnitTests/SetterTests.cs diff --git a/src/Perspex.Styling/Styling/Setter.cs b/src/Perspex.Styling/Styling/Setter.cs index 0db359fa40..e93729cf6e 100644 --- a/src/Perspex.Styling/Styling/Setter.cs +++ b/src/Perspex.Styling/Styling/Setter.cs @@ -62,15 +62,53 @@ namespace Perspex.Styling /// An optional activator. public void Apply(IStyle style, IStyleable control, IObservable activator) { - if (activator == null) + if (Property == null) { - control.SetValue(Property, Value, BindingPriority.Style); + throw new InvalidOperationException("Setter.Property must be set."); + } + + var binding = Value as IBinding; + + if (binding != null) + { + if (activator == null) + { + Bind(control, Property, binding); + } + else + { + throw new NotSupportedException( + "Setter bindings with activators not yet supported."); + } } else { - var binding = new StyleBinding(activator, Value, style.ToString()); - control.Bind(Property, binding, BindingPriority.StyleTrigger); + if (activator == null) + { + control.SetValue(Property, Value, BindingPriority.Style); + } + else + { + var activated = new StyleBinding(activator, Value, style.ToString()); + control.Bind(Property, activated, BindingPriority.StyleTrigger); + } } } + + private void Bind(IStyleable control, PerspexProperty property, IBinding binding) + { + var mode = binding.Mode; + + if (mode == BindingMode.Default) + { + mode = property.DefaultBindingMode; + } + + control.Bind( + property, + binding.CreateSubject(control, property), + mode, + binding.Priority); + } } } diff --git a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj index cc22e7f257..b9e82d589d 100644 --- a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj +++ b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj @@ -91,6 +91,7 @@ + diff --git a/tests/Perspex.Styling.UnitTests/SetterTests.cs b/tests/Perspex.Styling.UnitTests/SetterTests.cs new file mode 100644 index 0000000000..c42426e97b --- /dev/null +++ b/tests/Perspex.Styling.UnitTests/SetterTests.cs @@ -0,0 +1,28 @@ +// 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.Reactive.Subjects; +using Moq; +using Perspex.Controls; +using Perspex.Data; +using Xunit; + +namespace Perspex.Styling.UnitTests +{ + public class SetterTests + { + [Fact] + public void Setter_Should_Apply_Binding_To_Property() + { + var control = new TextBlock(); + var subject = new BehaviorSubject("foo"); + var binding = Mock.Of(x => x.CreateSubject(control, TextBlock.TextProperty) == subject); + var style = Mock.Of(); + var setter = new Setter(TextBlock.TextProperty, binding); + + setter.Apply(style, control, null); + + Assert.Equal("foo", control.Text); + } + } +} From e24770c0535261790e14028ad0b70274cb1fa220 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 19 Jan 2016 20:35:49 +0100 Subject: [PATCH 05/10] Use correct match logic in TreeDataTemplate. Use same as DataTemplate. --- .../Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs index 0ff1ec7a6d..ceabd09bb7 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Reactive.Linq; +using System.Reflection; using Perspex.Controls; using Perspex.Controls.Templates; using Perspex.Data; @@ -27,10 +28,12 @@ namespace Perspex.Markup.Xaml.Templates { if (DataType == null) { - throw new InvalidOperationException("DataTemplate must have a DataType."); + return true; + } + else + { + return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo()); } - - return DataType == data.GetType(); } public IEnumerable ItemsSelector(object item) From 36f2f3e1e0ee3587da3e66f8fa5b94748b645339 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 22 Jan 2016 22:04:10 +0100 Subject: [PATCH 06/10] More work on allowing bindings in setters. Support activated ISubjects. --- src/Perspex.Base/PerspexObjectExtensions.cs | 30 +++++ src/Perspex.Styling/Perspex.Styling.csproj | 4 +- .../Styling/ActivatedObservable.cs | 80 +++++++++++ .../Styling/ActivatedSubject.cs | 124 ++++++++++++++++++ src/Perspex.Styling/Styling/ActivatedValue.cs | 72 ++++++++++ src/Perspex.Styling/Styling/Setter.cs | 19 ++- src/Perspex.Styling/Styling/StyleBinding.cs | 107 --------------- .../ActivatedObservableTests.cs | 70 ++++++++++ .../ActivatedSubjectTests.cs | 46 +++++++ .../ActivatedValueTests.cs | 42 ++++++ .../Perspex.Styling.UnitTests.csproj | 4 +- .../StyleBindingTests.cs | 79 ----------- 12 files changed, 485 insertions(+), 192 deletions(-) create mode 100644 src/Perspex.Styling/Styling/ActivatedObservable.cs create mode 100644 src/Perspex.Styling/Styling/ActivatedSubject.cs create mode 100644 src/Perspex.Styling/Styling/ActivatedValue.cs delete mode 100644 src/Perspex.Styling/Styling/StyleBinding.cs create mode 100644 tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs create mode 100644 tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs create mode 100644 tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs delete mode 100644 tests/Perspex.Styling.UnitTests/StyleBindingTests.cs diff --git a/src/Perspex.Base/PerspexObjectExtensions.cs b/src/Perspex.Base/PerspexObjectExtensions.cs index d7b2d5a748..c958ecbffc 100644 --- a/src/Perspex.Base/PerspexObjectExtensions.cs +++ b/src/Perspex.Base/PerspexObjectExtensions.cs @@ -103,6 +103,36 @@ namespace Perspex GetDescription(o, property)); } + /// + /// Gets a subject for a . + /// + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject GetSubject( + this IPerspexObject o, + PerspexProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + // TODO: Subject.Create is not yet in stable Rx : once it is, remove the + // AnonymousSubject classes from this file and use Subject.Create. + var output = new Subject(); + var result = new AnonymousSubject( + Observer.Create( + x => output.OnNext(x), + e => output.OnError(e), + () => output.OnCompleted()), + o.GetObservable(property)); + o.Bind(property, output, priority); + return result; + } + /// /// Gets a subject for a . /// diff --git a/src/Perspex.Styling/Perspex.Styling.csproj b/src/Perspex.Styling/Perspex.Styling.csproj index 5f79ca5820..df3c17278d 100644 --- a/src/Perspex.Styling/Perspex.Styling.csproj +++ b/src/Perspex.Styling/Perspex.Styling.csproj @@ -45,6 +45,8 @@ + + @@ -59,7 +61,7 @@ - + diff --git a/src/Perspex.Styling/Styling/ActivatedObservable.cs b/src/Perspex.Styling/Styling/ActivatedObservable.cs new file mode 100644 index 0000000000..ed313fa3a6 --- /dev/null +++ b/src/Perspex.Styling/Styling/ActivatedObservable.cs @@ -0,0 +1,80 @@ +// 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; +using System.Reactive.Linq; + +namespace Perspex.Styling +{ + /// + /// An observable which is switched on or off according to an activator observable. + /// + /// + /// An has two inputs: an activator observable a + /// observable which produces the activated value. When the activator + /// produces true, the will produce the current activated + /// value. When the activator produces false it will produce + /// . + /// + internal class ActivatedObservable : ObservableBase, IDescription + { + /// + /// The activator. + /// + private readonly IObservable _activator; + + /// + /// Initializes a new instance of the class. + /// + /// The activator. + /// An observable that produces the activated value. + /// The binding description. + public ActivatedObservable( + IObservable activator, + IObservable source, + string description) + { + _activator = activator; + Description = description; + Source = source; + } + + /// + /// Gets a description of the binding. + /// + public string Description + { + get; + } + + /// + /// Gets an observable which produces the . + /// + public IObservable Source + { + get; + } + + /// + /// Notifies the provider that an observer is to receive notifications. + /// + /// The observer. + /// IDisposable object used to unsubscribe from the observable sequence. + protected override IDisposable SubscribeCore(IObserver observer) + { + Contract.Requires(observer != null); + + var sourceCompleted = Source.TakeLast(1).Select(_ => Unit.Default); + var activatorCompleted = _activator.TakeLast(1).Select(_ => Unit.Default); + var completed = sourceCompleted.Merge(activatorCompleted); + + return _activator + .CombineLatest(Source, (x, y) => new { Active = x, Value = y }) + .Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) + .DistinctUntilChanged() + .TakeUntil(completed) + .Subscribe(observer); + } + } +} diff --git a/src/Perspex.Styling/Styling/ActivatedSubject.cs b/src/Perspex.Styling/Styling/ActivatedSubject.cs new file mode 100644 index 0000000000..d99ffae105 --- /dev/null +++ b/src/Perspex.Styling/Styling/ActivatedSubject.cs @@ -0,0 +1,124 @@ +// 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; +using System.Reactive.Linq; +using System.Reactive.Subjects; + +namespace Perspex.Styling +{ + /// + /// A subject which is switched on or off according to an activator observable. + /// + /// + /// An has two inputs: an activator observable and either an + /// or a observable which produces the + /// activated value. When the activator produces true, the will + /// produce the current activated value. When the activator produces false it will produce + /// . + /// + internal class ActivatedSubject : ISubject, IDescription + { + private IObservable _activator; + private bool _active; + private object _pushValue; + + /// + /// Initializes a new instance of the class. + /// + /// The activator. + /// An observable that produces the activated value. + /// The binding description. + public ActivatedSubject( + IObservable activator, + ISubject source, + string description) + { + _activator = activator; + Description = description; + Source = source; + + _activator.Skip(1).Subscribe(ActivatorChanged); + } + + /// + /// Gets a description of the binding. + /// + public string Description + { + get; + } + + /// + /// Gets the underlying subject. + /// + public ISubject Source + { + get; + } + + /// + /// Notifies all subscribed observers about the end of the sequence. + /// + public void OnCompleted() + { + if (_active) + { + Source.OnCompleted(); + } + } + + /// + /// Notifies all subscribed observers with the exception. + /// + /// The exception to send to all subscribed observers. + /// is null. + public void OnError(Exception error) + { + if (_active) + { + Source.OnError(error); + } + } + + /// + /// Notifies all subscribed observers with the value. + /// + /// The value to send to all subscribed observers. + public void OnNext(object value) + { + _pushValue = value; + + if (_active) + { + Source.OnNext(value); + } + } + + /// + /// Notifies the provider that an observer is to receive notifications. + /// + /// The observer. + /// IDisposable object used to unsubscribe from the observable sequence. + public IDisposable Subscribe(IObserver observer) + { + Contract.Requires(observer != null); + + var completed = _activator.TakeLast(1).Select(_ => Unit.Default); + + return _activator + .CombineLatest(Source, (x, y) => new { Active = x, Value = y }) + .Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) + .DistinctUntilChanged() + .TakeUntil(completed) + .Subscribe(observer); + } + + private void ActivatorChanged(bool active) + { + _active = active; + Source.OnNext(active ? _pushValue : PerspexProperty.UnsetValue); + } + } +} diff --git a/src/Perspex.Styling/Styling/ActivatedValue.cs b/src/Perspex.Styling/Styling/ActivatedValue.cs new file mode 100644 index 0000000000..0c02598086 --- /dev/null +++ b/src/Perspex.Styling/Styling/ActivatedValue.cs @@ -0,0 +1,72 @@ +// 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; +using System.Reactive.Linq; + +namespace Perspex.Styling +{ + /// + /// An value which is switched on or off according to an activator observable. + /// + /// + /// An has two inputs: an activator observable and an + /// . When the activator produces true, the + /// will produce the current value. When the activator + /// produces false it will produce . + /// + internal class ActivatedValue : ObservableBase, IDescription + { + /// + /// The activator. + /// + private readonly IObservable _activator; + + /// + /// Initializes a new instance of the class. + /// + /// The activator. + /// The activated value. + /// The binding description. + public ActivatedValue( + IObservable activator, + object value, + string description) + { + _activator = activator; + Value = value; + Description = description; + } + + /// + /// Gets the activated value. + /// + public object Value + { + get; + } + + /// + /// Gets a description of the binding. + /// + public string Description + { + get; + } + + /// + /// Notifies the provider that an observer is to receive notifications. + /// + /// The observer. + /// IDisposable object used to unsubscribe from the observable sequence. + protected override IDisposable SubscribeCore(IObserver observer) + { + Contract.Requires(observer != null); + + return _activator + .Select(active => active ? Value : PerspexProperty.UnsetValue) + .Subscribe(observer); + } + } +} diff --git a/src/Perspex.Styling/Styling/Setter.cs b/src/Perspex.Styling/Styling/Setter.cs index e93729cf6e..103ebb6548 100644 --- a/src/Perspex.Styling/Styling/Setter.cs +++ b/src/Perspex.Styling/Styling/Setter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive.Subjects; using Perspex.Data; using Perspex.Metadata; @@ -77,8 +78,9 @@ namespace Perspex.Styling } else { - throw new NotSupportedException( - "Setter bindings with activators not yet supported."); + var subject = binding.CreateSubject(control, Property); + var activated = new ActivatedSubject(activator, subject, style.ToString()); + Bind(control, Property, binding, activated); } } else @@ -89,13 +91,22 @@ namespace Perspex.Styling } else { - var activated = new StyleBinding(activator, Value, style.ToString()); + var activated = new ActivatedValue(activator, Value, style.ToString()); control.Bind(Property, activated, BindingPriority.StyleTrigger); } } } private void Bind(IStyleable control, PerspexProperty property, IBinding binding) + { + Bind(control, property, binding, binding.CreateSubject(control, property)); + } + + private void Bind( + IStyleable control, + PerspexProperty property, + IBinding binding, + ISubject subject) { var mode = binding.Mode; @@ -106,7 +117,7 @@ namespace Perspex.Styling control.Bind( property, - binding.CreateSubject(control, property), + subject, mode, binding.Priority); } diff --git a/src/Perspex.Styling/Styling/StyleBinding.cs b/src/Perspex.Styling/Styling/StyleBinding.cs deleted file mode 100644 index b2ddbeee1b..0000000000 --- a/src/Perspex.Styling/Styling/StyleBinding.cs +++ /dev/null @@ -1,107 +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; -using System.Reactive.Linq; - -namespace Perspex.Styling -{ - /// - /// Provides an observable for a style. - /// - /// - /// A has two inputs: an activator observable and either an - /// or a observable which produces the - /// activated value. When the activator produces true, the will - /// produce the current activated value. When the activator produces false it will produce - /// . - /// - internal class StyleBinding : ObservableBase, IDescription - { - /// - /// The activator. - /// - private readonly IObservable _activator; - - /// - /// Initializes a new instance of the class. - /// - /// The activator. - /// The activated value. - /// The binding description. - public StyleBinding( - IObservable activator, - object activatedValue, - string description) - { - _activator = activator; - ActivatedValue = activatedValue; - Description = description; - } - - /// - /// Initializes a new instance of the class. - /// - /// The activator. - /// An observable that produces the activated value. - /// The binding description. - public StyleBinding( - IObservable activator, - IObservable source, - string description) - { - _activator = activator; - Description = description; - Source = source; - } - - /// - /// Gets the activated value. - /// - public object ActivatedValue - { - get; - } - - /// - /// Gets a description of the binding. - /// - public string Description - { - get; - } - - /// - /// Gets an observable which produces the . - /// - public IObservable Source - { - get; - } - - /// - /// Notifies the provider that an observer is to receive notifications. - /// - /// The observer. - /// IDisposable object used to unsubscribe from the observable sequence. - protected override IDisposable SubscribeCore(IObserver observer) - { - Contract.Requires(observer != null); - - if (Source == null) - { - return _activator - .Select(active => active ? ActivatedValue : PerspexProperty.UnsetValue) - .Subscribe(observer); - } - else - { - return _activator - .CombineLatest(Source, (x, y) => new { Active = x, Value = y }) - .Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) - .Subscribe(observer); - } - } - } -} diff --git a/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs new file mode 100644 index 0000000000..e1beb992c2 --- /dev/null +++ b/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Xunit; + +namespace Perspex.Styling.UnitTests +{ + public class ActivatedObservableTests + { + [Fact] + public void Should_Produce_Correct_Values() + { + var activator = new BehaviorSubject(false); + var source = new BehaviorSubject(1); + var target = new ActivatedObservable(activator, source, string.Empty); + var result = new List(); + + target.Subscribe(x => result.Add(x)); + + activator.OnNext(true); + source.OnNext(2); + activator.OnNext(false); + source.OnNext(3); + activator.OnNext(true); + + Assert.Equal( + new[] + { + PerspexProperty.UnsetValue, + 1, + 2, + PerspexProperty.UnsetValue, + 3, + }, + result); + } + + [Fact] + public void Should_Complete_When_Source_Completes() + { + var activator = new BehaviorSubject(false); + var source = new BehaviorSubject(1); + var target = new ActivatedObservable(activator, source, string.Empty); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + source.OnCompleted(); + + Assert.True(completed); + } + + [Fact] + public void Should_Complete_When_Activator_Completes() + { + var activator = new BehaviorSubject(false); + var source = new BehaviorSubject(1); + var target = new ActivatedObservable(activator, source, string.Empty); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + activator.OnCompleted(); + + Assert.True(completed); + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs new file mode 100644 index 0000000000..d4654dd199 --- /dev/null +++ b/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs @@ -0,0 +1,46 @@ +// 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.Reactive.Subjects; +using Perspex.Data; +using Xunit; + +namespace Perspex.Styling.UnitTests +{ + public class ActivatedSubjectTests + { + [Fact] + public void Should_Set_Values() + { + var data = new Class1 { Foo = "foo" }; + var activator = new BehaviorSubject(false); + var source = data.GetSubject( + (PerspexProperty)Class1.FooProperty, + BindingPriority.LocalValue); + var target = new ActivatedSubject(activator, source, string.Empty); + + target.OnNext("bar"); + Assert.Equal("foo", data.Foo); + activator.OnNext(true); + target.OnNext("baz"); + Assert.Equal("baz", data.Foo); + activator.OnNext(false); + Assert.Equal("foo", data.Foo); + target.OnNext("bax"); + activator.OnNext(true); + Assert.Equal("bax", data.Foo); + } + + private class Class1 : PerspexObject + { + public static readonly PerspexProperty FooProperty = + PerspexProperty.Register("Foo", "foodefault"); + + public string Foo + { + get { return GetValue(FooProperty); } + set { SetValue(FooProperty, value); } + } + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs new file mode 100644 index 0000000000..4921aeaa7b --- /dev/null +++ b/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Xunit; + +namespace Perspex.Styling.UnitTests +{ + public class ActivatedValueTests + { + [Fact] + public void Should_Produce_Correct_Values() + { + var activator = new BehaviorSubject(false); + var target = new ActivatedValue(activator, 1, string.Empty); + var result = new List(); + + target.Subscribe(x => result.Add(x)); + + activator.OnNext(true); + activator.OnNext(false); + + Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result); + } + + [Fact] + public void Should_Complete_When_Activator_Completes() + { + var activator = new BehaviorSubject(false); + var target = new ActivatedValue(activator, 1, string.Empty); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + activator.OnCompleted(); + + Assert.True(completed); + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj index b9e82d589d..af640ebdf4 100644 --- a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj +++ b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj @@ -80,6 +80,8 @@ + + @@ -90,7 +92,7 @@ - + diff --git a/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs b/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs deleted file mode 100644 index 82a7f66d07..0000000000 --- a/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs +++ /dev/null @@ -1,79 +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.Collections.Generic; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using Xunit; - -namespace Perspex.Styling.UnitTests -{ - public class StyleBindingTests - { - [Fact] - public async void Should_Produce_UnsetValue_On_Activator_False() - { - var activator = new BehaviorSubject(false); - var target = new StyleBinding(activator, 1, string.Empty); - var result = await target.Take(1); - - Assert.Equal(PerspexProperty.UnsetValue, result); - } - - [Fact] - public async void Should_Produce_Value_On_Activator_True() - { - var activator = new BehaviorSubject(true); - var target = new StyleBinding(activator, 1, string.Empty); - var result = await target.Take(1); - - Assert.Equal(1, result); - } - - [Fact] - public void Should_Change_Value_On_Activator_Change() - { - var activator = new BehaviorSubject(false); - var target = new StyleBinding(activator, 1, string.Empty); - var result = new List(); - - target.Subscribe(x => result.Add(x)); - - activator.OnNext(true); - activator.OnNext(false); - - Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result); - } - - [Fact] - public void Should_Change_Value_With_Source_Observable() - { - var activator = new BehaviorSubject(false); - var source = new BehaviorSubject(1); - var target = new StyleBinding(activator, source, string.Empty); - var result = new List(); - - target.Subscribe(x => result.Add(x)); - - activator.OnNext(true); - source.OnNext(2); - activator.OnNext(false); - - Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, 2, PerspexProperty.UnsetValue }, result); - } - - [Fact] - public void Should_Complete_When_Activator_Completes() - { - var activator = new BehaviorSubject(false); - var target = new StyleBinding(activator, 1, string.Empty); - var completed = false; - - target.Subscribe(_ => { }, () => completed = true); - activator.OnCompleted(); - - Assert.True(completed); - } - } -} From ce997f6965e9e1190e1cecbb88190ff7f50e1253 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 22 Jan 2016 22:10:05 +0100 Subject: [PATCH 07/10] Refactor ActivatedSubject Inherit from ActivatedObject. --- .../Styling/ActivatedObservable.cs | 26 ++++------ .../Styling/ActivatedSubject.cs | 48 ++++--------------- 2 files changed, 18 insertions(+), 56 deletions(-) diff --git a/src/Perspex.Styling/Styling/ActivatedObservable.cs b/src/Perspex.Styling/Styling/ActivatedObservable.cs index ed313fa3a6..97c1b73106 100644 --- a/src/Perspex.Styling/Styling/ActivatedObservable.cs +++ b/src/Perspex.Styling/Styling/ActivatedObservable.cs @@ -19,11 +19,6 @@ namespace Perspex.Styling /// internal class ActivatedObservable : ObservableBase, IDescription { - /// - /// The activator. - /// - private readonly IObservable _activator; - /// /// Initializes a new instance of the class. /// @@ -35,26 +30,25 @@ namespace Perspex.Styling IObservable source, string description) { - _activator = activator; + Activator = activator; Description = description; Source = source; } + /// + /// Gets the activator observable. + /// + public IObservable Activator { get; } + /// /// Gets a description of the binding. /// - public string Description - { - get; - } + public string Description { get; } /// /// Gets an observable which produces the . /// - public IObservable Source - { - get; - } + public IObservable Source { get; } /// /// Notifies the provider that an observer is to receive notifications. @@ -66,10 +60,10 @@ namespace Perspex.Styling Contract.Requires(observer != null); var sourceCompleted = Source.TakeLast(1).Select(_ => Unit.Default); - var activatorCompleted = _activator.TakeLast(1).Select(_ => Unit.Default); + var activatorCompleted = Activator.TakeLast(1).Select(_ => Unit.Default); var completed = sourceCompleted.Merge(activatorCompleted); - return _activator + return Activator .CombineLatest(Source, (x, y) => new { Active = x, Value = y }) .Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) .DistinctUntilChanged() diff --git a/src/Perspex.Styling/Styling/ActivatedSubject.cs b/src/Perspex.Styling/Styling/ActivatedSubject.cs index d99ffae105..99da010dae 100644 --- a/src/Perspex.Styling/Styling/ActivatedSubject.cs +++ b/src/Perspex.Styling/Styling/ActivatedSubject.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 System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -18,11 +17,10 @@ namespace Perspex.Styling /// produce the current activated value. When the activator produces false it will produce /// . /// - internal class ActivatedSubject : ISubject, IDescription + internal class ActivatedSubject : ActivatedObservable, ISubject, IDescription { - private IObservable _activator; private bool _active; - private object _pushValue; + private object _value; /// /// Initializes a new instance of the class. @@ -34,28 +32,17 @@ namespace Perspex.Styling IObservable activator, ISubject source, string description) + : base(activator, source, description) { - _activator = activator; - Description = description; - Source = source; - - _activator.Skip(1).Subscribe(ActivatorChanged); - } - - /// - /// Gets a description of the binding. - /// - public string Description - { - get; + Activator.Skip(1).Subscribe(ActivatorChanged); } /// /// Gets the underlying subject. /// - public ISubject Source + public new ISubject Source { - get; + get { return (ISubject)base.Source; } } /// @@ -88,7 +75,7 @@ namespace Perspex.Styling /// The value to send to all subscribed observers. public void OnNext(object value) { - _pushValue = value; + _value = value; if (_active) { @@ -96,29 +83,10 @@ namespace Perspex.Styling } } - /// - /// Notifies the provider that an observer is to receive notifications. - /// - /// The observer. - /// IDisposable object used to unsubscribe from the observable sequence. - public IDisposable Subscribe(IObserver observer) - { - Contract.Requires(observer != null); - - var completed = _activator.TakeLast(1).Select(_ => Unit.Default); - - return _activator - .CombineLatest(Source, (x, y) => new { Active = x, Value = y }) - .Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) - .DistinctUntilChanged() - .TakeUntil(completed) - .Subscribe(observer); - } - private void ActivatorChanged(bool active) { _active = active; - Source.OnNext(active ? _pushValue : PerspexProperty.UnsetValue); + Source.OnNext(active ? _value : PerspexProperty.UnsetValue); } } } From 3e6402711c82b835b9462d1216bd671fa6333a50 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Jan 2016 18:19:20 +0100 Subject: [PATCH 08/10] Make TreeViewItem.IsExpanded bindings work. --- .../ViewModels/MainWindowViewModel.cs | 34 ++++++++ .../ViewModels/TestNode.cs | 11 ++- .../Views/MainWindow.paml | 9 ++ .../XamlTestApplicationPcl.csproj | 16 ++++ .../XamlTestApplicationPcl/packages.config | 5 ++ .../Perspex.Markup.Xaml/Data/Binding.cs | 12 +-- .../MarkupExtensions/BindingExtension.cs | 2 + .../TemplateBindingExtension.cs | 3 +- src/Perspex.Base/Perspex.Base.csproj | 2 + src/Perspex.Base/PerspexObjectExtensions.cs | 51 +---------- .../Reactive/AnonymousSubject`1.cs | 16 ++++ .../Reactive/AnonymousSubject`2.cs | 49 +++++++++++ .../Generators/TreeItemContainerGenerator.cs | 1 - .../Primitives/ToggleButton.cs | 15 ++-- .../Styling/ActivatedSubject.cs | 31 +++++-- src/Perspex.Styling/Styling/Setter.cs | 9 +- .../StyleTests.cs | 87 +++++++++++++++++++ .../ActivatedSubjectTests.cs | 70 +++++++++++++-- 18 files changed, 340 insertions(+), 83 deletions(-) create mode 100644 src/Perspex.Base/Reactive/AnonymousSubject`1.cs create mode 100644 src/Perspex.Base/Reactive/AnonymousSubject`2.cs diff --git a/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs b/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs index 1523299d41..9ac24a622e 100644 --- a/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs +++ b/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs @@ -1,7 +1,9 @@ // Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Collections.Generic; +using ReactiveUI; namespace XamlTestApplication.ViewModels { @@ -22,6 +24,7 @@ namespace XamlTestApplication.ViewModels { Header = "Root", SubHeader = "Root Item", + IsExpanded = true, Children = new[] { new TestNode @@ -33,6 +36,7 @@ namespace XamlTestApplication.ViewModels { Header = "Child 2", SubHeader = "Child 2 Value", + IsExpanded = false, Children = new[] { new TestNode @@ -50,9 +54,39 @@ namespace XamlTestApplication.ViewModels } } }; + + CollapseNodesCommand = ReactiveCommand.Create(); + CollapseNodesCommand.Subscribe(_ => ExpandNodes(false)); + ExpandNodesCommand = ReactiveCommand.Create(); + ExpandNodesCommand.Subscribe(_ => ExpandNodes(true)); } public List Items { get; } public List Nodes { get; } + + public ReactiveCommand CollapseNodesCommand { get; } + + public ReactiveCommand ExpandNodesCommand { get; } + + public void ExpandNodes(bool expanded) + { + foreach (var node in Nodes) + { + ExpandNodes(node, expanded); + } + } + + private void ExpandNodes(TestNode node, bool expanded) + { + node.IsExpanded = expanded; + + if (node.Children != null) + { + foreach (var child in node.Children) + { + ExpandNodes(child, expanded); + } + } + } } } diff --git a/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs b/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs index 953bfd0f58..c2e0f4f15c 100644 --- a/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs +++ b/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs @@ -2,13 +2,22 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Collections.Generic; +using ReactiveUI; namespace XamlTestApplication.ViewModels { - public class TestNode + public class TestNode : ReactiveObject { + private bool _isExpanded; + public string Header { get; set; } public string SubHeader { get; set; } public IEnumerable Children { get; set; } + + public bool IsExpanded + { + get { return _isExpanded; } + set { this.RaiseAndSetIfChanged(ref this._isExpanded, value); } + } } } \ No newline at end of file diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml index 98633354b1..584c43c3af 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml @@ -147,6 +147,11 @@ + + + @@ -156,6 +161,10 @@ + + + + diff --git a/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj b/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj index a32ae5eec6..c7fb0e8484 100644 --- a/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj +++ b/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj @@ -123,6 +123,22 @@ ..\..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll True + + ..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll + True + diff --git a/samples/XamlTestApplicationPcl/packages.config b/samples/XamlTestApplicationPcl/packages.config index 2115574cc5..749731a770 100644 --- a/samples/XamlTestApplicationPcl/packages.config +++ b/samples/XamlTestApplicationPcl/packages.config @@ -1,4 +1,9 @@  + + + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs index 55b80f5d40..8a254cbf6e 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs @@ -71,21 +71,21 @@ namespace Perspex.Markup.Xaml.Data if (pathInfo.ElementName != null || ElementName != null) { - observer = CreateElementSubject( + observer = CreateElementObserver( (IControl)target, pathInfo.ElementName ?? ElementName, pathInfo.Path); } else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext) { - observer = CreateDataContextSubject( + observer = CreateDataContexObserver( target, pathInfo.Path, targetIsDataContext); } else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent) { - observer = CreateTemplatedParentSubject( + observer = CreateTemplatedParentObserver( target, pathInfo.Path); } @@ -148,7 +148,7 @@ namespace Perspex.Markup.Xaml.Data } } - private ExpressionObserver CreateDataContextSubject( + private ExpressionObserver CreateDataContexObserver( IPerspexObject target, string path, bool targetIsDataContext) @@ -178,7 +178,7 @@ namespace Perspex.Markup.Xaml.Data } } - private ExpressionObserver CreateTemplatedParentSubject( + private ExpressionObserver CreateTemplatedParentObserver( IPerspexObject target, string path) { @@ -196,7 +196,7 @@ namespace Perspex.Markup.Xaml.Data return result; } - private ExpressionObserver CreateElementSubject( + private ExpressionObserver CreateElementObserver( IControl target, string elementName, string path) diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index e43ba9cb03..d8a668da08 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -27,6 +27,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions ElementName = ElementName, Mode = Mode, Path = Path, + Priority = Priority, }; } @@ -35,5 +36,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions public string ElementName { get; set; } public BindingMode Mode { get; set; } public string Path { get; set; } + public BindingPriority Priority { get; set; } = BindingPriority.LocalValue; } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs index 361b04c915..cec5146c71 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs @@ -25,9 +25,9 @@ namespace Perspex.Markup.Xaml.MarkupExtensions Converter = Converter, ElementName = ElementName, Mode = Mode, - Priority = BindingPriority.TemplatedParent, RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), Path = Path, + Priority = Priority, }; } @@ -35,5 +35,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions public string ElementName { get; set; } public BindingMode Mode { get; set; } public string Path { get; set; } + public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent; } } \ No newline at end of file diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 1ece57e81d..3a61fbc996 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -76,6 +76,8 @@ + + diff --git a/src/Perspex.Base/PerspexObjectExtensions.cs b/src/Perspex.Base/PerspexObjectExtensions.cs index c958ecbffc..6cd75b691d 100644 --- a/src/Perspex.Base/PerspexObjectExtensions.cs +++ b/src/Perspex.Base/PerspexObjectExtensions.cs @@ -121,7 +121,7 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { // TODO: Subject.Create is not yet in stable Rx : once it is, remove the - // AnonymousSubject classes from this file and use Subject.Create. + // AnonymousSubject classes and use Subject.Create. var output = new Subject(); var result = new AnonymousSubject( Observer.Create( @@ -272,54 +272,5 @@ namespace Perspex handler(target)(e); } } - - class AnonymousSubject : ISubject - { - private readonly IObserver _observer; - private readonly IObservable _observable; - - public AnonymousSubject(IObserver observer, IObservable observable) - { - _observer = observer; - _observable = observable; - } - - public void OnCompleted() - { - _observer.OnCompleted(); - } - - public void OnError(Exception error) - { - if (error == null) - throw new ArgumentNullException("error"); - - _observer.OnError(error); - } - - public void OnNext(T value) - { - _observer.OnNext(value); - } - - public IDisposable Subscribe(IObserver observer) - { - if (observer == null) - throw new ArgumentNullException("observer"); - - // - // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence. - // - return _observable.Subscribe/*Unsafe*/(observer); - } - } - - class AnonymousSubject : AnonymousSubject, ISubject - { - public AnonymousSubject(IObserver observer, IObservable observable) - : base(observer, observable) - { - } - } } } diff --git a/src/Perspex.Base/Reactive/AnonymousSubject`1.cs b/src/Perspex.Base/Reactive/AnonymousSubject`1.cs new file mode 100644 index 0000000000..3bbf2295ff --- /dev/null +++ b/src/Perspex.Base/Reactive/AnonymousSubject`1.cs @@ -0,0 +1,16 @@ +// 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.Reactive +{ + public class AnonymousSubject : AnonymousSubject, ISubject + { + public AnonymousSubject(IObserver observer, IObservable observable) + : base(observer, observable) + { + } + } +} diff --git a/src/Perspex.Base/Reactive/AnonymousSubject`2.cs b/src/Perspex.Base/Reactive/AnonymousSubject`2.cs new file mode 100644 index 0000000000..04e58585b3 --- /dev/null +++ b/src/Perspex.Base/Reactive/AnonymousSubject`2.cs @@ -0,0 +1,49 @@ +// 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.Reactive +{ + public class AnonymousSubject : ISubject + { + private readonly IObserver _observer; + private readonly IObservable _observable; + + public AnonymousSubject(IObserver observer, IObservable observable) + { + _observer = observer; + _observable = observable; + } + + public void OnCompleted() + { + _observer.OnCompleted(); + } + + public void OnError(Exception error) + { + if (error == null) + throw new ArgumentNullException("error"); + + _observer.OnError(error); + } + + public void OnNext(T value) + { + _observer.OnNext(value); + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + throw new ArgumentNullException("observer"); + + // + // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence. + // + return _observable.Subscribe/*Unsafe*/(observer); + } + } +} diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index e630924a97..9feaab3d25 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -77,7 +77,6 @@ namespace Perspex.Controls.Generators result.SetValue(ContentProperty, template.Build(item)); result.SetValue(ItemsProperty, template.ItemsSelector(item)); - result.SetValue(IsExpandedProperty, template.IsExpanded(item)); if (!(item is IControl)) { diff --git a/src/Perspex.Controls/Primitives/ToggleButton.cs b/src/Perspex.Controls/Primitives/ToggleButton.cs index 601792ba1f..744af915c0 100644 --- a/src/Perspex.Controls/Primitives/ToggleButton.cs +++ b/src/Perspex.Controls/Primitives/ToggleButton.cs @@ -9,21 +9,22 @@ namespace Perspex.Controls.Primitives public class ToggleButton : Button { public static readonly PerspexProperty IsCheckedProperty = - PerspexProperty.Register("IsChecked"); + PerspexProperty.RegisterDirect( + "IsChecked", + o => o.IsChecked, + (o,v) => o.IsChecked = v); + + private bool _isChecked; static ToggleButton() { PseudoClass(IsCheckedProperty, ":checked"); } - public ToggleButton() - { - } - public bool IsChecked { - get { return GetValue(IsCheckedProperty); } - set { SetValue(IsCheckedProperty, value); } + get { return _isChecked; } + set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); } } protected override void OnClick(RoutedEventArgs e) diff --git a/src/Perspex.Styling/Styling/ActivatedSubject.cs b/src/Perspex.Styling/Styling/ActivatedSubject.cs index 99da010dae..3ccbfac6c0 100644 --- a/src/Perspex.Styling/Styling/ActivatedSubject.cs +++ b/src/Perspex.Styling/Styling/ActivatedSubject.cs @@ -19,7 +19,8 @@ namespace Perspex.Styling /// internal class ActivatedSubject : ActivatedObservable, ISubject, IDescription { - private bool _active; + private bool? _active; + private bool _completed; private object _value; /// @@ -34,7 +35,7 @@ namespace Perspex.Styling string description) : base(activator, source, description) { - Activator.Skip(1).Subscribe(ActivatorChanged); + Activator.Subscribe(ActivatorChanged, ActivatorError, ActivatorCompleted); } /// @@ -50,7 +51,7 @@ namespace Perspex.Styling /// public void OnCompleted() { - if (_active) + if (_active.Value && !_completed) { Source.OnCompleted(); } @@ -63,7 +64,7 @@ namespace Perspex.Styling /// is null. public void OnError(Exception error) { - if (_active) + if (_active.Value && !_completed) { Source.OnError(error); } @@ -77,7 +78,7 @@ namespace Perspex.Styling { _value = value; - if (_active) + if (_active.Value && !_completed) { Source.OnNext(value); } @@ -85,8 +86,26 @@ namespace Perspex.Styling private void ActivatorChanged(bool active) { + bool first = !_active.HasValue; + _active = active; - Source.OnNext(active ? _value : PerspexProperty.UnsetValue); + + if (!first) + { + Source.OnNext(active ? _value : PerspexProperty.UnsetValue); + } + } + + private void ActivatorCompleted() + { + _completed = true; + Source.OnCompleted(); + } + + private void ActivatorError(Exception e) + { + _completed = true; + Source.OnError(e); } } } diff --git a/src/Perspex.Styling/Styling/Setter.cs b/src/Perspex.Styling/Styling/Setter.cs index 103ebb6548..b224382f4a 100644 --- a/src/Perspex.Styling/Styling/Setter.cs +++ b/src/Perspex.Styling/Styling/Setter.cs @@ -5,6 +5,7 @@ using System; using System.Reactive.Subjects; using Perspex.Data; using Perspex.Metadata; +using Perspex.Reactive; namespace Perspex.Styling { @@ -63,6 +64,10 @@ namespace Perspex.Styling /// An optional activator. public void Apply(IStyle style, IStyleable control, IObservable activator) { + Contract.Requires(control != null); + + var description = style?.ToString(); + if (Property == null) { throw new InvalidOperationException("Setter.Property must be set."); @@ -79,7 +84,7 @@ namespace Perspex.Styling else { var subject = binding.CreateSubject(control, Property); - var activated = new ActivatedSubject(activator, subject, style.ToString()); + var activated = new ActivatedSubject(activator, subject, description); Bind(control, Property, binding, activated); } } @@ -91,7 +96,7 @@ namespace Perspex.Styling } else { - var activated = new ActivatedValue(activator, Value, style.ToString()); + var activated = new ActivatedValue(activator, Value, description); control.Bind(Property, activated, BindingPriority.StyleTrigger); } } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs index 63c8751d82..a496f26fc5 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs @@ -2,7 +2,11 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Linq; +using System.Reactive.Linq; using Moq; +using Perspex.Controls; +using Perspex.Controls.Primitives; +using Perspex.Data; using Perspex.Markup.Xaml.Data; using Perspex.Platform; using Perspex.Styling; @@ -29,5 +33,88 @@ namespace Perspex.Markup.Xaml.UnitTests Assert.IsType(setter.Value); } } + + [Fact] + public void Setter_With_TwoWay_Binding_Should_Update_Source() + { + using (PerspexLocator.EnterScope()) + { + PerspexLocator.CurrentMutable + .Bind() + .ToConstant(Mock.Of(x => + x.CurrentThreadIsLoopThread == true)); + + var data = new Data + { + Foo = "foo", + }; + + var control = new TextBox + { + DataContext = data, + }; + + var setter = new Setter + { + Property = TextBox.TextProperty, + Value = new Binding + { + Path = "Foo", + Mode = BindingMode.TwoWay + } + }; + + setter.Apply(null, control, null); + Assert.Equal("foo", control.Text); + + control.Text = "bar"; + Assert.Equal("bar", data.Foo); + } + } + + [Fact] + public void Setter_With_TwoWay_Binding_And_Activator_Should_Update_Source() + { + using (PerspexLocator.EnterScope()) + { + PerspexLocator.CurrentMutable + .Bind() + .ToConstant(Mock.Of(x => + x.CurrentThreadIsLoopThread == true)); + + var data = new Data + { + Foo = "foo", + }; + + var control = new TextBox + { + DataContext = data, + }; + + var setter = new Setter + { + Property = TextBox.TextProperty, + Value = new Binding + { + Path = "Foo", + Mode = BindingMode.TwoWay + } + }; + + var activator = Observable.Never().StartWith(true); + + setter.Apply(null, control, activator); + Assert.Equal("foo", control.Text); + + control.Text = "bar"; + Assert.Equal("bar", data.Foo); + } + } + + private class Data + { + public string Foo { get; set; } + } } } diff --git a/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs index d4654dd199..58b7d9fd46 100644 --- a/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs +++ b/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs @@ -1,8 +1,9 @@ // 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.Disposables; using System.Reactive.Subjects; -using Perspex.Data; using Xunit; namespace Perspex.Styling.UnitTests @@ -12,23 +13,44 @@ namespace Perspex.Styling.UnitTests [Fact] public void Should_Set_Values() { - var data = new Class1 { Foo = "foo" }; var activator = new BehaviorSubject(false); - var source = data.GetSubject( - (PerspexProperty)Class1.FooProperty, - BindingPriority.LocalValue); + var source = new TestSubject(); var target = new ActivatedSubject(activator, source, string.Empty); target.OnNext("bar"); - Assert.Equal("foo", data.Foo); + Assert.Equal(PerspexProperty.UnsetValue, source.Value); activator.OnNext(true); target.OnNext("baz"); - Assert.Equal("baz", data.Foo); + Assert.Equal("baz", source.Value); activator.OnNext(false); - Assert.Equal("foo", data.Foo); + Assert.Equal(PerspexProperty.UnsetValue, source.Value); target.OnNext("bax"); activator.OnNext(true); - Assert.Equal("bax", data.Foo); + Assert.Equal("bax", source.Value); + } + + [Fact] + public void Should_Invoke_OnCompleted_On_Activator_Completed() + { + var activator = new BehaviorSubject(false); + var source = new TestSubject(); + var target = new ActivatedSubject(activator, source, string.Empty); + + activator.OnCompleted(); + + Assert.True(source.Completed); + } + + [Fact] + public void Should_Invoke_OnError_On_Activator_Error() + { + var activator = new BehaviorSubject(false); + var source = new TestSubject(); + var target = new ActivatedSubject(activator, source, string.Empty); + + activator.OnError(new Exception()); + + Assert.NotNull(source.Error); } private class Class1 : PerspexObject @@ -42,5 +64,35 @@ namespace Perspex.Styling.UnitTests set { SetValue(FooProperty, value); } } } + + private class TestSubject : ISubject + { + private IObserver _observer; + + public bool Completed { get; set; } + public Exception Error { get; set; } + public object Value { get; set; } = PerspexProperty.UnsetValue; + + public void OnCompleted() + { + Completed = true; + } + + public void OnError(Exception error) + { + Error = error; + } + + public void OnNext(object value) + { + Value = value; + } + + public IDisposable Subscribe(IObserver observer) + { + _observer = observer; + return Disposable.Empty; + } + } } } From bf30371721a37366356cebf4394d2dcdeedb8d5f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Jan 2016 18:25:08 +0100 Subject: [PATCH 09/10] Removed ITreeDataTemplate.IsExpanded. --- samples/TestApplicationShared/App.cs | 5 +- .../Templates/FuncTreeDataTemplate.cs | 63 ------------------- .../Templates/FuncTreeDataTemplate`1.cs | 55 ---------------- .../Templates/ITreeDataTemplate.cs | 7 --- tests/Perspex.LeakTests/ControlTests.cs | 3 +- 5 files changed, 2 insertions(+), 131 deletions(-) diff --git a/samples/TestApplicationShared/App.cs b/samples/TestApplicationShared/App.cs index f8b64146d4..6429b76b32 100644 --- a/samples/TestApplicationShared/App.cs +++ b/samples/TestApplicationShared/App.cs @@ -21,11 +21,8 @@ namespace TestApplication { new FuncTreeDataTemplate( x => new TextBlock {Text = x.Name}, - x => x.Children, - x => true), + x => x.Children), }; } - - } } diff --git a/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs index d7ed589135..3d17cf5659 100644 --- a/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs @@ -14,8 +14,6 @@ namespace Perspex.Controls.Templates { private readonly Func _itemsSelector; - private readonly Func _isExpanded; - /// /// Initializes a new instance of the class. /// @@ -35,30 +33,6 @@ namespace Perspex.Controls.Templates { } - /// - /// Initializes a new instance of the class. - /// - /// The type of data which the data template matches. - /// - /// A function which when passed an object of returns a control. - /// - /// - /// A function which when passed an object of returns the child - /// items. - /// - /// - /// A function which when passed an object of returns the the - /// initial expanded state of the node. - /// - public FuncTreeDataTemplate( - Type type, - Func build, - Func itemsSelector, - Func isExpanded) - : this(o => IsInstance(o, type), build, itemsSelector, isExpanded) - { - } - /// /// Initializes a new instance of the class. /// @@ -75,46 +49,9 @@ namespace Perspex.Controls.Templates Func match, Func build, Func itemsSelector) - : this(match, build, itemsSelector, _ => false) - { - _itemsSelector = itemsSelector; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A function which determines whether the data template matches the specified data. - /// - /// - /// A function which when passed a matching object returns a control. - /// - /// - /// A function which when passed a matching object returns the child items. - /// - /// - /// A function which when passed a matching object returns the the initial expanded state - /// of the node. - /// - public FuncTreeDataTemplate( - Func match, - Func build, - Func itemsSelector, - Func isExpanded) : base(match, build) { _itemsSelector = itemsSelector; - _isExpanded = isExpanded; - } - - /// - /// Checks to see if the item should be initially expanded. - /// - /// The item. - /// True if the item should be initially expanded, otherwise false. - public bool IsExpanded(object item) - { - return this?._isExpanded(item) ?? false; } /// diff --git a/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs index d55cfcf095..481eb673d7 100644 --- a/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs @@ -32,32 +32,6 @@ namespace Perspex.Controls.Templates { } - /// - /// Initializes a new instance of the class. - /// - /// - /// A function which when passed an object of returns a control. - /// - /// - /// A function which when passed an object of returns the child - /// items. - /// - /// - /// A function which when passed an object of returns the the - /// initial expanded state of the node. - /// - public FuncTreeDataTemplate( - Func build, - Func itemsSelector, - Func isExpanded) - : base( - typeof(T), - Cast(build), - Cast(itemsSelector), - Cast(isExpanded)) - { - } - /// /// Initializes a new instance of the class. /// @@ -81,35 +55,6 @@ namespace Perspex.Controls.Templates { } - /// - /// Initializes a new instance of the class. - /// - /// - /// A function which determines whether the data template matches the specified data. - /// - /// - /// A function which when passed a matching object returns a control. - /// - /// - /// A function which when passed a matching object returns the child items. - /// - /// - /// A function which when passed a matching object returns the the initial expanded state - /// of the node. - /// - public FuncTreeDataTemplate( - Func match, - Func build, - Func itemsSelector, - Func isExpanded) - : base( - CastMatch(match), - Cast(build), - Cast(itemsSelector), - Cast(isExpanded)) - { - } - /// /// Casts a typed match function to an untyped match function. /// diff --git a/src/Perspex.Controls/Templates/ITreeDataTemplate.cs b/src/Perspex.Controls/Templates/ITreeDataTemplate.cs index ca967f60a4..6415ed37b2 100644 --- a/src/Perspex.Controls/Templates/ITreeDataTemplate.cs +++ b/src/Perspex.Controls/Templates/ITreeDataTemplate.cs @@ -10,13 +10,6 @@ namespace Perspex.Controls.Templates /// public interface ITreeDataTemplate : IDataTemplate { - /// - /// Checks to see if the item should be initially expanded. - /// - /// The item. - /// True if the item should be initially expanded, otherwise false. - bool IsExpanded(object item); - /// /// Selects the child items of an item. /// diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index a6275f452c..24bcda978b 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -273,8 +273,7 @@ namespace Perspex.LeakTests { new FuncTreeDataTemplate( x => new TextBlock { Text = x.Name }, - x => x.Children, - x => true) + x => x.Children) }, Items = nodes } From e08b9e3e4b28e017974ed0d40f5562aa1cc4fc47 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Jan 2016 18:48:19 +0100 Subject: [PATCH 10/10] Removed PerspexObject.BindTwoWay. Instead use subjects and Bind. --- src/Perspex.Base/PerspexObject.cs | 64 +------------------ .../PerspexObjectTests_Binding.cs | 30 +-------- 2 files changed, 3 insertions(+), 91 deletions(-) diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 5c4271a57d..5310651514 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -185,7 +185,8 @@ namespace Perspex sourceBinding.Source.Bind(sourceBinding.Property, this.GetObservable(binding.Property), binding.Priority); break; case BindingMode.TwoWay: - BindTwoWay(binding.Property, sourceBinding.Source, sourceBinding.Property); + var subject = sourceBinding.Source.GetSubject(sourceBinding.Property, sourceBinding.Priority); + this.Bind(binding.Property, subject, BindingMode.TwoWay, sourceBinding.Priority); break; } } @@ -480,67 +481,6 @@ namespace Perspex } } - /// - /// Initiates a two-way binding between s. - /// - /// The property on this object. - /// The source object. - /// The property on the source object. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - /// - /// The binding is first carried out from to this. - /// - public IDisposable BindTwoWay( - PerspexProperty property, - PerspexObject source, - PerspexProperty sourceProperty, - BindingPriority priority = BindingPriority.LocalValue) - { - VerifyAccess(); - _propertyLog.Verbose( - "Bound two way {Property} to {Binding} with priority {Priority}", - property, - source, - priority); - - return new CompositeDisposable( - Bind(property, source.GetObservable(sourceProperty)), - source.Bind(sourceProperty, this.GetObservable(property))); - } - - /// - /// Initiates a two-way binding between a and an - /// . - /// - /// The property on this object. - /// The subject to bind to. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - /// - /// The binding is first carried out from to this. - /// - public IDisposable BindTwoWay( - PerspexProperty property, - ISubject source, - BindingPriority priority = BindingPriority.LocalValue) - { - VerifyAccess(); - _propertyLog.Verbose( - "Bound two way {Property} to {Binding} with priority {Priority}", - property, - GetDescription(source), - priority); - - return new CompositeDisposable( - Bind(property, source), - this.GetObservable(property).Subscribe(source)); - } - /// /// Forces the specified property to be revalidated. /// diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs index 6788e509e2..3e7ee6f86d 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs @@ -82,7 +82,7 @@ namespace Perspex.Base.UnitTests } [Fact] - public void Two_Way_Binding_Works() + public void Two_Way_Separate_Binding_Works() { Class1 obj1 = new Class1(); Class1 obj2 = new Class1(); @@ -143,34 +143,6 @@ namespace Perspex.Base.UnitTests Assert.Equal("third", obj2.GetValue(Class1.FooProperty)); } - [Fact] - public void BindTwoWay_Gets_Initial_Value_From_Source() - { - Class1 source = new Class1(); - Class1 target = new Class1(); - - source.SetValue(Class1.FooProperty, "initial"); - target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty); - - Assert.Equal("initial", target.GetValue(Class1.FooProperty)); - } - - [Fact] - public void BindTwoWay_Updates_Values() - { - Class1 source = new Class1(); - Class1 target = new Class1(); - - source.SetValue(Class1.FooProperty, "first"); - target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty); - - Assert.Equal("first", target.GetValue(Class1.FooProperty)); - source.SetValue(Class1.FooProperty, "second"); - Assert.Equal("second", target.GetValue(Class1.FooProperty)); - target.SetValue(Class1.FooProperty, "third"); - Assert.Equal("third", source.GetValue(Class1.FooProperty)); - } - [Fact] public void Local_Binding_Overwrites_Local_Value() {