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/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 00ed242855..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,48 +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(IObservablePropertyBag instance, PerspexProperty property) - { - Contract.Requires(instance != null); - Contract.Requires(property != null); - - var subject = CreateSubject( - instance, - property.PropertyType, - property == Control.DataContextProperty); - - if (subject != null) - { - Bind(instance, property, subject); - } - } - /// /// 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( - IObservablePropertyBag 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) { @@ -120,47 +96,11 @@ namespace Perspex.Markup.Xaml.Data return new ExpressionSubject( observer, - targetType, + targetProperty?.PropertyType ?? typeof(object), Converter ?? DefaultValueConverter.Instance, 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 +149,7 @@ namespace Perspex.Markup.Xaml.Data } private ExpressionObserver CreateDataContextSubject( - IObservablePropertyBag target, + IPerspexObject target, string path, bool targetIsDataContext) { @@ -231,7 +171,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 +179,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 deleted file mode 100644 index e72fb5eccc..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(IObservablePropertyBag 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( - IObservablePropertyBag 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 4ea1feb9a2..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. @@ -48,9 +49,9 @@ 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); + 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( - IObservablePropertyBag 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); @@ -91,7 +87,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/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/DataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs index 37a8f338d5..96e38fd292 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs @@ -20,10 +20,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.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo()); } public IControl Build(object data) 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 85db3e71b7..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 @@ -96,7 +97,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 +120,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/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/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 56% rename from src/Perspex.Base/IPropertyBag.cs rename to src/Perspex.Base/IPerspexObject.cs index fe29c349ef..4290bfe792 100644 --- a/src/Perspex.Base/IPropertyBag.cs +++ b/src/Perspex.Base/IPerspexObject.cs @@ -1,23 +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; +using Perspex.Data; + 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 +31,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 +44,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 +56,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..1ece57e81d 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -42,11 +42,13 @@ Properties\SharedAssemblyInfo.cs - + + + + - - + @@ -56,7 +58,7 @@ - + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index b95758a7e7..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; @@ -23,7 +22,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 +88,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. @@ -159,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 { @@ -171,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) { @@ -188,7 +182,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); @@ -197,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, @@ -223,81 +217,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 +508,7 @@ namespace Perspex return new CompositeDisposable( Bind(property, source.GetObservable(sourceProperty)), - source.Bind(sourceProperty, GetObservable(property))); + source.Bind(sourceProperty, this.GetObservable(property))); } /// @@ -619,7 +538,7 @@ namespace Perspex return new CompositeDisposable( Bind(property, source), - GetObservable(property).Subscribe(source)); + this.GetObservable(property).Subscribe(source)); } /// @@ -638,11 +557,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..d7b2d5a748 100644 --- a/src/Perspex.Base/PerspexObjectExtensions.cs +++ b/src/Perspex.Base/PerspexObjectExtensions.cs @@ -2,8 +2,12 @@ // 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; +using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Reactive.Subjects; +using Perspex.Data; +using Perspex.Reactive; namespace Perspex { @@ -12,6 +16,162 @@ 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)); + } + + /// + /// 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 . + /// + /// The object. + /// The property to bind. + /// 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, + 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 +212,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 . @@ -71,5 +242,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/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/DropDown.cs b/src/Perspex.Controls/DropDown.cs index 27cf8209d4..8742016d9c 100644 --- a/src/Perspex.Controls/DropDown.cs +++ b/src/Perspex.Controls/DropDown.cs @@ -16,81 +16,68 @@ namespace Perspex.Controls /// /// A drop-down list control. /// - public class DropDown : SelectingItemsControl, IContentControl + public class DropDown : SelectingItemsControl { - public static readonly PerspexProperty ContentProperty = - ContentControl.ContentProperty.AddOwner(); - - public static readonly PerspexProperty HorizontalContentAlignmentProperty = - ContentControl.HorizontalContentAlignmentProperty.AddOwner(); - - public static readonly PerspexProperty VerticalContentAlignmentProperty = - ContentControl.VerticalContentAlignmentProperty.AddOwner(); - + /// + /// Defines the property. + /// public static readonly PerspexProperty IsDropDownOpenProperty = PerspexProperty.RegisterDirect( nameof(IsDropDownOpen), o => o.IsDropDownOpen, (o, v) => o.IsDropDownOpen = v); + /// + /// Defines the property. + /// public static readonly PerspexProperty SelectionBoxItemProperty = - PerspexProperty.Register("SelectionBoxItem"); + PerspexProperty.RegisterDirect("SelectionBoxItem", o => o.SelectionBoxItem); private bool _isDropDownOpen; private Popup _popup; + private object _selectionBoxItem; + /// + /// Initializes static members of the class. + /// static DropDown() { FocusableProperty.OverrideDefaultValue(true); SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); } - public DropDown() - { - Bind(ContentProperty, GetObservable(SelectedItemProperty)); - } - - public object Content - { - get { return GetValue(ContentProperty); } - set { SetValue(ContentProperty, value); } - } - - public HorizontalAlignment HorizontalContentAlignment - { - get { return GetValue(HorizontalContentAlignmentProperty); } - set { SetValue(HorizontalContentAlignmentProperty, value); } - } - - public VerticalAlignment VerticalContentAlignment - { - get { return GetValue(VerticalContentAlignmentProperty); } - set { SetValue(VerticalContentAlignmentProperty, value); } - } - + /// + /// Gets or sets a value indicating whether the dropdown is currently open. + /// public bool IsDropDownOpen { get { return _isDropDownOpen; } set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); } } + /// + /// Gets or sets the item to display as the control's content. + /// protected object SelectionBoxItem { - get { return GetValue(SelectionBoxItemProperty); } - set { SetValue(SelectionBoxItemProperty, value); } + get { return _selectionBoxItem; } + set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } } + /// protected override IItemContainerGenerator CreateItemContainerGenerator() { return new ItemContainerGenerator(this, DropDownItem.ContentProperty); } + /// protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); this.UpdateSelectionBoxItem(this.SelectedItem); } + /// protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); @@ -111,6 +98,7 @@ namespace Perspex.Controls } } + /// protected override void OnPointerPressed(PointerPressEventArgs e) { if (!IsDropDownOpen && ((IVisual)e.Source).GetVisualRoot() != typeof(PopupRoot)) @@ -131,6 +119,7 @@ namespace Perspex.Controls base.OnPointerPressed(e); } + /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { if (_popup != null) 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..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 { @@ -36,10 +37,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/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 b6ce37fa88..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,14 +225,14 @@ namespace Perspex.Controls.Primitives } } - protected sealed override BindingDescriptor CreateBindingDescriptor(BindingDescriptor source) + protected sealed override IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source) { var result = base.CreateBindingDescriptor(source); // 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..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; @@ -106,10 +107,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..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 { @@ -73,7 +74,7 @@ namespace Perspex.Controls public TextBox() { - var canScrollHorizontally = GetObservable(AcceptsReturnProperty) + var canScrollHorizontally = this.GetObservable(AcceptsReturnProperty) .Select(x => !x); Bind( @@ -81,7 +82,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/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/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/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.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.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/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/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/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/src/Perspex.Styling/Styling/Setter.cs b/src/Perspex.Styling/Styling/Setter.cs index 88b7ac9cbc..e93729cf6e 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; @@ -60,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/src/Perspex.Themes.Default/DropDown.paml b/src/Perspex.Themes.Default/DropDown.paml index 2d65df2c29..d1646ac21c 100644 --- a/src/Perspex.Themes.Default/DropDown.paml +++ b/src/Perspex.Themes.Default/DropDown.paml @@ -2,8 +2,6 @@ "; + 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/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/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index 8a579223b3..427a806376 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs @@ -6,11 +6,9 @@ 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 Perspex.Data; using Xunit; namespace Perspex.Styling.UnitTests @@ -80,6 +78,8 @@ namespace Perspex.Styling.UnitTests Classes = new Classes(); } + public event EventHandler PropertyChanged; + public Classes Classes { get; } public string Name { get; set; } @@ -96,42 +96,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 +132,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..fab2f15a97 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -5,11 +5,10 @@ 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 Perspex.Data; using Xunit; namespace Perspex.Styling.UnitTests @@ -110,6 +109,8 @@ namespace Perspex.Styling.UnitTests Classes = new Classes(); } + public event EventHandler PropertyChanged; + public Classes Classes { get; } public string Name { get; set; } @@ -124,44 +125,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 +163,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/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); + } + } +} 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 82ab942a09..3a0f0e5434 100644 --- a/tests/Perspex.Styling.UnitTests/TestControlBase.cs +++ b/tests/Perspex.Styling.UnitTests/TestControlBase.cs @@ -3,9 +3,9 @@ using System; using System.Reactive; -using System.Reactive.Subjects; using Perspex.Collections; using Perspex.Controls; +using Perspex.Data; namespace Perspex.Styling.UnitTests { @@ -17,6 +17,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 +33,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..847a124066 100644 --- a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs +++ b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs @@ -2,16 +2,17 @@ // 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; +using Perspex.Data; namespace Perspex.Styling.UnitTests { public abstract class TestTemplatedControl : ITemplatedControl, IStyleable { + public event EventHandler PropertyChanged; + public abstract Classes Classes { get; @@ -32,29 +33,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 +52,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 +67,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(); }