diff --git a/samples/TestApplicationShared/App.cs b/samples/TestApplicationShared/App.cs index f8b64146d4..6429b76b32 100644 --- a/samples/TestApplicationShared/App.cs +++ b/samples/TestApplicationShared/App.cs @@ -21,11 +21,8 @@ namespace TestApplication { new FuncTreeDataTemplate( x => new TextBlock {Text = x.Name}, - x => x.Children, - x => true), + x => x.Children), }; } - - } } diff --git a/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 534626027c..0a6ebeae2c 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/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs b/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs index 1523299d41..9ac24a622e 100644 --- a/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs +++ b/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs @@ -1,7 +1,9 @@ // Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Collections.Generic; +using ReactiveUI; namespace XamlTestApplication.ViewModels { @@ -22,6 +24,7 @@ namespace XamlTestApplication.ViewModels { Header = "Root", SubHeader = "Root Item", + IsExpanded = true, Children = new[] { new TestNode @@ -33,6 +36,7 @@ namespace XamlTestApplication.ViewModels { Header = "Child 2", SubHeader = "Child 2 Value", + IsExpanded = false, Children = new[] { new TestNode @@ -50,9 +54,39 @@ namespace XamlTestApplication.ViewModels } } }; + + CollapseNodesCommand = ReactiveCommand.Create(); + CollapseNodesCommand.Subscribe(_ => ExpandNodes(false)); + ExpandNodesCommand = ReactiveCommand.Create(); + ExpandNodesCommand.Subscribe(_ => ExpandNodes(true)); } public List Items { get; } public List Nodes { get; } + + public ReactiveCommand CollapseNodesCommand { get; } + + public ReactiveCommand ExpandNodesCommand { get; } + + public void ExpandNodes(bool expanded) + { + foreach (var node in Nodes) + { + ExpandNodes(node, expanded); + } + } + + private void ExpandNodes(TestNode node, bool expanded) + { + node.IsExpanded = expanded; + + if (node.Children != null) + { + foreach (var child in node.Children) + { + ExpandNodes(child, expanded); + } + } + } } } diff --git a/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs b/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs index 953bfd0f58..c2e0f4f15c 100644 --- a/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs +++ b/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs @@ -2,13 +2,22 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Collections.Generic; +using ReactiveUI; namespace XamlTestApplication.ViewModels { - public class TestNode + public class TestNode : ReactiveObject { + private bool _isExpanded; + public string Header { get; set; } public string SubHeader { get; set; } public IEnumerable Children { get; set; } + + public bool IsExpanded + { + get { return _isExpanded; } + set { this.RaiseAndSetIfChanged(ref this._isExpanded, value); } + } } } \ No newline at end of file diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml index 98633354b1..584c43c3af 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml @@ -147,6 +147,11 @@ + + + @@ -156,6 +161,10 @@ + + + + diff --git a/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj b/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj index a32ae5eec6..c7fb0e8484 100644 --- a/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj +++ b/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj @@ -123,6 +123,22 @@ ..\..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll True + + ..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll + True + diff --git a/samples/XamlTestApplicationPcl/packages.config b/samples/XamlTestApplicationPcl/packages.config index 2115574cc5..749731a770 100644 --- a/samples/XamlTestApplicationPcl/packages.config +++ b/samples/XamlTestApplicationPcl/packages.config @@ -1,4 +1,9 @@  + + + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/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..8a254cbf6e 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,66 +51,41 @@ 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) { - observer = CreateElementSubject( + observer = CreateElementObserver( (IControl)target, pathInfo.ElementName ?? ElementName, pathInfo.Path); } else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext) { - observer = CreateDataContextSubject( + observer = CreateDataContexObserver( target, pathInfo.Path, targetIsDataContext); } else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent) { - observer = CreateTemplatedParentSubject( + observer = CreateTemplatedParentObserver( target, pathInfo.Path); } @@ -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(); @@ -208,8 +148,8 @@ namespace Perspex.Markup.Xaml.Data } } - private ExpressionObserver CreateDataContextSubject( - IObservablePropertyBag target, + private ExpressionObserver CreateDataContexObserver( + IPerspexObject target, string path, bool targetIsDataContext) { @@ -231,15 +171,15 @@ namespace Perspex.Markup.Xaml.Data { return new ExpressionObserver( target.GetObservable(Visual.VisualParentProperty) - .OfType() + .OfType() .Select(x => x.GetObservable(Control.DataContextProperty)) .Switch(), path); } } - private ExpressionObserver CreateTemplatedParentSubject( - IObservablePropertyBag target, + private ExpressionObserver CreateTemplatedParentObserver( + IPerspexObject target, string path) { Contract.Requires(target != null); @@ -256,7 +196,7 @@ namespace Perspex.Markup.Xaml.Data return result; } - private ExpressionObserver CreateElementSubject( + private ExpressionObserver CreateElementObserver( IControl target, string elementName, string path) diff --git a/src/Markup/Perspex.Markup.Xaml/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..d8a668da08 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 @@ -26,6 +27,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions ElementName = ElementName, Mode = Mode, Path = Path, + Priority = Priority, }; } @@ -34,5 +36,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions public string ElementName { get; set; } public BindingMode Mode { get; set; } public string Path { get; set; } + public BindingPriority Priority { get; set; } = BindingPriority.LocalValue; } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs index b3bd1c7c75..cec5146c71 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 @@ -24,9 +25,9 @@ namespace Perspex.Markup.Xaml.MarkupExtensions Converter = Converter, ElementName = ElementName, Mode = Mode, - Priority = BindingPriority.TemplatedParent, RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), Path = Path, + Priority = Priority, }; } @@ -34,5 +35,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions public string ElementName { get; set; } public BindingMode Mode { get; set; } public string Path { get; set; } + public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent; } } \ No newline at end of file diff --git a/src/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..ceabd09bb7 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -4,8 +4,10 @@ using System; using System.Collections; using System.Reactive.Linq; +using System.Reflection; using Perspex.Controls; using Perspex.Controls.Templates; +using Perspex.Data; using Perspex.Markup.Data; using Perspex.Markup.Xaml.Data; using Perspex.Metadata; @@ -19,16 +21,19 @@ namespace Perspex.Markup.Xaml.Templates [Content] public TemplateContent Content { get; set; } + [AssignBinding] public Binding ItemsSource { get; set; } public bool Match(object data) { if (DataType == null) { - throw new InvalidOperationException("DataTemplate must have a DataType."); + return true; + } + else + { + return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo()); } - - return DataType == data.GetType(); } public IEnumerable ItemsSelector(object item) 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..3a61fbc996 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 @@ - + @@ -74,6 +76,8 @@ + + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index b95758a7e7..5310651514 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,18 +182,19 @@ 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); + var subject = sourceBinding.Source.GetSubject(sourceBinding.Property, sourceBinding.Priority); + this.Bind(binding.Property, subject, BindingMode.TwoWay, sourceBinding.Priority); break; } } } - 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 +218,6 @@ namespace Perspex SetValue(property, PerspexProperty.UnsetValue); } - /// - /// Gets an observable for a . - /// - /// The property. - /// An observable. - public IObservable GetObservable(PerspexProperty property) - { - Contract.Requires(property != null); - - return new PerspexObservable( - observer => - { - EventHandler handler = (s, e) => - { - if (e.Property == property) - { - observer.OnNext(e.NewValue); - } - }; - - observer.OnNext(GetValue(property)); - - PropertyChanged += handler; - - return Disposable.Create(() => - { - PropertyChanged -= handler; - }); - }, - GetDescription(property)); - } - - /// - /// Gets an observable for a . - /// - /// The property type. - /// The property. - /// An observable. - public IObservable GetObservable(PerspexProperty property) - { - Contract.Requires(property != null); - - return GetObservable((PerspexProperty)property).Cast(); - } - - /// - /// Gets an observable for a . - /// - /// The type of the property. - /// The property. - /// An observable which when subscribed pushes the old and new values of the - /// property each time it is changed. - public IObservable> GetObservableWithHistory(PerspexProperty property) - { - return new PerspexObservable>( - observer => - { - EventHandler handler = (s, e) => - { - if (e.Property == property) - { - observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue)); - } - }; - - PropertyChanged += handler; - - return Disposable.Create(() => - { - PropertyChanged -= handler; - }); - }, - GetDescription(property)); - } - /// /// Gets a value. /// @@ -561,67 +481,6 @@ namespace Perspex } } - /// - /// Initiates a two-way binding between s. - /// - /// The property on this object. - /// The source object. - /// The property on the source object. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - /// - /// The binding is first carried out from to this. - /// - public IDisposable BindTwoWay( - PerspexProperty property, - PerspexObject source, - PerspexProperty sourceProperty, - BindingPriority priority = BindingPriority.LocalValue) - { - VerifyAccess(); - _propertyLog.Verbose( - "Bound two way {Property} to {Binding} with priority {Priority}", - property, - source, - priority); - - return new CompositeDisposable( - Bind(property, source.GetObservable(sourceProperty)), - source.Bind(sourceProperty, GetObservable(property))); - } - - /// - /// Initiates a two-way binding between a and an - /// . - /// - /// The property on this object. - /// The subject to bind to. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - /// - /// The binding is first carried out from to this. - /// - public IDisposable BindTwoWay( - PerspexProperty property, - ISubject source, - BindingPriority priority = BindingPriority.LocalValue) - { - VerifyAccess(); - _propertyLog.Verbose( - "Bound two way {Property} to {Binding} with priority {Priority}", - property, - GetDescription(source), - priority); - - return new CompositeDisposable( - Bind(property, source), - GetObservable(property).Subscribe(source)); - } - /// /// Forces the specified property to be revalidated. /// @@ -638,11 +497,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..6cd75b691d 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,192 @@ 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 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 and use Subject.Create. + var output = new Subject(); + var result = new AnonymousSubject( + Observer.Create( + x => output.OnNext(x), + e => output.OnError(e), + () => output.OnCompleted()), + o.GetObservable(property)); + o.Bind(property, output, priority); + return result; + } + + /// + /// Gets a subject for a . + /// + /// 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 +242,17 @@ namespace Perspex return observable.Subscribe(e => SubscribeAdapter(e, handler)); } + /// + /// Gets a description of a property that van be used in observables. + /// + /// The object. + /// The property + /// The description. + private static string GetDescription(IPerspexObject o, PerspexProperty property) + { + return $"{o.GetType().Name}.{property.Name}"; + } + /// /// Observer method for . diff --git a/src/Perspex.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.Base/Reactive/AnonymousSubject`1.cs b/src/Perspex.Base/Reactive/AnonymousSubject`1.cs new file mode 100644 index 0000000000..3bbf2295ff --- /dev/null +++ b/src/Perspex.Base/Reactive/AnonymousSubject`1.cs @@ -0,0 +1,16 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive.Subjects; + +namespace Perspex.Reactive +{ + public class AnonymousSubject : AnonymousSubject, ISubject + { + public AnonymousSubject(IObserver observer, IObservable observable) + : base(observer, observable) + { + } + } +} diff --git a/src/Perspex.Base/Reactive/AnonymousSubject`2.cs b/src/Perspex.Base/Reactive/AnonymousSubject`2.cs new file mode 100644 index 0000000000..04e58585b3 --- /dev/null +++ b/src/Perspex.Base/Reactive/AnonymousSubject`2.cs @@ -0,0 +1,49 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive.Subjects; + +namespace Perspex.Reactive +{ + public class AnonymousSubject : ISubject + { + private readonly IObserver _observer; + private readonly IObservable _observable; + + public AnonymousSubject(IObserver observer, IObservable observable) + { + _observer = observer; + _observable = observable; + } + + public void OnCompleted() + { + _observer.OnCompleted(); + } + + public void OnError(Exception error) + { + if (error == null) + throw new ArgumentNullException("error"); + + _observer.OnError(error); + } + + public void OnNext(T value) + { + _observer.OnNext(value); + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + throw new ArgumentNullException("observer"); + + // + // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence. + // + return _observable.Subscribe/*Unsafe*/(observer); + } + } +} diff --git a/src/Perspex.Controls/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/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index e630924a97..9feaab3d25 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -77,7 +77,6 @@ namespace Perspex.Controls.Generators result.SetValue(ContentProperty, template.Build(item)); result.SetValue(ItemsProperty, template.ItemsSelector(item)); - result.SetValue(IsExpandedProperty, template.IsExpanded(item)); if (!(item is IControl)) { diff --git a/src/Perspex.Controls/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/ToggleButton.cs b/src/Perspex.Controls/Primitives/ToggleButton.cs index 601792ba1f..744af915c0 100644 --- a/src/Perspex.Controls/Primitives/ToggleButton.cs +++ b/src/Perspex.Controls/Primitives/ToggleButton.cs @@ -9,21 +9,22 @@ namespace Perspex.Controls.Primitives public class ToggleButton : Button { public static readonly PerspexProperty IsCheckedProperty = - PerspexProperty.Register("IsChecked"); + PerspexProperty.RegisterDirect( + "IsChecked", + o => o.IsChecked, + (o,v) => o.IsChecked = v); + + private bool _isChecked; static ToggleButton() { PseudoClass(IsCheckedProperty, ":checked"); } - public ToggleButton() - { - } - public bool IsChecked { - get { return GetValue(IsCheckedProperty); } - set { SetValue(IsCheckedProperty, value); } + get { return _isChecked; } + set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); } } protected override void OnClick(RoutedEventArgs e) diff --git a/src/Perspex.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/Templates/FuncTreeDataTemplate.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs index d7ed589135..3d17cf5659 100644 --- a/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs @@ -14,8 +14,6 @@ namespace Perspex.Controls.Templates { private readonly Func _itemsSelector; - private readonly Func _isExpanded; - /// /// Initializes a new instance of the class. /// @@ -35,30 +33,6 @@ namespace Perspex.Controls.Templates { } - /// - /// Initializes a new instance of the class. - /// - /// The type of data which the data template matches. - /// - /// A function which when passed an object of returns a control. - /// - /// - /// A function which when passed an object of returns the child - /// items. - /// - /// - /// A function which when passed an object of returns the the - /// initial expanded state of the node. - /// - public FuncTreeDataTemplate( - Type type, - Func build, - Func itemsSelector, - Func isExpanded) - : this(o => IsInstance(o, type), build, itemsSelector, isExpanded) - { - } - /// /// Initializes a new instance of the class. /// @@ -75,46 +49,9 @@ namespace Perspex.Controls.Templates Func match, Func build, Func itemsSelector) - : this(match, build, itemsSelector, _ => false) - { - _itemsSelector = itemsSelector; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A function which determines whether the data template matches the specified data. - /// - /// - /// A function which when passed a matching object returns a control. - /// - /// - /// A function which when passed a matching object returns the child items. - /// - /// - /// A function which when passed a matching object returns the the initial expanded state - /// of the node. - /// - public FuncTreeDataTemplate( - Func match, - Func build, - Func itemsSelector, - Func isExpanded) : base(match, build) { _itemsSelector = itemsSelector; - _isExpanded = isExpanded; - } - - /// - /// Checks to see if the item should be initially expanded. - /// - /// The item. - /// True if the item should be initially expanded, otherwise false. - public bool IsExpanded(object item) - { - return this?._isExpanded(item) ?? false; } /// diff --git a/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs index d55cfcf095..481eb673d7 100644 --- a/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs @@ -32,32 +32,6 @@ namespace Perspex.Controls.Templates { } - /// - /// Initializes a new instance of the class. - /// - /// - /// A function which when passed an object of returns a control. - /// - /// - /// A function which when passed an object of returns the child - /// items. - /// - /// - /// A function which when passed an object of returns the the - /// initial expanded state of the node. - /// - public FuncTreeDataTemplate( - Func build, - Func itemsSelector, - Func isExpanded) - : base( - typeof(T), - Cast(build), - Cast(itemsSelector), - Cast(isExpanded)) - { - } - /// /// Initializes a new instance of the class. /// @@ -81,35 +55,6 @@ namespace Perspex.Controls.Templates { } - /// - /// Initializes a new instance of the class. - /// - /// - /// A function which determines whether the data template matches the specified data. - /// - /// - /// A function which when passed a matching object returns a control. - /// - /// - /// A function which when passed a matching object returns the child items. - /// - /// - /// A function which when passed a matching object returns the the initial expanded state - /// of the node. - /// - public FuncTreeDataTemplate( - Func match, - Func build, - Func itemsSelector, - Func isExpanded) - : base( - CastMatch(match), - Cast(build), - Cast(itemsSelector), - Cast(isExpanded)) - { - } - /// /// Casts a typed match function to an untyped match function. /// diff --git a/src/Perspex.Controls/Templates/ITreeDataTemplate.cs b/src/Perspex.Controls/Templates/ITreeDataTemplate.cs index ca967f60a4..6415ed37b2 100644 --- a/src/Perspex.Controls/Templates/ITreeDataTemplate.cs +++ b/src/Perspex.Controls/Templates/ITreeDataTemplate.cs @@ -10,13 +10,6 @@ namespace Perspex.Controls.Templates /// public interface ITreeDataTemplate : IDataTemplate { - /// - /// Checks to see if the item should be initially expanded. - /// - /// The item. - /// True if the item should be initially expanded, otherwise false. - bool IsExpanded(object item); - /// /// Selects the child items of an item. /// diff --git a/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..df3c17278d 100644 --- a/src/Perspex.Styling/Perspex.Styling.csproj +++ b/src/Perspex.Styling/Perspex.Styling.csproj @@ -45,6 +45,8 @@ + + @@ -56,11 +58,10 @@ - - + diff --git a/src/Perspex.Styling/Styling/ActivatedObservable.cs b/src/Perspex.Styling/Styling/ActivatedObservable.cs new file mode 100644 index 0000000000..97c1b73106 --- /dev/null +++ b/src/Perspex.Styling/Styling/ActivatedObservable.cs @@ -0,0 +1,74 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive; +using System.Reactive.Linq; + +namespace Perspex.Styling +{ + /// + /// An observable which is switched on or off according to an activator observable. + /// + /// + /// An has two inputs: an activator observable a + /// observable which produces the activated value. When the activator + /// produces true, the will produce the current activated + /// value. When the activator produces false it will produce + /// . + /// + internal class ActivatedObservable : ObservableBase, IDescription + { + /// + /// Initializes a new instance of the class. + /// + /// The activator. + /// An observable that produces the activated value. + /// The binding description. + public ActivatedObservable( + IObservable activator, + IObservable source, + string description) + { + Activator = activator; + Description = description; + Source = source; + } + + /// + /// Gets the activator observable. + /// + public IObservable Activator { get; } + + /// + /// Gets a description of the binding. + /// + public string Description { get; } + + /// + /// Gets an observable which produces the . + /// + public IObservable Source { get; } + + /// + /// Notifies the provider that an observer is to receive notifications. + /// + /// The observer. + /// IDisposable object used to unsubscribe from the observable sequence. + protected override IDisposable SubscribeCore(IObserver observer) + { + Contract.Requires(observer != null); + + var sourceCompleted = Source.TakeLast(1).Select(_ => Unit.Default); + var activatorCompleted = Activator.TakeLast(1).Select(_ => Unit.Default); + var completed = sourceCompleted.Merge(activatorCompleted); + + return Activator + .CombineLatest(Source, (x, y) => new { Active = x, Value = y }) + .Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) + .DistinctUntilChanged() + .TakeUntil(completed) + .Subscribe(observer); + } + } +} diff --git a/src/Perspex.Styling/Styling/ActivatedSubject.cs b/src/Perspex.Styling/Styling/ActivatedSubject.cs new file mode 100644 index 0000000000..3ccbfac6c0 --- /dev/null +++ b/src/Perspex.Styling/Styling/ActivatedSubject.cs @@ -0,0 +1,111 @@ +// 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.Linq; +using System.Reactive.Subjects; + +namespace Perspex.Styling +{ + /// + /// A subject which is switched on or off according to an activator observable. + /// + /// + /// An has two inputs: an activator observable and either an + /// or a observable which produces the + /// activated value. When the activator produces true, the will + /// produce the current activated value. When the activator produces false it will produce + /// . + /// + internal class ActivatedSubject : ActivatedObservable, ISubject, IDescription + { + private bool? _active; + private bool _completed; + private object _value; + + /// + /// Initializes a new instance of the class. + /// + /// The activator. + /// An observable that produces the activated value. + /// The binding description. + public ActivatedSubject( + IObservable activator, + ISubject source, + string description) + : base(activator, source, description) + { + Activator.Subscribe(ActivatorChanged, ActivatorError, ActivatorCompleted); + } + + /// + /// Gets the underlying subject. + /// + public new ISubject Source + { + get { return (ISubject)base.Source; } + } + + /// + /// Notifies all subscribed observers about the end of the sequence. + /// + public void OnCompleted() + { + if (_active.Value && !_completed) + { + Source.OnCompleted(); + } + } + + /// + /// Notifies all subscribed observers with the exception. + /// + /// The exception to send to all subscribed observers. + /// is null. + public void OnError(Exception error) + { + if (_active.Value && !_completed) + { + Source.OnError(error); + } + } + + /// + /// Notifies all subscribed observers with the value. + /// + /// The value to send to all subscribed observers. + public void OnNext(object value) + { + _value = value; + + if (_active.Value && !_completed) + { + Source.OnNext(value); + } + } + + private void ActivatorChanged(bool active) + { + bool first = !_active.HasValue; + + _active = active; + + if (!first) + { + Source.OnNext(active ? _value : PerspexProperty.UnsetValue); + } + } + + private void ActivatorCompleted() + { + _completed = true; + Source.OnCompleted(); + } + + private void ActivatorError(Exception e) + { + _completed = true; + Source.OnError(e); + } + } +} diff --git a/src/Perspex.Styling/Styling/ActivatedValue.cs b/src/Perspex.Styling/Styling/ActivatedValue.cs new file mode 100644 index 0000000000..0c02598086 --- /dev/null +++ b/src/Perspex.Styling/Styling/ActivatedValue.cs @@ -0,0 +1,72 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive; +using System.Reactive.Linq; + +namespace Perspex.Styling +{ + /// + /// An value which is switched on or off according to an activator observable. + /// + /// + /// An has two inputs: an activator observable and an + /// . When the activator produces true, the + /// will produce the current value. When the activator + /// produces false it will produce . + /// + internal class ActivatedValue : ObservableBase, IDescription + { + /// + /// The activator. + /// + private readonly IObservable _activator; + + /// + /// Initializes a new instance of the class. + /// + /// The activator. + /// The activated value. + /// The binding description. + public ActivatedValue( + IObservable activator, + object value, + string description) + { + _activator = activator; + Value = value; + Description = description; + } + + /// + /// Gets the activated value. + /// + public object Value + { + get; + } + + /// + /// Gets a description of the binding. + /// + public string Description + { + get; + } + + /// + /// Notifies the provider that an observer is to receive notifications. + /// + /// The observer. + /// IDisposable object used to unsubscribe from the observable sequence. + protected override IDisposable SubscribeCore(IObserver observer) + { + Contract.Requires(observer != null); + + return _activator + .Select(active => active ? Value : PerspexProperty.UnsetValue) + .Subscribe(observer); + } + } +} diff --git a/src/Perspex.Styling/Styling/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..b224382f4a 100644 --- a/src/Perspex.Styling/Styling/Setter.cs +++ b/src/Perspex.Styling/Styling/Setter.cs @@ -2,7 +2,10 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive.Subjects; +using Perspex.Data; using Perspex.Metadata; +using Perspex.Reactive; namespace Perspex.Styling { @@ -46,6 +49,7 @@ namespace Perspex.Styling /// Gets or sets the property value. /// [Content] + [AssignBinding] public object Value { get; @@ -60,15 +64,67 @@ namespace Perspex.Styling /// An optional activator. public void Apply(IStyle style, IStyleable control, IObservable activator) { - if (activator == null) + Contract.Requires(control != null); + + var description = style?.ToString(); + + if (Property == null) + { + throw new InvalidOperationException("Setter.Property must be set."); + } + + var binding = Value as IBinding; + + if (binding != null) { - control.SetValue(Property, Value, BindingPriority.Style); + if (activator == null) + { + Bind(control, Property, binding); + } + else + { + var subject = binding.CreateSubject(control, Property); + var activated = new ActivatedSubject(activator, subject, description); + Bind(control, Property, binding, activated); + } } 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 ActivatedValue(activator, Value, description); + control.Bind(Property, activated, BindingPriority.StyleTrigger); + } + } + } + + private void Bind(IStyleable control, PerspexProperty property, IBinding binding) + { + Bind(control, property, binding, binding.CreateSubject(control, property)); + } + + private void Bind( + IStyleable control, + PerspexProperty property, + IBinding binding, + ISubject subject) + { + var mode = binding.Mode; + + if (mode == BindingMode.Default) + { + mode = property.DefaultBindingMode; } + + control.Bind( + property, + subject, + mode, + binding.Priority); } } } diff --git a/src/Perspex.Styling/Styling/StyleBinding.cs b/src/Perspex.Styling/Styling/StyleBinding.cs deleted file mode 100644 index b2ddbeee1b..0000000000 --- a/src/Perspex.Styling/Styling/StyleBinding.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) The Perspex Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Reactive; -using System.Reactive.Linq; - -namespace Perspex.Styling -{ - /// - /// Provides an observable for a style. - /// - /// - /// A has two inputs: an activator observable and either an - /// or a observable which produces the - /// activated value. When the activator produces true, the will - /// produce the current activated value. When the activator produces false it will produce - /// . - /// - internal class StyleBinding : ObservableBase, IDescription - { - /// - /// The activator. - /// - private readonly IObservable _activator; - - /// - /// Initializes a new instance of the class. - /// - /// The activator. - /// The activated value. - /// The binding description. - public StyleBinding( - IObservable activator, - object activatedValue, - string description) - { - _activator = activator; - ActivatedValue = activatedValue; - Description = description; - } - - /// - /// Initializes a new instance of the class. - /// - /// The activator. - /// An observable that produces the activated value. - /// The binding description. - public StyleBinding( - IObservable activator, - IObservable source, - string description) - { - _activator = activator; - Description = description; - Source = source; - } - - /// - /// Gets the activated value. - /// - public object ActivatedValue - { - get; - } - - /// - /// Gets a description of the binding. - /// - public string Description - { - get; - } - - /// - /// Gets an observable which produces the . - /// - public IObservable Source - { - get; - } - - /// - /// Notifies the provider that an observer is to receive notifications. - /// - /// The observer. - /// IDisposable object used to unsubscribe from the observable sequence. - protected override IDisposable SubscribeCore(IObserver observer) - { - Contract.Requires(observer != null); - - if (Source == null) - { - return _activator - .Select(active => active ? ActivatedValue : PerspexProperty.UnsetValue) - .Subscribe(observer); - } - else - { - return _activator - .CombineLatest(Source, (x, y) => new { Active = x, Value = y }) - .Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) - .Subscribe(observer); - } - } - } -} diff --git a/tests/Perspex.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..3e7ee6f86d 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 @@ -81,7 +82,7 @@ namespace Perspex.Base.UnitTests } [Fact] - public void Two_Way_Binding_Works() + public void Two_Way_Separate_Binding_Works() { Class1 obj1 = new Class1(); Class1 obj2 = new Class1(); @@ -142,34 +143,6 @@ namespace Perspex.Base.UnitTests Assert.Equal("third", obj2.GetValue(Class1.FooProperty)); } - [Fact] - public void BindTwoWay_Gets_Initial_Value_From_Source() - { - Class1 source = new Class1(); - Class1 target = new Class1(); - - source.SetValue(Class1.FooProperty, "initial"); - target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty); - - Assert.Equal("initial", target.GetValue(Class1.FooProperty)); - } - - [Fact] - public void BindTwoWay_Updates_Values() - { - Class1 source = new Class1(); - Class1 target = new Class1(); - - source.SetValue(Class1.FooProperty, "first"); - target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty); - - Assert.Equal("first", target.GetValue(Class1.FooProperty)); - source.SetValue(Class1.FooProperty, "second"); - Assert.Equal("second", target.GetValue(Class1.FooProperty)); - target.SetValue(Class1.FooProperty, "third"); - Assert.Equal("third", source.GetValue(Class1.FooProperty)); - } - [Fact] public void Local_Binding_Overwrites_Local_Value() { @@ -234,7 +207,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 +221,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 +237,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_GetSubject.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs new file mode 100644 index 0000000000..2d2bf97ac6 --- /dev/null +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs @@ -0,0 +1,49 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using Xunit; + +namespace Perspex.Base.UnitTests +{ + public class PerspexObjectTests_GetSubject + { + [Fact] + public void GetSubject_Returns_Values() + { + var source = new Class1 { Foo = "foo" }; + var target = source.GetSubject(Class1.FooProperty); + var result = new List(); + + target.Subscribe(x => result.Add(x)); + source.Foo = "bar"; + source.Foo = "baz"; + + Assert.Equal(new[] { "foo", "bar", "baz" }, result); + } + + [Fact] + public void GetSubject_Sets_Values() + { + var source = new Class1 { Foo = "foo" }; + var target = source.GetSubject(Class1.FooProperty); + + target.OnNext("bar"); + Assert.Equal("bar", source.Foo); + } + + private class Class1 : PerspexObject + { + public static readonly PerspexProperty FooProperty = + PerspexProperty.Register("Foo", "foodefault"); + + public string Foo + { + get { return GetValue(FooProperty); } + set { SetValue(FooProperty, value); } + } + } + } +} 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..24bcda978b 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. @@ -272,8 +273,7 @@ namespace Perspex.LeakTests { new FuncTreeDataTemplate( x => new TextBlock { Text = x.Name }, - x => x.Children, - x => true) + x => x.Children) }, Items = nodes } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs index 95675fef4b..64efaa07de 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -1,14 +1,12 @@ // Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; -using System.Globalization; -using System.Reactive.Linq; -using System.Reactive.Subjects; using Moq; using Perspex.Controls; +using Perspex.Data; using Perspex.Markup.Data; using Perspex.Markup.Xaml.Data; +using ReactiveUI; using Xunit; namespace Perspex.Markup.Xaml.UnitTests.Data @@ -18,101 +16,101 @@ namespace Perspex.Markup.Xaml.UnitTests.Data [Fact] public void OneWay_Binding_Should_Be_Set_Up() { - var target = CreateTarget(); + var source = new Source { Foo = "foo" }; + var target = new TextBlock { DataContext = source }; var binding = new Binding { Path = "Foo", Mode = BindingMode.OneWay, }; - binding.Bind(target.Object, TextBox.TextProperty); + target.Bind(TextBox.TextProperty, binding); - target.Verify(x => x.Bind( - TextBox.TextProperty, - It.IsAny>(), - BindingPriority.LocalValue)); + Assert.Equal("foo", target.Text); + source.Foo = "bar"; + Assert.Equal("bar", target.Text); + target.Text = "baz"; + Assert.Equal("bar", source.Foo); } [Fact] public void TwoWay_Binding_Should_Be_Set_Up() { - var target = CreateTarget(); + var source = new Source { Foo = "foo" }; + var target = new TextBlock { DataContext = source }; var binding = new Binding { Path = "Foo", Mode = BindingMode.TwoWay, }; - binding.Bind(target.Object, TextBox.TextProperty); + target.Bind(TextBox.TextProperty, binding); - target.Verify(x => x.BindTwoWay( - TextBox.TextProperty, - It.IsAny>(), - BindingPriority.LocalValue)); + Assert.Equal("foo", target.Text); + source.Foo = "bar"; + Assert.Equal("bar", target.Text); + target.Text = "baz"; + Assert.Equal("baz", source.Foo); } [Fact] public void OneTime_Binding_Should_Be_Set_Up() { - var dataContext = new BehaviorSubject(null); - var expression = new BehaviorSubject(null); - var target = CreateTarget(dataContext: dataContext); + var source = new Source { Foo = "foo" }; + var target = new TextBlock { DataContext = source }; var binding = new Binding { Path = "Foo", Mode = BindingMode.OneTime, }; - binding.Bind(target.Object, TextBox.TextProperty, expression); + target.Bind(TextBox.TextProperty, binding); - target.Verify(x => x.SetValue( - (PerspexProperty)TextBox.TextProperty, - null, - BindingPriority.LocalValue)); - target.ResetCalls(); - - expression.OnNext("foo"); - dataContext.OnNext(1); - - target.Verify(x => x.SetValue( - (PerspexProperty)TextBox.TextProperty, - "foo", - BindingPriority.LocalValue)); + Assert.Equal("foo", target.Text); + source.Foo = "bar"; + Assert.Equal("foo", target.Text); + target.Text = "baz"; + Assert.Equal("bar", source.Foo); } [Fact] public void OneWayToSource_Binding_Should_Be_Set_Up() { - var textObservable = new Mock>(); - var expression = new Mock>(); - var target = CreateTarget(text: textObservable.Object); + var source = new Source { Foo = "foo" }; + var target = new TextBlock { DataContext = source, Text = "bar" }; var binding = new Binding { Path = "Foo", Mode = BindingMode.OneWayToSource, }; - binding.Bind(target.Object, TextBox.TextProperty, expression.Object); + target.Bind(TextBox.TextProperty, binding); - textObservable.Verify(x => x.Subscribe(expression.Object)); + Assert.Equal("bar", source.Foo); + target.Text = "baz"; + Assert.Equal("baz", source.Foo); + source.Foo = "quz"; + Assert.Equal("baz", target.Text); } [Fact] public void Default_BindingMode_Should_Be_Used() { - var target = CreateTarget(null); + // Default for TextBox.Text is two-way. + var source = new Source { Foo = "foo" }; + var target = new TextBlock { DataContext = source }; var binding = new Binding { Path = "Foo", }; - binding.Bind(target.Object, TextBox.TextProperty); + target.Bind(TextBox.TextProperty, binding); - // Default for TextBox.Text is two-way. - target.Verify(x => x.BindTwoWay( - TextBox.TextProperty, - It.IsAny>(), - BindingPriority.LocalValue)); + Assert.Equal("foo", target.Text); + source.Foo = "bar"; + Assert.Equal("bar", target.Text); + target.Text = "baz"; + Assert.Equal("baz", source.Foo); } [Fact] @@ -131,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); @@ -155,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); @@ -164,13 +163,13 @@ namespace Perspex.Markup.Xaml.UnitTests.Data [Fact] public void Should_Use_DefaultValueConverter_When_No_Converter_Specified() { - var target = CreateTarget(null); + var target = new TextBlock(); ; var binding = new Binding { Path = "Foo", }; - var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType); + var result = binding.CreateSubject(target, TextBox.TextProperty); Assert.IsType(((ExpressionSubject)result).Converter); } @@ -178,7 +177,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data [Fact] public void Should_Use_Supplied_Converter() { - var target = CreateTarget(null); + var target = new TextBlock(); var converter = new Mock(); var binding = new Binding { @@ -186,7 +185,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Foo", }; - var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType); + var result = binding.CreateSubject(target, TextBox.TextProperty); Assert.Same(converter.Object, ((ExpressionSubject)result).Converter); } @@ -194,7 +193,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data [Fact] public void Should_Pass_ConverterParameter_To_Supplied_Converter() { - var target = CreateTarget(); + var target = new TextBlock(); var converter = new Mock(); var binding = new Binding { @@ -203,7 +202,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Bar", }; - var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType); + var result = binding.CreateSubject(target, TextBox.TextProperty); Assert.Same("foo", ((ExpressionSubject)result).ConverterParameter); } @@ -239,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 @@ -261,24 +260,15 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Assert.Equal(2, vm.Bar); } - private Mock CreateTarget(object dataContext) + public class Source : ReactiveObject { - return CreateTarget(dataContext: Observable.Never().StartWith(dataContext)); - } + private string _foo; - private Mock CreateTarget( - IObservable dataContext = null, - IObservable text = null) - { - var result = new Mock(); - - dataContext = dataContext ?? Observable.Never().StartWith((object)null); - text = text ?? Observable.Never().StartWith((string)null); - - result.Setup(x => x.GetObservable(Control.DataContextProperty)).Returns(dataContext); - result.Setup(x => x.GetObservable((PerspexProperty)Control.DataContextProperty)).Returns(dataContext); - result.Setup(x => x.GetObservable((PerspexProperty)TextBox.TextProperty)).Returns(text); - return result; + public string Foo + { + get { return _foo; } + set { this.RaiseAndSetIfChanged(ref _foo, value); } + } } private class OldDataContextViewModel @@ -297,7 +287,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data public OldDataContextTest() { - Bind(BarProperty, GetObservable(FooProperty)); + Bind(BarProperty, this.GetObservable(FooProperty)); } } } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_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 af0b492e49..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,48 +47,28 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Path = "Foo", }; - binding.Bind(target.Object, TextBox.TextProperty); + target.Object.Bind(TextBox.TextProperty, binding); - target.Verify(x => x.BindTwoWay( + target.Verify(x => x.Bind( TextBox.TextProperty, It.IsAny>(), BindingPriority.TemplatedParent)); } - [Fact] - public void OneWayToSource_Binding_Should_Be_Set_Up() - { - var textObservable = new Mock>(); - var expression = new Mock>(); - var target = CreateTarget(text: textObservable.Object); - var binding = new Binding - { - Path = "Foo", - Mode = BindingMode.OneWayToSource, - }; - - binding.Bind(target.Object, TextBox.TextProperty, expression.Object); - - textObservable.Verify(x => x.Subscribe(expression.Object)); - } - - private Mock CreateTarget(ITemplatedControl templatedParent) + private Mock CreateTarget(ITemplatedControl templatedParent) { - return CreateTarget(templatedParent: Observable.Never().StartWith(templatedParent)); + return CreateTarget(templatedParent: templatedParent); } - private Mock CreateTarget( - IObservable templatedParent = null, - IObservable text = null) + private Mock CreateTarget( + ITemplatedControl templatedParent = null, + string text = null) { - var result = new Mock(); - - templatedParent = templatedParent ?? Observable.Never().StartWith((ITemplatedControl)null); - text = text ?? Observable.Never().StartWith((string)null); + var result = new Mock(); - result.Setup(x => x.GetObservable(Control.TemplatedParentProperty)).Returns(templatedParent); - result.Setup(x => x.GetObservable((PerspexProperty)Control.TemplatedParentProperty)).Returns(templatedParent); - result.Setup(x => x.GetObservable((PerspexProperty)TextBox.TextProperty)).Returns(text); + result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent); + result.Setup(x => x.GetValue((PerspexProperty)Control.TemplatedParentProperty)).Returns(templatedParent); + result.Setup(x => x.GetValue((PerspexProperty)TextBox.TextProperty)).Returns(text); return result; } } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs index 23db6d4c39..908bb16e37 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reactive.Linq; -using System.Reactive.Subjects; using Moq; using Perspex.Controls; using Perspex.Markup.Xaml.Data; @@ -31,12 +30,10 @@ namespace Perspex.Markup.Xaml.UnitTests.Data } }; - var target = new Mock(); + var target = new Mock(); target.Setup(x => x.GetValue(Control.DataContextProperty)).Returns(source); - target.Setup(x => x.GetObservable(Control.DataContextProperty)).Returns( - Observable.Never().StartWith(source)); - var subject = binding.CreateSubject(target.Object, typeof(string)); + var 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..a496f26fc5 --- /dev/null +++ b/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs @@ -0,0 +1,120 @@ +// 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 System.Reactive.Linq; +using Moq; +using Perspex.Controls; +using Perspex.Controls.Primitives; +using Perspex.Data; +using Perspex.Markup.Xaml.Data; +using Perspex.Platform; +using Perspex.Styling; +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); + } + } + + [Fact] + public void Setter_With_TwoWay_Binding_Should_Update_Source() + { + using (PerspexLocator.EnterScope()) + { + PerspexLocator.CurrentMutable + .Bind() + .ToConstant(Mock.Of(x => + x.CurrentThreadIsLoopThread == true)); + + var data = new Data + { + Foo = "foo", + }; + + var control = new TextBox + { + DataContext = data, + }; + + var setter = new Setter + { + Property = TextBox.TextProperty, + Value = new Binding + { + Path = "Foo", + Mode = BindingMode.TwoWay + } + }; + + setter.Apply(null, control, null); + Assert.Equal("foo", control.Text); + + control.Text = "bar"; + Assert.Equal("bar", data.Foo); + } + } + + [Fact] + public void Setter_With_TwoWay_Binding_And_Activator_Should_Update_Source() + { + using (PerspexLocator.EnterScope()) + { + PerspexLocator.CurrentMutable + .Bind() + .ToConstant(Mock.Of(x => + x.CurrentThreadIsLoopThread == true)); + + var data = new Data + { + Foo = "foo", + }; + + var control = new TextBox + { + DataContext = data, + }; + + var setter = new Setter + { + Property = TextBox.TextProperty, + Value = new Binding + { + Path = "Foo", + Mode = BindingMode.TwoWay + } + }; + + var activator = Observable.Never().StartWith(true); + + setter.Apply(null, control, activator); + Assert.Equal("foo", control.Text); + + control.Text = "bar"; + Assert.Equal("bar", data.Foo); + } + } + + private class Data + { + public string Foo { get; set; } + } + } +} diff --git a/tests/Perspex.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/ActivatedObservableTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs new file mode 100644 index 0000000000..e1beb992c2 --- /dev/null +++ b/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Xunit; + +namespace Perspex.Styling.UnitTests +{ + public class ActivatedObservableTests + { + [Fact] + public void Should_Produce_Correct_Values() + { + var activator = new BehaviorSubject(false); + var source = new BehaviorSubject(1); + var target = new ActivatedObservable(activator, source, string.Empty); + var result = new List(); + + target.Subscribe(x => result.Add(x)); + + activator.OnNext(true); + source.OnNext(2); + activator.OnNext(false); + source.OnNext(3); + activator.OnNext(true); + + Assert.Equal( + new[] + { + PerspexProperty.UnsetValue, + 1, + 2, + PerspexProperty.UnsetValue, + 3, + }, + result); + } + + [Fact] + public void Should_Complete_When_Source_Completes() + { + var activator = new BehaviorSubject(false); + var source = new BehaviorSubject(1); + var target = new ActivatedObservable(activator, source, string.Empty); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + source.OnCompleted(); + + Assert.True(completed); + } + + [Fact] + public void Should_Complete_When_Activator_Completes() + { + var activator = new BehaviorSubject(false); + var source = new BehaviorSubject(1); + var target = new ActivatedObservable(activator, source, string.Empty); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + activator.OnCompleted(); + + Assert.True(completed); + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs new file mode 100644 index 0000000000..58b7d9fd46 --- /dev/null +++ b/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using Xunit; + +namespace Perspex.Styling.UnitTests +{ + public class ActivatedSubjectTests + { + [Fact] + public void Should_Set_Values() + { + var activator = new BehaviorSubject(false); + var source = new TestSubject(); + var target = new ActivatedSubject(activator, source, string.Empty); + + target.OnNext("bar"); + Assert.Equal(PerspexProperty.UnsetValue, source.Value); + activator.OnNext(true); + target.OnNext("baz"); + Assert.Equal("baz", source.Value); + activator.OnNext(false); + Assert.Equal(PerspexProperty.UnsetValue, source.Value); + target.OnNext("bax"); + activator.OnNext(true); + Assert.Equal("bax", source.Value); + } + + [Fact] + public void Should_Invoke_OnCompleted_On_Activator_Completed() + { + var activator = new BehaviorSubject(false); + var source = new TestSubject(); + var target = new ActivatedSubject(activator, source, string.Empty); + + activator.OnCompleted(); + + Assert.True(source.Completed); + } + + [Fact] + public void Should_Invoke_OnError_On_Activator_Error() + { + var activator = new BehaviorSubject(false); + var source = new TestSubject(); + var target = new ActivatedSubject(activator, source, string.Empty); + + activator.OnError(new Exception()); + + Assert.NotNull(source.Error); + } + + private class Class1 : PerspexObject + { + public static readonly PerspexProperty FooProperty = + PerspexProperty.Register("Foo", "foodefault"); + + public string Foo + { + get { return GetValue(FooProperty); } + set { SetValue(FooProperty, value); } + } + } + + private class TestSubject : ISubject + { + private IObserver _observer; + + public bool Completed { get; set; } + public Exception Error { get; set; } + public object Value { get; set; } = PerspexProperty.UnsetValue; + + public void OnCompleted() + { + Completed = true; + } + + public void OnError(Exception error) + { + Error = error; + } + + public void OnNext(object value) + { + Value = value; + } + + public IDisposable Subscribe(IObserver observer) + { + _observer = observer; + return Disposable.Empty; + } + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs new file mode 100644 index 0000000000..4921aeaa7b --- /dev/null +++ b/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Xunit; + +namespace Perspex.Styling.UnitTests +{ + public class ActivatedValueTests + { + [Fact] + public void Should_Produce_Correct_Values() + { + var activator = new BehaviorSubject(false); + var target = new ActivatedValue(activator, 1, string.Empty); + var result = new List(); + + target.Subscribe(x => result.Add(x)); + + activator.OnNext(true); + activator.OnNext(false); + + Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result); + } + + [Fact] + public void Should_Complete_When_Activator_Completes() + { + var activator = new BehaviorSubject(false); + var target = new ActivatedValue(activator, 1, string.Empty); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + activator.OnCompleted(); + + Assert.True(completed); + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj index cc22e7f257..af640ebdf4 100644 --- a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj +++ b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj @@ -80,6 +80,8 @@ + + @@ -90,7 +92,8 @@ - + + 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/StyleBindingTests.cs b/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs deleted file mode 100644 index 82a7f66d07..0000000000 --- a/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) The Perspex Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using Xunit; - -namespace Perspex.Styling.UnitTests -{ - public class StyleBindingTests - { - [Fact] - public async void Should_Produce_UnsetValue_On_Activator_False() - { - var activator = new BehaviorSubject(false); - var target = new StyleBinding(activator, 1, string.Empty); - var result = await target.Take(1); - - Assert.Equal(PerspexProperty.UnsetValue, result); - } - - [Fact] - public async void Should_Produce_Value_On_Activator_True() - { - var activator = new BehaviorSubject(true); - var target = new StyleBinding(activator, 1, string.Empty); - var result = await target.Take(1); - - Assert.Equal(1, result); - } - - [Fact] - public void Should_Change_Value_On_Activator_Change() - { - var activator = new BehaviorSubject(false); - var target = new StyleBinding(activator, 1, string.Empty); - var result = new List(); - - target.Subscribe(x => result.Add(x)); - - activator.OnNext(true); - activator.OnNext(false); - - Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result); - } - - [Fact] - public void Should_Change_Value_With_Source_Observable() - { - var activator = new BehaviorSubject(false); - var source = new BehaviorSubject(1); - var target = new StyleBinding(activator, source, string.Empty); - var result = new List(); - - target.Subscribe(x => result.Add(x)); - - activator.OnNext(true); - source.OnNext(2); - activator.OnNext(false); - - Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, 2, PerspexProperty.UnsetValue }, result); - } - - [Fact] - public void Should_Complete_When_Activator_Completes() - { - var activator = new BehaviorSubject(false); - var target = new StyleBinding(activator, 1, string.Empty); - var completed = false; - - target.Subscribe(_ => { }, () => completed = true); - activator.OnCompleted(); - - Assert.True(completed); - } - } -} 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(); }