From 1841e7ad7a642e3d23cf2a0605b949181da31514 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 18 Jan 2016 15:46:41 +0100 Subject: [PATCH] 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 {