From 292a04d973d35b0de3e682a4377b32e5cd59343b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Sep 2015 02:22:49 +0200 Subject: [PATCH 01/54] Renamed DataTemplate -> FuncDataTemplate. --- samples/TestApplication/GalleryStyle.cs | 2 +- samples/TestApplication/Program.cs | 2 +- src/Perspex.Controls/ContentControl.cs | 2 +- .../Generators/TreeItemContainerGenerator.cs | 2 +- src/Perspex.Controls/IContentControl.cs | 2 +- src/Perspex.Controls/Perspex.Controls.csproj | 4 ++-- .../Templates/DataTemplateExtensions.cs | 2 +- .../{DataTemplate.cs => FuncDataTemplate.cs} | 14 +++++++------- .../{DataTemplate`1.cs => FuncDataTemplate`1.cs} | 10 +++++----- src/Perspex.Controls/Templates/TreeDataTemplate.cs | 2 +- src/Perspex.Themes.Default/MenuItemStyle.cs | 4 ++-- .../ItemsControlTests.cs | 2 +- .../Perspex.Controls.UnitTests/TabControlTests.cs | 2 +- 13 files changed, 25 insertions(+), 25 deletions(-) rename src/Perspex.Controls/Templates/{DataTemplate.cs => FuncDataTemplate.cs} (80%) rename src/Perspex.Controls/Templates/{DataTemplate`1.cs => FuncDataTemplate`1.cs} (84%) diff --git a/samples/TestApplication/GalleryStyle.cs b/samples/TestApplication/GalleryStyle.cs index 1076e2cd0f..6bd137060a 100644 --- a/samples/TestApplication/GalleryStyle.cs +++ b/samples/TestApplication/GalleryStyle.cs @@ -61,7 +61,7 @@ namespace TestApplication { DataTemplates = new DataTemplates { - new DataTemplate(x => new Border + new FuncDataTemplate(x => new Border { [~Border.BackgroundProperty] = control[~TemplatedControl.BackgroundProperty], Padding = new Thickness(10), diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index bfbb8a17ce..147a9b0f7c 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -401,7 +401,7 @@ namespace TestApplication Margin = new Thickness(10), DataTemplates = new DataTemplates { - new DataTemplate(x => + new FuncDataTemplate(x => new StackPanel { Gap = 4, diff --git a/src/Perspex.Controls/ContentControl.cs b/src/Perspex.Controls/ContentControl.cs index 4ee47a8ef9..6297ec4f71 100644 --- a/src/Perspex.Controls/ContentControl.cs +++ b/src/Perspex.Controls/ContentControl.cs @@ -11,7 +11,7 @@ using Perspex.Layout; namespace Perspex.Controls { /// - /// Displays according to a . + /// Displays according to a . /// public class ContentControl : TemplatedControl, IContentControl, IReparentingHost { diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index 1a0fdb1a5f..3dabba7aad 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -192,7 +192,7 @@ namespace Perspex.Controls.Generators if (template == null) { - template = DataTemplate.Default; + template = FuncDataTemplate.Default; } var treeTemplate = template as ITreeDataTemplate; diff --git a/src/Perspex.Controls/IContentControl.cs b/src/Perspex.Controls/IContentControl.cs index cc2c4a3fea..50978a79cb 100644 --- a/src/Perspex.Controls/IContentControl.cs +++ b/src/Perspex.Controls/IContentControl.cs @@ -7,7 +7,7 @@ namespace Perspex.Controls { /// /// Defines a control that displays according to a - /// . + /// . /// public interface IContentControl : IControl { diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 6bcd55b76c..91b85d45eb 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -70,7 +70,7 @@ - + @@ -100,7 +100,7 @@ - + diff --git a/src/Perspex.Controls/Templates/DataTemplateExtensions.cs b/src/Perspex.Controls/Templates/DataTemplateExtensions.cs index 8e885ede2d..fbd4086ed5 100644 --- a/src/Perspex.Controls/Templates/DataTemplateExtensions.cs +++ b/src/Perspex.Controls/Templates/DataTemplateExtensions.cs @@ -42,7 +42,7 @@ namespace Perspex.Controls.Templates } else { - result = DataTemplate.Default.Build(data); + result = FuncDataTemplate.Default.Build(data); } result.DataContext = data; diff --git a/src/Perspex.Controls/Templates/DataTemplate.cs b/src/Perspex.Controls/Templates/FuncDataTemplate.cs similarity index 80% rename from src/Perspex.Controls/Templates/DataTemplate.cs rename to src/Perspex.Controls/Templates/FuncDataTemplate.cs index 2c18cfdb13..ab2e203d4c 100644 --- a/src/Perspex.Controls/Templates/DataTemplate.cs +++ b/src/Perspex.Controls/Templates/FuncDataTemplate.cs @@ -9,13 +9,13 @@ namespace Perspex.Controls.Templates /// /// Builds a control for a piece of data. /// - public class DataTemplate : FuncTemplate, IDataTemplate + public class FuncDataTemplate : FuncTemplate, IDataTemplate { /// /// The default data template used in the case where not matching data template is found. /// - public static readonly DataTemplate Default = - new DataTemplate(typeof(object), o => (o != null) ? new TextBlock { Text = o.ToString() } : null); + public static readonly FuncDataTemplate Default = + new FuncDataTemplate(typeof(object), o => (o != null) ? new TextBlock { Text = o.ToString() } : null); /// /// The implementation of the method. @@ -23,19 +23,19 @@ namespace Perspex.Controls.Templates private readonly Func _match; /// - /// Initializes a new instance of the class. + /// 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. /// - public DataTemplate(Type type, Func build) + public FuncDataTemplate(Type type, Func build) : this(o => IsInstance(o, type), build) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A function which determines whether the data template matches the specified data. @@ -43,7 +43,7 @@ namespace Perspex.Controls.Templates /// /// A function which returns a control for matching data. /// - public DataTemplate(Func match, Func build) + public FuncDataTemplate(Func match, Func build) : base(build) { Contract.Requires(match != null); diff --git a/src/Perspex.Controls/Templates/DataTemplate`1.cs b/src/Perspex.Controls/Templates/FuncDataTemplate`1.cs similarity index 84% rename from src/Perspex.Controls/Templates/DataTemplate`1.cs rename to src/Perspex.Controls/Templates/FuncDataTemplate`1.cs index ba8a56b8d1..b0021d093d 100644 --- a/src/Perspex.Controls/Templates/DataTemplate`1.cs +++ b/src/Perspex.Controls/Templates/FuncDataTemplate`1.cs @@ -9,21 +9,21 @@ namespace Perspex.Controls.Templates /// Builds a control for a piece of data of specified type. /// /// The type of the template's data. - public class DataTemplate : DataTemplate + public class FuncDataTemplate : FuncDataTemplate { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A function which when passed an object of returns a control. /// - public DataTemplate(Func build) + public FuncDataTemplate(Func build) : base(typeof(T), CastBuild(build)) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A function which determines whether the data template matches the specified data. @@ -31,7 +31,7 @@ namespace Perspex.Controls.Templates /// /// A function which when passed an object of returns a control. /// - public DataTemplate(Func match, Func build) + public FuncDataTemplate(Func match, Func build) : base(CastMatch(match), CastBuild(build)) { } diff --git a/src/Perspex.Controls/Templates/TreeDataTemplate.cs b/src/Perspex.Controls/Templates/TreeDataTemplate.cs index e384aaf90d..fc99c20d53 100644 --- a/src/Perspex.Controls/Templates/TreeDataTemplate.cs +++ b/src/Perspex.Controls/Templates/TreeDataTemplate.cs @@ -10,7 +10,7 @@ namespace Perspex.Controls.Templates /// /// A template used to build hierachical data. /// - public class TreeDataTemplate : DataTemplate, ITreeDataTemplate + public class TreeDataTemplate : FuncDataTemplate, ITreeDataTemplate { private readonly Func _itemsSelector; diff --git a/src/Perspex.Themes.Default/MenuItemStyle.cs b/src/Perspex.Themes.Default/MenuItemStyle.cs index a53b0e865c..3ff1638dc6 100644 --- a/src/Perspex.Themes.Default/MenuItemStyle.cs +++ b/src/Perspex.Themes.Default/MenuItemStyle.cs @@ -21,8 +21,8 @@ namespace Perspex.Themes.Default /// public class MenuItemStyle : Styles { - private static readonly DataTemplate AccessKeyDataTemplate = - new DataTemplate(x => new AccessText { Text = x }); + private static readonly FuncDataTemplate AccessKeyDataTemplate = + new FuncDataTemplate(x => new AccessText { Text = x }); /// /// Initializes a new instance of the class. diff --git a/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs b/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs index da7d4b953f..124a559ef0 100644 --- a/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs @@ -305,7 +305,7 @@ namespace Perspex.Controls.UnitTests DataContext = "Base", DataTemplates = new DataTemplates { - new DataTemplate(x => new Button { Content = x }) + new FuncDataTemplate(x => new Button { Content = x }) }, Items = items, }; diff --git a/tests/Perspex.Controls.UnitTests/TabControlTests.cs b/tests/Perspex.Controls.UnitTests/TabControlTests.cs index 57ef725e04..e4385d1bd7 100644 --- a/tests/Perspex.Controls.UnitTests/TabControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/TabControlTests.cs @@ -147,7 +147,7 @@ namespace Perspex.Controls.UnitTests DataContext = "Base", DataTemplates = new DataTemplates { - new DataTemplate(x => new Button { Content = x }) + new FuncDataTemplate(x => new Button { Content = x }) }, Items = items, }; From 1257178cc5621798c8e789e5fad85efda01f005c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Sep 2015 02:24:05 +0200 Subject: [PATCH 02/54] Renamed XamlDataTemplate -> DataTemplate --- .../Templates/{XamlDataTemplate.cs => DataTemplate.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Markup/Perspex.Markup.Xaml/Templates/{XamlDataTemplate.cs => DataTemplate.cs} (100%) diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/XamlDataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs similarity index 100% rename from src/Markup/Perspex.Markup.Xaml/Templates/XamlDataTemplate.cs rename to src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs From 328f4c95d69b9332577ec21e4e4916b02e84d769 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Sep 2015 02:43:25 +0200 Subject: [PATCH 03/54] Trying to define DataTemplates in XAML. Getting a crash from OmniXAML. --- samples/XamlTestApplication/MainViewModel.cs | 52 ------------------- .../ViewModels/MainWindowViewModel.cs | 22 ++++++++ .../ViewModels/TestItem.cs | 17 ++++++ .../XamlTestApplication/Views/MainWindow.cs | 3 +- .../XamlTestApplication/Views/MainWindow.paml | 23 ++++---- .../XamlTestApplication.csproj | 3 +- .../Context/PerspexObjectAssembler.cs | 2 +- .../Context/PerspexWiringContext.cs | 2 +- src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- .../Perspex.Markup.Xaml.csproj | 2 +- .../Templates/DataTemplate.cs | 2 +- 11 files changed, 57 insertions(+), 73 deletions(-) delete mode 100644 samples/XamlTestApplication/MainViewModel.cs create mode 100644 samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs create mode 100644 samples/XamlTestApplication/ViewModels/TestItem.cs diff --git a/samples/XamlTestApplication/MainViewModel.cs b/samples/XamlTestApplication/MainViewModel.cs deleted file mode 100644 index 450c8f422f..0000000000 --- a/samples/XamlTestApplication/MainViewModel.cs +++ /dev/null @@ -1,52 +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.Collections.Generic; -using ReactiveUI; - -namespace XamlTestApplication -{ - public class MainViewModel : ReactiveObject - { - private string _name; - - public MainViewModel() - { - Name = "Jos\u00E9 Manuel"; - People = new List - { - new Person("a little bit of Monica in my life"), - new Person("a little bit of Erica by my side"), - new Person("a little bit of Rita is all I need"), - new Person("a little bit of Tina is what I see"), - new Person("a little bit of Sandra in the sun"), - new Person("a little bit of Mary all night long"), - new Person("a little bit of Jessica here I am"), - }; - } - - public string Name - { - get { return _name; } - set { this.RaiseAndSetIfChanged(ref _name, value); } - } - - public List People { get; set; } - } - - public class Person - { - private string _name; - - public Person(string name) - { - _name = name; - } - - public string Name - { - get { return _name; } - set { _name = value; } - } - } -} \ No newline at end of file diff --git a/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs b/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000000..75664f8188 --- /dev/null +++ b/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,22 @@ +// 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.Collections.Generic; + +namespace XamlTestApplication.ViewModels +{ + public class MainWindowViewModel + { + public MainWindowViewModel() + { + Items = new List(); + + for (int i = 0; i < 10; ++i) + { + Items.Add(new TestItem($"Item {i}", $"Item {i} Value")); + } + } + + public List Items { get; } + } +} diff --git a/samples/XamlTestApplication/ViewModels/TestItem.cs b/samples/XamlTestApplication/ViewModels/TestItem.cs new file mode 100644 index 0000000000..6b711be54f --- /dev/null +++ b/samples/XamlTestApplication/ViewModels/TestItem.cs @@ -0,0 +1,17 @@ +// 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 XamlTestApplication.ViewModels +{ + public class TestItem + { + public TestItem(string header, string subheader) + { + Header = header; + SubHeader = subheader; + } + + public string Header { get; } + public string SubHeader { get; } + } +} diff --git a/samples/XamlTestApplication/Views/MainWindow.cs b/samples/XamlTestApplication/Views/MainWindow.cs index dc218641bf..fe2dd3d347 100644 --- a/samples/XamlTestApplication/Views/MainWindow.cs +++ b/samples/XamlTestApplication/Views/MainWindow.cs @@ -10,6 +10,7 @@ using OmniXaml; using Perspex.Controls; using Perspex.Diagnostics; using Perspex.Markup.Xaml; +using XamlTestApplication.ViewModels; namespace XamlTestApplication.Views { @@ -18,7 +19,7 @@ namespace XamlTestApplication.Views public MainWindow() { InitializeComponent(); - + DataContext = new MainWindowViewModel(); DevTools.Attach(this); } diff --git a/samples/XamlTestApplication/Views/MainWindow.paml b/samples/XamlTestApplication/Views/MainWindow.paml index 0f37faa54c..baa5c9d5dd 100644 --- a/samples/XamlTestApplication/Views/MainWindow.paml +++ b/samples/XamlTestApplication/Views/MainWindow.paml @@ -1,6 +1,7 @@  @@ -49,20 +50,14 @@ - - - - - - - - - - - - - - + + + + + + + + diff --git a/samples/XamlTestApplication/XamlTestApplication.csproj b/samples/XamlTestApplication/XamlTestApplication.csproj index d70e2e3297..c5b09898a6 100644 --- a/samples/XamlTestApplication/XamlTestApplication.csproj +++ b/samples/XamlTestApplication/XamlTestApplication.csproj @@ -77,9 +77,10 @@ - + + diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs index b51aae5969..06d78ab753 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs @@ -15,7 +15,7 @@ namespace Perspex.Markup.Xaml.Context public PerspexObjectAssembler(IWiringContext wiringContext, ObjectAssemblerSettings objectAssemblerSettings = null) { var mapping = new DeferredLoaderMapping(); - mapping.Map(template => template.Content, new TemplateLoader()); + mapping.Map(template => template.Content, new TemplateLoader()); var assembler = new ObjectAssembler(wiringContext, new TopDownValueContext(), objectAssemblerSettings); _objectAssembler = new TemplateHostingObjectAssembler(assembler, mapping); diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs index b2ab8c06ae..e5af8d5d4c 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs @@ -107,7 +107,7 @@ namespace Perspex.Markup.Xaml.Context new ContentPropertyDefinition(typeof(Style), "Setters"), new ContentPropertyDefinition(typeof(TextBlock), "Text"), new ContentPropertyDefinition(typeof(TextBox), "Text"), - new ContentPropertyDefinition(typeof(XamlDataTemplate), "Content"), + new ContentPropertyDefinition(typeof(DataTemplate), "Content"), }; contentPropertyProvider.AddAll(contentProperties); diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 1461be589a..1aa1e5080b 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 1461be589a5d39d378fd177648c61a780271c742 +Subproject commit 1aa1e5080b04ccf40baef4420ee29ffef9303811 diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 2bce8946ac..e2374ef20b 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -242,7 +242,7 @@ - + diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs index 49e5ce48a2..e0b51e89f0 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs @@ -9,7 +9,7 @@ using Perspex.Controls.Templates; namespace Perspex.Markup.Xaml.Templates { [ContentProperty("Content")] - public class XamlDataTemplate : IDataTemplate + public class DataTemplate : IDataTemplate { private bool MyMatch(object data) { From 7ae7f56a280a725d2be10e36dfaea6f60193f5b7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Sep 2015 22:37:16 +0200 Subject: [PATCH 04/54] Updated OmniXAML. --- src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 1aa1e5080b..49e6ec001f 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 1aa1e5080b04ccf40baef4420ee29ffef9303811 +Subproject commit 49e6ec001f5873cf2290e0bc1f6f06ca9b9cf808 From 9a207b072ad1b2bbdd0f6747a7e3d0673fb30a5b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Sep 2015 22:38:19 +0200 Subject: [PATCH 05/54] Added missing OmniXAML file. --- src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index e2374ef20b..1f0e96020d 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -58,6 +58,7 @@ + From a0509caede3283d1e4b40a526c3c818e8aa672a4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Sep 2015 23:34:10 +0200 Subject: [PATCH 06/54] Renamed TreeDataTemplate -> FuncTreeDataTemplate. --- samples/TestApplication/Program.cs | 2 +- samples/XamlTestApplication/Program.cs | 2 +- .../Generators/TreeItemContainerGenerator.cs | 2 +- src/Perspex.Controls/Perspex.Controls.csproj | 4 ++-- ...DataTemplate.cs => FuncTreeDataTemplate.cs} | 18 +++++++++--------- ...Template`1.cs => FuncTreeDataTemplate`1.cs} | 18 +++++++++--------- .../Views/LogicalTreeView.cs | 2 +- .../Views/VisualTreeView.cs | 2 +- 8 files changed, 25 insertions(+), 25 deletions(-) rename src/Perspex.Controls/Templates/{TreeDataTemplate.cs => FuncTreeDataTemplate.cs} (89%) rename src/Perspex.Controls/Templates/{TreeDataTemplate`1.cs => FuncTreeDataTemplate`1.cs} (88%) diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index 147a9b0f7c..ce9e4a540d 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -86,7 +86,7 @@ namespace TestApplication { DataTemplates = new DataTemplates { - new TreeDataTemplate( + new FuncTreeDataTemplate( x => new TextBlock { Text = x.Name }, x => x.Children, x => true), diff --git a/samples/XamlTestApplication/Program.cs b/samples/XamlTestApplication/Program.cs index d115b67809..c79fa68899 100644 --- a/samples/XamlTestApplication/Program.cs +++ b/samples/XamlTestApplication/Program.cs @@ -40,7 +40,7 @@ namespace XamlTestApplication { DataTemplates = new DataTemplates { - new TreeDataTemplate( + new FuncTreeDataTemplate( x => new TextBlock { Text = x.Name }, x => x.Children, x => true), diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index 3dabba7aad..4f35084d70 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -199,7 +199,7 @@ namespace Perspex.Controls.Generators if (treeTemplate == null) { - treeTemplate = new TreeDataTemplate(typeof(object), template.Build, x => null); + treeTemplate = new FuncTreeDataTemplate(typeof(object), template.Build, x => null); } return treeTemplate; diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 91b85d45eb..9085848a74 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -127,7 +127,7 @@ - + @@ -154,7 +154,7 @@ - + diff --git a/src/Perspex.Controls/Templates/TreeDataTemplate.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs similarity index 89% rename from src/Perspex.Controls/Templates/TreeDataTemplate.cs rename to src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs index fc99c20d53..0442f9a649 100644 --- a/src/Perspex.Controls/Templates/TreeDataTemplate.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs @@ -10,14 +10,14 @@ namespace Perspex.Controls.Templates /// /// A template used to build hierachical data. /// - public class TreeDataTemplate : FuncDataTemplate, ITreeDataTemplate + public class FuncTreeDataTemplate : FuncDataTemplate, ITreeDataTemplate { private readonly Func _itemsSelector; private readonly Func _isExpanded; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The type of data which the data template matches. /// @@ -27,7 +27,7 @@ namespace Perspex.Controls.Templates /// A function which when passed an object of returns the child /// items. /// - public TreeDataTemplate( + public FuncTreeDataTemplate( Type type, Func build, Func itemsSelector) @@ -36,7 +36,7 @@ namespace Perspex.Controls.Templates } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The type of data which the data template matches. /// @@ -50,7 +50,7 @@ namespace Perspex.Controls.Templates /// A function which when passed an object of returns the the /// initial expanded state of the node. /// - public TreeDataTemplate( + public FuncTreeDataTemplate( Type type, Func build, Func itemsSelector, @@ -60,7 +60,7 @@ namespace Perspex.Controls.Templates } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A function which determines whether the data template matches the specified data. @@ -71,7 +71,7 @@ namespace Perspex.Controls.Templates /// /// A function which when passed a matching object returns the child items. /// - public TreeDataTemplate( + public FuncTreeDataTemplate( Func match, Func build, Func itemsSelector) @@ -81,7 +81,7 @@ namespace Perspex.Controls.Templates } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A function which determines whether the data template matches the specified data. @@ -96,7 +96,7 @@ namespace Perspex.Controls.Templates /// A function which when passed a matching object returns the the initial expanded state /// of the node. /// - public TreeDataTemplate( + public FuncTreeDataTemplate( Func match, Func build, Func itemsSelector, diff --git a/src/Perspex.Controls/Templates/TreeDataTemplate`1.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs similarity index 88% rename from src/Perspex.Controls/Templates/TreeDataTemplate`1.cs rename to src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs index 68b3ebbf6d..18de28feb6 100644 --- a/src/Perspex.Controls/Templates/TreeDataTemplate`1.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs @@ -10,10 +10,10 @@ namespace Perspex.Controls.Templates /// A template used to build hierachical data. /// /// The type of the template's data. - public class TreeDataTemplate : TreeDataTemplate + public class FuncTreeDataTemplate : FuncTreeDataTemplate { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A function which when passed an object of returns a control. @@ -22,7 +22,7 @@ namespace Perspex.Controls.Templates /// A function which when passed an object of returns the child /// items. /// - public TreeDataTemplate( + public FuncTreeDataTemplate( Func build, Func itemsSelector) : base( @@ -33,7 +33,7 @@ namespace Perspex.Controls.Templates } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A function which when passed an object of returns a control. @@ -46,7 +46,7 @@ namespace Perspex.Controls.Templates /// A function which when passed an object of returns the the /// initial expanded state of the node. /// - public TreeDataTemplate( + public FuncTreeDataTemplate( Func build, Func itemsSelector, Func isExpanded) @@ -59,7 +59,7 @@ namespace Perspex.Controls.Templates } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A function which determines whether the data template matches the specified data. @@ -70,7 +70,7 @@ namespace Perspex.Controls.Templates /// /// A function which when passed a matching object returns the child items. /// - public TreeDataTemplate( + public FuncTreeDataTemplate( Func match, Func build, Func itemsSelector) @@ -82,7 +82,7 @@ namespace Perspex.Controls.Templates } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A function which determines whether the data template matches the specified data. @@ -97,7 +97,7 @@ namespace Perspex.Controls.Templates /// A function which when passed a matching object returns the the initial expanded state /// of the node. /// - public TreeDataTemplate( + public FuncTreeDataTemplate( Func match, Func build, Func itemsSelector, diff --git a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs b/src/Perspex.Diagnostics/Views/LogicalTreeView.cs index d68bc71fee..276506bf09 100644 --- a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs +++ b/src/Perspex.Diagnostics/Views/LogicalTreeView.cs @@ -48,7 +48,7 @@ namespace Perspex.Diagnostics.Views { DataTemplates = new DataTemplates { - new TreeDataTemplate(GetHeader, x => x.Children), + new FuncTreeDataTemplate(GetHeader, x => x.Children), }, [!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), }), diff --git a/src/Perspex.Diagnostics/Views/VisualTreeView.cs b/src/Perspex.Diagnostics/Views/VisualTreeView.cs index 485ed264aa..df7277e396 100644 --- a/src/Perspex.Diagnostics/Views/VisualTreeView.cs +++ b/src/Perspex.Diagnostics/Views/VisualTreeView.cs @@ -49,7 +49,7 @@ namespace Perspex.Diagnostics.Views { DataTemplates = new DataTemplates { - new TreeDataTemplate(GetHeader, x => x.Children), + new FuncTreeDataTemplate(GetHeader, x => x.Children), }, [!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), }), From 1020f8c9b3de5ba8b75b52c506c4de3320661712 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Sep 2015 23:36:58 +0200 Subject: [PATCH 07/54] Tidied up DataTemplate. --- .../Templates/DataTemplate.cs | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs index e0b51e89f0..9b1bf4731c 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs @@ -11,35 +11,30 @@ namespace Perspex.Markup.Xaml.Templates [ContentProperty("Content")] public class DataTemplate : IDataTemplate { - private bool MyMatch(object data) + public Type DataType { get; set; } + + public TemplateContent Content { get; set; } + + public bool Match(object data) { if (DataType == null) { - throw new InvalidOperationException("XAML DataTemplates must have a DataType"); + throw new InvalidOperationException("DataTemplate must have a DataType."); } return DataType == data.GetType(); } - private Control CreateVisualTreeForItem(object data) - { - var visualTreeForItem = Content.Load(); - visualTreeForItem.DataContext = data; - return visualTreeForItem; - } - - public Type DataType { get; set; } - - public TemplateContent Content { get; set; } - public IControl Build(object param) { return CreateVisualTreeForItem(param); } - public bool Match(object data) + private Control CreateVisualTreeForItem(object data) { - return MyMatch(data); + var visualTreeForItem = Content.Load(); + visualTreeForItem.DataContext = data; + return visualTreeForItem; } } } \ No newline at end of file From d581a1785d3c744b6f12f62f06b8d6e25de20401 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Sep 2015 23:37:33 +0200 Subject: [PATCH 08/54] Fixed test DataTemplate. --- samples/XamlTestApplication/Views/MainWindow.paml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/XamlTestApplication/Views/MainWindow.paml b/samples/XamlTestApplication/Views/MainWindow.paml index baa5c9d5dd..b94dda826f 100644 --- a/samples/XamlTestApplication/Views/MainWindow.paml +++ b/samples/XamlTestApplication/Views/MainWindow.paml @@ -53,8 +53,10 @@ - - + + + + From 5cd27f7e8ffbabc1db6cdb017a8e8dc7ffe3438d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Sep 2015 23:56:31 +0200 Subject: [PATCH 09/54] Started implementing TreeDataTemplate. Binding system needs to be updated first. --- .../ViewModels/MainWindowViewModel.cs | 36 +++++++++++++ .../XamlTestApplication/Views/MainWindow.paml | 26 +++++++--- samples/XamlTestApplication/Views/TestNode.cs | 14 +++++ .../XamlTestApplication.csproj | 1 + .../Perspex.Markup.Xaml.csproj | 1 + .../Templates/DataTemplate.cs | 8 +-- .../Templates/TreeDataTemplate.cs | 52 +++++++++++++++++++ 7 files changed, 123 insertions(+), 15 deletions(-) create mode 100644 samples/XamlTestApplication/Views/TestNode.cs create mode 100644 src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs diff --git a/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs b/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs index 75664f8188..1523299d41 100644 --- a/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs +++ b/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs @@ -15,8 +15,44 @@ namespace XamlTestApplication.ViewModels { Items.Add(new TestItem($"Item {i}", $"Item {i} Value")); } + + Nodes = new List + { + new TestNode + { + Header = "Root", + SubHeader = "Root Item", + Children = new[] + { + new TestNode + { + Header = "Child 1", + SubHeader = "Child 1 Value", + }, + new TestNode + { + Header = "Child 2", + SubHeader = "Child 2 Value", + Children = new[] + { + new TestNode + { + Header = "Grandchild", + SubHeader = "Grandchild Value", + }, + new TestNode + { + Header = "Grandmaster Flash", + SubHeader = "White Lines", + }, + } + }, + } + } + }; } public List Items { get; } + public List Nodes { get; } } } diff --git a/samples/XamlTestApplication/Views/MainWindow.paml b/samples/XamlTestApplication/Views/MainWindow.paml index b94dda826f..7fcf6bf749 100644 --- a/samples/XamlTestApplication/Views/MainWindow.paml +++ b/samples/XamlTestApplication/Views/MainWindow.paml @@ -60,16 +60,26 @@ - + + + + + + + + + + + + + - - - - - - + + - + + + diff --git a/samples/XamlTestApplication/Views/TestNode.cs b/samples/XamlTestApplication/Views/TestNode.cs new file mode 100644 index 0000000000..94c8fb6052 --- /dev/null +++ b/samples/XamlTestApplication/Views/TestNode.cs @@ -0,0 +1,14 @@ +// 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.Collections.Generic; + +namespace XamlTestApplication.ViewModels +{ + public class TestNode + { + public string Header { get; set; } + public string SubHeader { get; set; } + public IEnumerable Children { get; set; } + } +} diff --git a/samples/XamlTestApplication/XamlTestApplication.csproj b/samples/XamlTestApplication/XamlTestApplication.csproj index c5b09898a6..28a5382d7e 100644 --- a/samples/XamlTestApplication/XamlTestApplication.csproj +++ b/samples/XamlTestApplication/XamlTestApplication.csproj @@ -80,6 +80,7 @@ + diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 1f0e96020d..06eb0f4792 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -245,6 +245,7 @@ + diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs index 9b1bf4731c..aa07179dac 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs @@ -12,7 +12,6 @@ namespace Perspex.Markup.Xaml.Templates public class DataTemplate : IDataTemplate { public Type DataType { get; set; } - public TemplateContent Content { get; set; } public bool Match(object data) @@ -25,12 +24,7 @@ namespace Perspex.Markup.Xaml.Templates return DataType == data.GetType(); } - public IControl Build(object param) - { - return CreateVisualTreeForItem(param); - } - - private Control CreateVisualTreeForItem(object data) + public IControl Build(object data) { var visualTreeForItem = Content.Load(); visualTreeForItem.DataContext = data; diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs new file mode 100644 index 0000000000..e66d12ba75 --- /dev/null +++ b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -0,0 +1,52 @@ +// 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; +using OmniXaml.Attributes; +using Perspex.Controls; +using Perspex.Controls.Templates; +using Perspex.Markup.Xaml.DataBinding; + +namespace Perspex.Markup.Xaml.Templates +{ + [ContentProperty("Content")] + public class TreeDataTemplate : ITreeDataTemplate + { + public Type DataType { get; set; } + public TemplateContent Content { get; set; } + public XamlBinding ItemsSource { get; set; } + + public bool Match(object data) + { + if (DataType == null) + { + throw new InvalidOperationException("DataTemplate must have a DataType."); + } + + return DataType == data.GetType(); + } + + public IEnumerable ItemsSelector(object item) + { + if (ItemsSource != null) + { + // TODO: Get value of ItemsSource here. + } + + return null; + } + + public bool IsExpanded(object item) + { + return true; + } + + public IControl Build(object data) + { + var visualTreeForItem = Content.Load(); + visualTreeForItem.DataContext = data; + return visualTreeForItem; + } + } +} \ No newline at end of file From 005cd231cc3b4aaec9e65321b0a49f3f8dd127fc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Sep 2015 02:37:31 +0200 Subject: [PATCH 10/54] Initial implementation of ExpressionObserver. --- Perspex.sln | 14 ++ Perspex.v2.ncrunchsolution | Bin 1836 -> 2342 bytes .../Perspex.Markup/Binding/ExpressionNode.cs | 103 +++++++++++ .../Binding/ExpressionNodeBuilder.cs | 74 ++++++++ .../Binding/ExpressionObserver.cs | 55 ++++++ .../Perspex.Markup/Binding/ExpressionValue.cs | 26 +++ .../Binding/PropertyAccessorNode.cs | 67 +++++++ .../Perspex.Markup/Perspex.Markup.csproj | 93 ++++++++++ .../Perspex.Markup/Properties/AssemblyInfo.cs | 30 ++++ src/Markup/Perspex.Markup/packages.config | 13 ++ .../Binding/ExpressionNodeBuilderTests.cs | 29 ++++ .../Binding/ExpressionObserverTests.cs | 163 ++++++++++++++++++ .../Binding/NotifyingBase.cs | 38 ++++ .../Perspex.Markup.UnitTests.csproj | 103 +++++++++++ .../Properties/AssemblyInfo.cs | 36 ++++ .../Perspex.Markup.UnitTests/packages.config | 14 ++ 16 files changed, 858 insertions(+) create mode 100644 src/Markup/Perspex.Markup/Binding/ExpressionNode.cs create mode 100644 src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs create mode 100644 src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs create mode 100644 src/Markup/Perspex.Markup/Binding/ExpressionValue.cs create mode 100644 src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs create mode 100644 src/Markup/Perspex.Markup/Perspex.Markup.csproj create mode 100644 src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs create mode 100644 src/Markup/Perspex.Markup/packages.config create mode 100644 tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs create mode 100644 tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs create mode 100644 tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs create mode 100644 tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj create mode 100644 tests/Perspex.Markup.UnitTests/Properties/AssemblyInfo.cs create mode 100644 tests/Perspex.Markup.UnitTests/packages.config diff --git a/Perspex.sln b/Perspex.sln index 22b196d2c8..3b97ab21cf 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -96,6 +96,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.HtmlRenderer", "src EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PlatformSupport", "src\Shared\PlatformSupport\PlatformSupport.shproj", "{E4D9629C-F168-4224-3F51-A5E482FFBC42}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup", "src\Markup\Perspex.Markup\Perspex.Markup.csproj", "{6417E941-21BC-467B-A771-0DE389353CE6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup.UnitTests", "tests\Perspex.Markup.UnitTests\Perspex.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 @@ -235,6 +239,14 @@ Global {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.Build.0 = Release|Any CPU + {6417E941-21BC-467B-A771-0DE389353CE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6417E941-21BC-467B-A771-0DE389353CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6417E941-21BC-467B-A771-0DE389353CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6417E941-21BC-467B-A771-0DE389353CE6}.Release|Any CPU.Build.0 = Release|Any CPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -260,5 +272,7 @@ Global {54F237D5-A70A-4752-9656-0C70B1A7B047} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} {FB05AC90-89BA-4F2F-A924-F37875FB547C} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} {E4D9629C-F168-4224-3F51-A5E482FFBC42} = {A689DEF5-D50F-4975-8B72-124C9EB54066} + {6417E941-21BC-467B-A771-0DE389353CE6} = {8B6A8209-894F-4BA1-B880-965FD453982C} + {8EF392D5-1416-45AA-9956-7CBBC3229E8A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection EndGlobal diff --git a/Perspex.v2.ncrunchsolution b/Perspex.v2.ncrunchsolution index fe4feb2cb68b687b4720bc1a47cbec77763ab35f..56e7a57022a3819f4a5ae943d50d65dc44d2efa5 100644 GIT binary patch literal 2342 zcmds(O>fgc5Qg7#CH@0vq=pg+35m*5R7i!?R23~B2Va8gHnfh7ybc9^Jn+nH9j8@$ z2u?1@+V$>y?7Tbkde?t`@9R{R3N6&=E2B~e-d`}gL|SXQ)FgVM9X;jQ*BE`FTpww* z!mV$j7wDcps_p#W5$VExPw=mF#W*9T0t!5t4{o4u;A!s88Vpn8u zLDp>V_mO>~LXBov&A>4CJQe!xcpt;InYBCAW6zb<>jtc~uBoiCuAl|7*fQ{p%6`LY z>{iEEm5x4fCggg9zQlfxG=md1HZ~Xw!!7@;Dt#jLMSbX)Y-g2P+^6g??sAi;??&p2 zd%}5OxzDzYdNs}_Ft=RYZmtb=7pX7q&r4U0|1kv!M_4LFqCVokyx?JOkQmcr?ZHn*P##QcZ}G@#R-|SDe@lmUiFR%J;wH z!HVP;db?!?+%K{1X_q@7XN4)UT@AIz`z8G=-a{ldPtmrgL%T*wv_xm3_E6F$Z@4uFtEElM}B@?OX|dmqz}ze;G$;jDhig#G&izgZ^YjHsi@X9J2s8Rt13o diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs new file mode 100644 index 0000000000..6e21887dea --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs @@ -0,0 +1,103 @@ +// 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.Subjects; + +namespace Perspex.Markup.Binding +{ + public abstract class ExpressionNode : IObservable + { + private object _target; + + private Subject _subject; + + private ExpressionValue _value = ExpressionValue.None; + + public ExpressionNode Next + { + get; + set; + } + + public object Target + { + get { return _target; } + set + { + if (_target != null) + { + Unsubscribe(_target); + } + + _target = value; + + if (_target != null) + { + Subscribe(_target); + } + else + { + CurrentValue = ExpressionValue.None; + } + + if (Next != null) + { + Next.Target = CurrentValue.Value; + } + } + } + + public ExpressionValue CurrentValue + { + get + { + return _value; + } + + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + _value = value; + + if (Next != null) + { + Next.Target = value.Value; + } + + if (_subject != null) + { + _subject.OnNext(value); + } + } + } + + public IDisposable Subscribe(IObserver observer) + { + if (Next != null) + { + return Next.Subscribe(observer); + } + else + { + if (_subject == null) + { + _subject = new Subject(); + } + + observer.OnNext(CurrentValue); + return _subject.Subscribe(observer); + } + } + + protected abstract void Subscribe(object target); + + protected abstract void Unsubscribe(object target); + + } +} diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs new file mode 100644 index 0000000000..ebd4b85baf --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Perspex.Markup.Binding +{ + public class ExpressionNodeBuilder + { + public static IList Build(string expression) + { + if (string.IsNullOrWhiteSpace(expression)) + { + throw new ArgumentException("'expression' may not be empty."); + } + + var syntaxTree = CSharpSyntaxTree.ParseText(expression, new CSharpParseOptions(kind: SourceCodeKind.Interactive)); + var syntaxRoot = syntaxTree.GetRoot(); + var syntax = syntaxRoot.ChildNodes().SingleOrDefault()?.ChildNodes()?.SingleOrDefault() as ExpressionStatementSyntax; + + if (syntax != null) + { + var result = new List(); + + foreach (SyntaxNode node in syntax.ChildNodes()) + { + var identifier = node as IdentifierNameSyntax; + var memberAccess = node as MemberAccessExpressionSyntax; + + if (identifier != null) + { + result.Add(new PropertyAccessorNode(identifier.Identifier.ValueText)); + } + else if (memberAccess != null) + { + Build(memberAccess, result); + } + } + + for (int i = 0; i < result.Count - 1; ++i) + { + result[i].Next = result[i + 1]; + } + + return result; + } + else + { + throw new Exception($"Invalid expression: {expression}"); + } + } + + private static void Build(MemberAccessExpressionSyntax syntax, IList result) + { + foreach (SyntaxNode node in syntax.ChildNodes()) + { + var identifier = node as IdentifierNameSyntax; + var memberAccess = node as MemberAccessExpressionSyntax; + + if (identifier != null) + { + result.Add(new PropertyAccessorNode(identifier.Identifier.ValueText)); + } + else if (memberAccess != null) + { + Build(memberAccess, result); + } + } + } + } +} diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs new file mode 100644 index 0000000000..1ddd23e372 --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs @@ -0,0 +1,55 @@ +// 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.Linq; +using System.Reactive; +using System.Reactive.Disposables; + +namespace Perspex.Markup.Binding +{ + public class ExpressionObserver : ObservableBase + { + private int _count; + + public ExpressionObserver(object root, string expression) + { + Root = root; + Nodes = ExpressionNodeBuilder.Build(expression); + } + + public object Root { get; } + + public IList Nodes { get; } + + protected override IDisposable SubscribeCore(IObserver observer) + { + IncrementCount(); + + var subscription = Nodes[0].Subscribe(observer); + + return Disposable.Create(() => + { + DecrementCount(); + subscription.Dispose(); + }); + } + + private void IncrementCount() + { + if (_count++ == 0) + { + Nodes[0].Target = Root; + } + } + + private void DecrementCount() + { + if (--_count == 0) + { + Nodes[0].Target = null; + } + } + } +} diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs b/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs new file mode 100644 index 0000000000..7579f98db4 --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs @@ -0,0 +1,26 @@ +// 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.Markup.Binding +{ + public class ExpressionValue + { + public static readonly ExpressionValue None = new ExpressionValue(); + + public ExpressionValue(object value) + { + HasValue = true; + Value = value; + } + + private ExpressionValue() + { + HasValue = false; + } + + public bool HasValue { get; } + public object Value { get; } + } +} diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs new file mode 100644 index 0000000000..c2ad61bbb4 --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -0,0 +1,67 @@ +// 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.ComponentModel; +using System.Reflection; + +namespace Perspex.Markup.Binding +{ + public class PropertyAccessorNode : ExpressionNode + { + private PropertyInfo _propertyInfo; + + public PropertyAccessorNode(string propertyName) + { + PropertyName = propertyName; + } + + public string PropertyName { get; } + + protected override void Subscribe(object target) + { + var result = ExpressionValue.None; + + if (target != null) + { + _propertyInfo = target.GetType().GetTypeInfo().GetDeclaredProperty(PropertyName); + + if (_propertyInfo != null) + { + result = new ExpressionValue(_propertyInfo.GetValue(target)); + + var inpc = target as INotifyPropertyChanged; + + if (inpc != null) + { + inpc.PropertyChanged += PropertyChanged; + } + } + } + else + { + _propertyInfo = null; + } + + CurrentValue = result; + } + + private void PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == PropertyName) + { + CurrentValue = new ExpressionValue(_propertyInfo.GetValue(Target)); + } + } + + protected override void Unsubscribe(object target) + { + var inpc = target as INotifyPropertyChanged; + + if (inpc != null) + { + inpc.PropertyChanged -= PropertyChanged; + } + } + } +} diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj new file mode 100644 index 0000000000..5db8937f65 --- /dev/null +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -0,0 +1,93 @@ + + + + + 11.0 + Debug + AnyCPU + {6417E941-21BC-467B-A771-0DE389353CE6} + Library + Properties + Perspex.Markup + Perspex.Markup + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + ..\..\..\packages\Microsoft.CodeAnalysis.Common.1.1.0-beta1-20150812-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll + True + + + ..\..\..\packages\Microsoft.CodeAnalysis.CSharp.1.1.0-beta1-20150812-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll + True + + + ..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.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 + + + ..\..\..\packages\System.Reflection.Metadata.1.1.0-alpha-00009\lib\portable-net45+win8\System.Reflection.Metadata.dll + True + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs b/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c3a8edb54e --- /dev/null +++ b/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Markup")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Markup")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Markup/Perspex.Markup/packages.config b/src/Markup/Perspex.Markup/packages.config new file mode 100644 index 0000000000..aa2968fe54 --- /dev/null +++ b/src/Markup/Perspex.Markup/packages.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs new file mode 100644 index 0000000000..cdbbc1d1d3 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs @@ -0,0 +1,29 @@ +// 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.Markup.Binding; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionNodeBuilderTests + { + [Fact] + public void Should_Build_Single_Property() + { + var result = ExpressionNodeBuilder.Build("Foo"); + + Assert.Equal(1, result.Count); + Assert.IsType(result[0]); + } + + [Fact] + public void Should_Build_Property_Chain() + { + var result = ExpressionNodeBuilder.Build("Foo.Bar.Baz"); + + Assert.Equal(3, result.Count); + Assert.IsType(result[0]); + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs new file mode 100644 index 0000000000..22770868b5 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs @@ -0,0 +1,163 @@ +// 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 Perspex.Markup.Binding; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionObserverTests + { + [Fact] + public async void Should_Get_Simple_Property_Value() + { + var data = new { Foo = "foo" }; + var target = new ExpressionObserver(data, "Foo"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal("foo", result.Value); + } + + [Fact] + public async void Should_Get_Simple_Property_Chain() + { + var data = new { Foo = new { Bar = new { Baz = "baz" } } }; + var target = new ExpressionObserver(data, "Foo.Bar.Baz"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal("baz", result.Value); + } + + [Fact] + public async void Should_Not_Have_Value_For_Broken_Chain() + { + var data = new { Foo = new { Bar = 1 } }; + var target = new ExpressionObserver(data, "Foo.Bar.Baz"); + var result = await target.Take(1); + + Assert.False(result.HasValue); + } + + [Fact] + public void Should_Track_Simple_Property_Value() + { + var data = new Class1 { Foo = "foo" }; + var target = new ExpressionObserver(data, "Foo"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Foo = "bar"; + + Assert.Equal(new[] { "foo", "bar" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + } + + [Fact] + public void Should_Track_End_Of_Property_Chain_Changing() + { + var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Class2.Bar"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Class2.Bar = "baz"; + + Assert.Equal(new[] { "bar", "baz" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + Assert.Equal(0, data.Class2.SubscriptionCount); + } + + [Fact] + public void Should_Track_Middle_Of_Property_Chain_Changing() + { + var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Class2.Bar"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + var old = data.Class2; + data.Class2 = new Class2 { Bar = "baz" }; + + Assert.Equal(new[] { "bar", "baz" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, old.SubscriptionCount); + } + + [Fact] + public void Should_Track_Middle_Of_Property_Chain_Breaking_Then_Mending() + { + var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Class2.Bar"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + var old = data.Class2; + data.Class2 = null; + data.Class2 = new Class2 { Bar = "baz" }; + + Assert.Equal(new[] { "bar", null, "baz" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, old.SubscriptionCount); + } + + private class Class1 : NotifyingBase + { + private string _foo; + private Class2 _class2; + + public string Foo + { + get { return _foo; } + set + { + _foo = value; + RaisePropertyChanged(nameof(Foo)); + } + } + + public Class2 Class2 + { + get { return _class2; } + set + { + _class2 = value; + RaisePropertyChanged(nameof(Class2)); + } + } + } + + private class Class2 : NotifyingBase + { + private string _bar; + + public string Bar + { + get { return _bar; } + set + { + _bar = value; + RaisePropertyChanged(nameof(Bar)); + } + } + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs b/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs new file mode 100644 index 0000000000..ba8cfc57f7 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs @@ -0,0 +1,38 @@ +// 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.ComponentModel; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class NotifyingBase : INotifyPropertyChanged + { + private PropertyChangedEventHandler _propertyChanged; + + public event PropertyChangedEventHandler PropertyChanged + { + add + { + _propertyChanged += value; + ++SubscriptionCount; + } + + remove + { + _propertyChanged -= value; + --SubscriptionCount; + } + } + + public int SubscriptionCount + { + get; + private set; + } + + protected void RaisePropertyChanged(string propertyName) + { + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj new file mode 100644 index 0000000000..f1c7abc376 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -0,0 +1,103 @@ + + + + + + + Debug + AnyCPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A} + Library + Properties + Perspex.Markup.UnitTests + Perspex.Markup.UnitTests + v4.6 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + True + + + + + + + + + ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll + True + + + ..\..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll + True + + + + + + + + + + + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/tests/Perspex.Markup.UnitTests/Properties/AssemblyInfo.cs b/tests/Perspex.Markup.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..34123fa8b5 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Markup.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Markup.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8ef392d5-1416-45aa-9956-7cbbc3229e8a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/Perspex.Markup.UnitTests/packages.config b/tests/Perspex.Markup.UnitTests/packages.config new file mode 100644 index 0000000000..0d1a6f02e7 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/packages.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file From cf3834755dbaac17683bc9517514f7c6bbbcf79a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Sep 2015 02:51:21 +0200 Subject: [PATCH 11/54] More ExpressionObserver tests. --- .../Perspex.Markup/Binding/ExpressionNode.cs | 4 +- .../Binding/PropertyAccessorNode.cs | 2 +- .../Binding/ExpressionObserverTests.cs | 78 +++++++++++++------ .../Binding/NotifyingBase.cs | 8 +- .../Perspex.Markup.UnitTests.csproj | 1 + 5 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs index 6e21887dea..64fbe20873 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs @@ -35,7 +35,7 @@ namespace Perspex.Markup.Binding if (_target != null) { - Subscribe(_target); + SubscribeAndUpdate(_target); } else { @@ -95,7 +95,7 @@ namespace Perspex.Markup.Binding } } - protected abstract void Subscribe(object target); + protected abstract void SubscribeAndUpdate(object target); protected abstract void Unsubscribe(object target); diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index c2ad61bbb4..1c48d272d8 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -18,7 +18,7 @@ namespace Perspex.Markup.Binding public string PropertyName { get; } - protected override void Subscribe(object target) + protected override void SubscribeAndUpdate(object target) { var result = ExpressionValue.None; diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs index 22770868b5..f564788f56 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs @@ -63,66 +63,94 @@ namespace Perspex.Markup.UnitTests.Binding [Fact] public void Should_Track_End_Of_Property_Chain_Changing() { - var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Class2.Bar"); + var data = new Class1 { Next = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x.Value)); - data.Class2.Bar = "baz"; + ((Class2)data.Next).Bar = "baz"; Assert.Equal(new[] { "bar", "baz" }, result); sub.Dispose(); Assert.Equal(0, data.SubscriptionCount); - Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, data.Next.SubscriptionCount); } [Fact] - public void Should_Track_Middle_Of_Property_Chain_Changing() + public void Should_Track_Property_Chain_Changing() { - var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Class2.Bar"); + var data = new Class1 { Next = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x.Value)); - var old = data.Class2; - data.Class2 = new Class2 { Bar = "baz" }; + var old = data.Next; + data.Next = new Class2 { Bar = "baz" }; Assert.Equal(new[] { "bar", "baz" }, result); sub.Dispose(); Assert.Equal(0, data.SubscriptionCount); - Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, data.Next.SubscriptionCount); Assert.Equal(0, old.SubscriptionCount); } [Fact] - public void Should_Track_Middle_Of_Property_Chain_Breaking_Then_Mending() + public void Should_Track_Property_Chain_Breaking_With_Null_Then_Mending() { - var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Class2.Bar"); + var data = new Class1 { Next = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x.Value)); - var old = data.Class2; - data.Class2 = null; - data.Class2 = new Class2 { Bar = "baz" }; + var old = data.Next; + data.Next = null; + data.Next = new Class2 { Bar = "baz" }; Assert.Equal(new[] { "bar", null, "baz" }, result); sub.Dispose(); Assert.Equal(0, data.SubscriptionCount); - Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, data.Next.SubscriptionCount); Assert.Equal(0, old.SubscriptionCount); } + [Fact] + public void Should_Track_Property_Chain_Breaking_With_Object_Then_Mending() + { + var data = new Class1 { Next = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Next.Bar"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + var old = data.Next; + var breaking = new WithoutBar(); + data.Next = breaking; + data.Next = new Class2 { Bar = "baz" }; + + Assert.Equal(new[] { "bar", null, "baz" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + Assert.Equal(0, data.Next.SubscriptionCount); + Assert.Equal(0, breaking.SubscriptionCount); + Assert.Equal(0, old.SubscriptionCount); + } + + private interface INext + { + int SubscriptionCount { get; } + } + private class Class1 : NotifyingBase { private string _foo; - private Class2 _class2; + private INext _next; public string Foo { @@ -134,18 +162,18 @@ namespace Perspex.Markup.UnitTests.Binding } } - public Class2 Class2 + public INext Next { - get { return _class2; } + get { return _next; } set { - _class2 = value; - RaisePropertyChanged(nameof(Class2)); + _next = value; + RaisePropertyChanged(nameof(Next)); } } } - private class Class2 : NotifyingBase + private class Class2 : NotifyingBase, INext { private string _bar; @@ -159,5 +187,9 @@ namespace Perspex.Markup.UnitTests.Binding } } } + + private class WithoutBar : NotifyingBase, INext + { + } } } diff --git a/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs b/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs index ba8cfc57f7..5cc6a32fea 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.ComponentModel; +using System.Linq; namespace Perspex.Markup.UnitTests.Binding { @@ -19,8 +20,11 @@ namespace Perspex.Markup.UnitTests.Binding remove { - _propertyChanged -= value; - --SubscriptionCount; + if (_propertyChanged?.GetInvocationList().Contains(value) == true) + { + _propertyChanged -= value; + --SubscriptionCount; + } } } diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index f1c7abc376..675e280df9 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -74,6 +74,7 @@ + From 6e264dccc446d36f2f3f25240b9a44e8288b1d8a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Sep 2015 20:22:44 +0200 Subject: [PATCH 12/54] Allow setting values via ExpressionObserver. --- .../Binding/ExpressionObserver.cs | 20 ++++++++ .../Binding/PropertyAccessorNode.cs | 11 +++++ .../Binding/ExpressionObserverTests.cs | 47 +++++++++++++++++++ .../Perspex.Markup.UnitTests.csproj | 3 ++ 4 files changed, 81 insertions(+) diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs index 1ddd23e372..4bc0192c9f 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs @@ -19,6 +19,26 @@ namespace Perspex.Markup.Binding Nodes = ExpressionNodeBuilder.Build(expression); } + public bool SetValue(object value) + { + var last = Nodes.Last() as PropertyAccessorNode; + + if (last != null) + { + try + { + IncrementCount(); + return last.SetValue(value); + } + finally + { + DecrementCount(); + } + } + + return false; + } + public object Root { get; } public IList Nodes { get; } diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index 1c48d272d8..351879d1b4 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -16,6 +16,17 @@ namespace Perspex.Markup.Binding PropertyName = propertyName; } + public bool SetValue(object value) + { + if (_propertyInfo != null) + { + _propertyInfo.SetValue(Target, value); + return true; + } + + return false; + } + public string PropertyName { get; } protected override void SubscribeAndUpdate(object target) diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs index f564788f56..79be969d4f 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs @@ -142,6 +142,53 @@ namespace Perspex.Markup.UnitTests.Binding Assert.Equal(0, old.SubscriptionCount); } + [Fact] + public void SetValue_Should_Set_Simple_Property_Value() + { + var data = new Class1 { Foo = "foo" }; + var target = new ExpressionObserver(data, "Foo"); + + Assert.True(target.SetValue("bar")); + Assert.Equal("bar", data.Foo); + } + + [Fact] + public void SetValue_Should_Set_Property_At_The_End_Of_Chain() + { + var data = new Class1 { Next = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Next.Bar"); + + Assert.True(target.SetValue("baz")); + Assert.Equal("baz", ((Class2)data.Next).Bar); + } + + [Fact] + public void SetValue_Should_Return_False_For_Missing_Property() + { + var data = new Class1 { Next = new WithoutBar()}; + var target = new ExpressionObserver(data, "Next.Bar"); + + Assert.False(target.SetValue("baz")); + } + + [Fact] + public void SetValue_Should_Return_False_For_Missing_Object() + { + var data = new Class1(); + var target = new ExpressionObserver(data, "Next.Bar"); + + Assert.False(target.SetValue("baz")); + } + + [Fact] + public void SetValue_Should_Throw_For_Wrong_Type() + { + var data = new Class1 { Foo = "foo" }; + var target = new ExpressionObserver(data, "Foo"); + + Assert.Throws(() => target.SetValue(1.2)); + } + private interface INext { int SubscriptionCount { get; } diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index 675e280df9..909f67155f 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -86,6 +86,9 @@ Perspex.Markup + + + From 772937ad1543e339b55eb10cadaec0fc9ea1fa6e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Sep 2015 20:29:35 +0200 Subject: [PATCH 13/54] Change ExpressionValue to struct. And add some docs. --- .../Perspex.Markup/Binding/ExpressionNode.cs | 5 ---- .../Binding/ExpressionObserver.cs | 23 ++++++++++++++++++ .../Perspex.Markup/Binding/ExpressionValue.cs | 24 ++++++++++++++----- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs index 64fbe20873..aec90ee070 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs @@ -58,11 +58,6 @@ namespace Perspex.Markup.Binding set { - if (value == null) - { - throw new ArgumentNullException("value"); - } - _value = value; if (Next != null) diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs index 4bc0192c9f..cdbeddda0c 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs @@ -9,16 +9,32 @@ using System.Reactive.Disposables; namespace Perspex.Markup.Binding { + /// + /// Observes the value of an expression on a root object. + /// public class ExpressionObserver : ObservableBase { private int _count; + /// + /// Initializes a new instance of the class. + /// + /// The root object. + /// The expression. public ExpressionObserver(object root, string expression) { Root = root; Nodes = ExpressionNodeBuilder.Build(expression); } + /// + /// Attempts to set the value of a property expression. + /// + /// The value to set. + /// + /// True if the value could be set; false if the expression does not evaluate to a + /// property. + /// public bool SetValue(object value) { var last = Nodes.Last() as PropertyAccessorNode; @@ -39,10 +55,17 @@ namespace Perspex.Markup.Binding return false; } + /// + /// Gets the root object that the expression is being observed on. + /// public object Root { get; } + /// + /// Gets a list of nodes representing the parts of the expression. + /// public IList Nodes { get; } + /// protected override IDisposable SubscribeCore(IObserver observer) { IncrementCount(); diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs b/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs index 7579f98db4..2441acda73 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs @@ -5,22 +5,34 @@ using System; namespace Perspex.Markup.Binding { - public class ExpressionValue + /// + /// Holds the value for an . + /// + public struct ExpressionValue { + /// + /// An that has no value. + /// public static readonly ExpressionValue None = new ExpressionValue(); + /// + /// Initializes a new instance of the struct. + /// + /// public ExpressionValue(object value) { HasValue = true; Value = value; } - private ExpressionValue() - { - HasValue = false; - } - + /// + /// Gets a value indicating whether the evaluated expression resulted in a value. + /// public bool HasValue { get; } + + /// + /// Gets a the result of the expression. + /// public object Value { get; } } } From 962c1ea01b35fb9c100a897d055a5e0a7fa1d44c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Sep 2015 20:48:53 +0200 Subject: [PATCH 14/54] Pass SetValue through expression chain. --- .../Perspex.Markup/Binding/ExpressionNode.cs | 8 +++-- .../Binding/ExpressionNodeBuilder.cs | 12 ++++--- .../Binding/ExpressionObserver.cs | 34 +++++++------------ .../Binding/PropertyAccessorNode.cs | 23 ++++++++----- .../Perspex.Markup/Properties/AssemblyInfo.cs | 2 ++ .../Binding/ExpressionNodeBuilderTests.cs | 18 ++++++++-- 6 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs index aec90ee070..114cd43b80 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs @@ -2,12 +2,11 @@ // 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.Subjects; namespace Perspex.Markup.Binding { - public abstract class ExpressionNode : IObservable + internal abstract class ExpressionNode : IObservable { private object _target; @@ -72,6 +71,11 @@ namespace Perspex.Markup.Binding } } + public virtual bool SetValue(object value) + { + return Next?.SetValue(value) ?? false; + } + public IDisposable Subscribe(IObserver observer) { if (Next != null) diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs index ebd4b85baf..7d493066c7 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs @@ -1,16 +1,18 @@ -using System; +// 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.Linq; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Perspex.Markup.Binding { - public class ExpressionNodeBuilder + internal class ExpressionNodeBuilder { - public static IList Build(string expression) + public static ExpressionNode Build(string expression) { if (string.IsNullOrWhiteSpace(expression)) { @@ -45,7 +47,7 @@ namespace Perspex.Markup.Binding result[i].Next = result[i + 1]; } - return result; + return result[0]; } else { diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs index cdbeddda0c..bd0b08b7c3 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs @@ -15,6 +15,7 @@ namespace Perspex.Markup.Binding public class ExpressionObserver : ObservableBase { private int _count; + private ExpressionNode _node; /// /// Initializes a new instance of the class. @@ -24,7 +25,7 @@ namespace Perspex.Markup.Binding public ExpressionObserver(object root, string expression) { Root = root; - Nodes = ExpressionNodeBuilder.Build(expression); + _node = ExpressionNodeBuilder.Build(expression); } /// @@ -37,22 +38,16 @@ namespace Perspex.Markup.Binding /// public bool SetValue(object value) { - var last = Nodes.Last() as PropertyAccessorNode; + IncrementCount(); - if (last != null) + try { - try - { - IncrementCount(); - return last.SetValue(value); - } - finally - { - DecrementCount(); - } + return _node.SetValue(value); + } + finally + { + DecrementCount(); } - - return false; } /// @@ -60,17 +55,12 @@ namespace Perspex.Markup.Binding /// public object Root { get; } - /// - /// Gets a list of nodes representing the parts of the expression. - /// - public IList Nodes { get; } - /// protected override IDisposable SubscribeCore(IObserver observer) { IncrementCount(); - var subscription = Nodes[0].Subscribe(observer); + var subscription = _node.Subscribe(observer); return Disposable.Create(() => { @@ -83,7 +73,7 @@ namespace Perspex.Markup.Binding { if (_count++ == 0) { - Nodes[0].Target = Root; + _node.Target = Root; } } @@ -91,7 +81,7 @@ namespace Perspex.Markup.Binding { if (--_count == 0) { - Nodes[0].Target = null; + _node.Target = null; } } } diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index 351879d1b4..27e931a089 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -7,7 +7,7 @@ using System.Reflection; namespace Perspex.Markup.Binding { - public class PropertyAccessorNode : ExpressionNode + internal class PropertyAccessorNode : ExpressionNode { private PropertyInfo _propertyInfo; @@ -16,19 +16,26 @@ namespace Perspex.Markup.Binding PropertyName = propertyName; } - public bool SetValue(object value) + public string PropertyName { get; } + + public override bool SetValue(object value) { - if (_propertyInfo != null) + if (Next != null) { - _propertyInfo.SetValue(Target, value); - return true; + return Next.SetValue(value); } + else + { + if (_propertyInfo != null) + { + _propertyInfo.SetValue(Target, value); + return true; + } - return false; + return false; + } } - public string PropertyName { get; } - protected override void SubscribeAndUpdate(object target) { var result = ExpressionValue.None; diff --git a/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs b/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs index c3a8edb54e..9076f81754 100644 --- a/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs +++ b/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs @@ -28,3 +28,5 @@ using System.Runtime.InteropServices; // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: InternalsVisibleTo("Perspex.Markup.UnitTests")] \ No newline at end of file diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs index cdbbc1d1d3..443d18cdef 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs @@ -1,6 +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. +using System.Collections.Generic; using Perspex.Markup.Binding; using Xunit; @@ -11,7 +12,7 @@ namespace Perspex.Markup.UnitTests.Binding [Fact] public void Should_Build_Single_Property() { - var result = ExpressionNodeBuilder.Build("Foo"); + var result = ToList(ExpressionNodeBuilder.Build("Foo")); Assert.Equal(1, result.Count); Assert.IsType(result[0]); @@ -20,10 +21,23 @@ namespace Perspex.Markup.UnitTests.Binding [Fact] public void Should_Build_Property_Chain() { - var result = ExpressionNodeBuilder.Build("Foo.Bar.Baz"); + var result = ToList(ExpressionNodeBuilder.Build("Foo.Bar.Baz")); Assert.Equal(3, result.Count); Assert.IsType(result[0]); } + + private List ToList(ExpressionNode node) + { + var result = new List(); + + while (node != null) + { + result.Add(node); + node = node.Next; + } + + return result; + } } } From 774a9c0911e9e8d590f9e65cce44ce3431d376a7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Sep 2015 22:05:59 +0200 Subject: [PATCH 15/54] Implemented binding negation. --- .../Perspex.Markup/Binding/ExpressionNode.cs | 9 ++- .../Binding/ExpressionNodeBuilder.cs | 67 ++++++++-------- .../Perspex.Markup/Binding/LogicalNotNode.cs | 49 ++++++++++++ .../Binding/PropertyAccessorNode.cs | 3 +- .../Perspex.Markup/Perspex.Markup.csproj | 1 + .../Binding/ExpressionNodeBuilderTests.cs | 27 +++++++ .../ExpressionObserverTests_Negation.cs | 77 +++++++++++++++++++ ...cs => ExpressionObserverTests_Property.cs} | 2 +- .../Perspex.Markup.UnitTests.csproj | 3 +- 9 files changed, 194 insertions(+), 44 deletions(-) create mode 100644 src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs create mode 100644 tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs rename tests/Perspex.Markup.UnitTests/Binding/{ExpressionObserverTests.cs => ExpressionObserverTests_Property.cs} (99%) diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs index 114cd43b80..ff40507e7e 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs @@ -14,12 +14,13 @@ namespace Perspex.Markup.Binding private ExpressionValue _value = ExpressionValue.None; - public ExpressionNode Next + public ExpressionNode(ExpressionNode next) { - get; - set; + Next = next; } + public ExpressionNode Next { get; } + public object Target { get { return _target; } @@ -76,7 +77,7 @@ namespace Perspex.Markup.Binding return Next?.SetValue(value) ?? false; } - public IDisposable Subscribe(IObserver observer) + public virtual IDisposable Subscribe(IObserver observer) { if (Next != null) { diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs index 7d493066c7..b37892be4c 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs @@ -21,33 +21,11 @@ namespace Perspex.Markup.Binding var syntaxTree = CSharpSyntaxTree.ParseText(expression, new CSharpParseOptions(kind: SourceCodeKind.Interactive)); var syntaxRoot = syntaxTree.GetRoot(); - var syntax = syntaxRoot.ChildNodes().SingleOrDefault()?.ChildNodes()?.SingleOrDefault() as ExpressionStatementSyntax; + var syntax = syntaxRoot.ChildNodes().SingleOrDefault()?.ChildNodes()?.SingleOrDefault(); if (syntax != null) { - var result = new List(); - - foreach (SyntaxNode node in syntax.ChildNodes()) - { - var identifier = node as IdentifierNameSyntax; - var memberAccess = node as MemberAccessExpressionSyntax; - - if (identifier != null) - { - result.Add(new PropertyAccessorNode(identifier.Identifier.ValueText)); - } - else if (memberAccess != null) - { - Build(memberAccess, result); - } - } - - for (int i = 0; i < result.Count - 1; ++i) - { - result[i].Next = result[i + 1]; - } - - return result[0]; + return Build(expression, syntax, null); } else { @@ -55,22 +33,37 @@ namespace Perspex.Markup.Binding } } - private static void Build(MemberAccessExpressionSyntax syntax, IList result) + private static ExpressionNode Build(string expression, SyntaxNode syntax, ExpressionNode next) { - foreach (SyntaxNode node in syntax.ChildNodes()) - { - var identifier = node as IdentifierNameSyntax; - var memberAccess = node as MemberAccessExpressionSyntax; + var expressionStatement = syntax as ExpressionStatementSyntax; + var identifier = syntax as IdentifierNameSyntax; + var memberAccess = syntax as MemberAccessExpressionSyntax; + var unaryExpression = syntax as PrefixUnaryExpressionSyntax; - if (identifier != null) - { - result.Add(new PropertyAccessorNode(identifier.Identifier.ValueText)); - } - else if (memberAccess != null) - { - Build(memberAccess, result); - } + if (expressionStatement != null) + { + return Build(expression, expressionStatement.Expression, next); + } + else if (identifier != null) + { + next = new PropertyAccessorNode(next, identifier.Identifier.ValueText); } + else if (memberAccess != null) + { + next = new PropertyAccessorNode(next, memberAccess.Name.Identifier.ValueText); + next = Build(expression, memberAccess.Expression, next); + } + else if (unaryExpression != null && unaryExpression.Kind() == SyntaxKind.LogicalNotExpression) + { + next = Build(expression, unaryExpression.Operand, next); + next = new LogicalNotNode(next); + } + else + { + throw new Exception($"Invalid expression: {expression}"); + } + + return next; } } } diff --git a/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs b/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs new file mode 100644 index 0000000000..55244e28eb --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/LogicalNotNode.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.Globalization; +using System.Reactive.Linq; + +namespace Perspex.Markup.Binding +{ + internal class LogicalNotNode : ExpressionNode + { + public LogicalNotNode(ExpressionNode next) + : base(next) + { + } + + public override IDisposable Subscribe(IObserver observer) + { + return Next.Select(x => Negate(x)).Subscribe(observer); + } + + protected override void SubscribeAndUpdate(object target) + { + CurrentValue = new ExpressionValue(target); + } + + protected override void Unsubscribe(object target) + { + } + + private ExpressionValue Negate(ExpressionValue v) + { + if (v.HasValue) + { + try + { + var boolean = Convert.ToBoolean(v.Value, CultureInfo.InvariantCulture); + return new ExpressionValue(!boolean); + } + catch + { + // TODO: Maybe should log something here. + } + } + + return ExpressionValue.None; + } + } +} diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index 27e931a089..da54212165 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -11,7 +11,8 @@ namespace Perspex.Markup.Binding { private PropertyInfo _propertyInfo; - public PropertyAccessorNode(string propertyName) + public PropertyAccessorNode(ExpressionNode next, string propertyName) + : base(next) { PropertyName = propertyName; } diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj index 5db8937f65..fd08d6e577 100644 --- a/src/Markup/Perspex.Markup/Perspex.Markup.csproj +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -36,6 +36,7 @@ + diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs index 443d18cdef..a6759d4a1f 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs @@ -25,6 +25,33 @@ namespace Perspex.Markup.UnitTests.Binding Assert.Equal(3, result.Count); Assert.IsType(result[0]); + Assert.IsType(result[1]); + Assert.IsType(result[2]); + } + + [Fact] + public void Should_Build_Negated_Property_Chain() + { + var result = ToList(ExpressionNodeBuilder.Build("!Foo.Bar.Baz")); + + Assert.Equal(4, result.Count); + Assert.IsType(result[0]); + Assert.IsType(result[1]); + Assert.IsType(result[2]); + Assert.IsType(result[3]); + } + + [Fact] + public void Should_Build_Double_Negated_Property_Chain() + { + var result = ToList(ExpressionNodeBuilder.Build("!!Foo.Bar.Baz")); + + Assert.Equal(5, result.Count); + Assert.IsType(result[0]); + Assert.IsType(result[1]); + Assert.IsType(result[2]); + Assert.IsType(result[3]); + Assert.IsType(result[4]); } private List ToList(ExpressionNode node) diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs new file mode 100644 index 0000000000..b38fb809bb --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs @@ -0,0 +1,77 @@ +// 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.Linq; +using Perspex.Markup.Binding; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionObserverTests_Negation + { + [Fact] + public async void Should_Negate_Boolean_Value() + { + var data = new { Foo = true }; + var target = new ExpressionObserver(data, "!Foo"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal(false, result.Value); + } + + [Fact] + public async void Should_Negate_0() + { + var data = new { Foo = 0 }; + var target = new ExpressionObserver(data, "!Foo"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal(true, result.Value); + } + + [Fact] + public async void Should_Negate_1() + { + var data = new { Foo = 1 }; + var target = new ExpressionObserver(data, "!Foo"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal(false, result.Value); + } + + [Fact] + public async void Should_Negate_False_String() + { + var data = new { Foo = "false" }; + var target = new ExpressionObserver(data, "!Foo"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal(true, result.Value); + } + + [Fact] + public async void Should_Negate_True_String() + { + var data = new { Foo = "True" }; + var target = new ExpressionObserver(data, "!Foo"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal(false, result.Value); + } + + [Fact] + public async void Should_Return_Empty_For_Value_Not_Convertible_To_Boolean() + { + var data = new { Foo = new object() }; + var target = new ExpressionObserver(data, "!Foo"); + var result = await target.Take(1); + + Assert.False(result.HasValue); + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs similarity index 99% rename from tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs rename to tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs index 79be969d4f..ccda547bd5 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs @@ -9,7 +9,7 @@ using Xunit; namespace Perspex.Markup.UnitTests.Binding { - public class ExpressionObserverTests + public class ExpressionObserverTests_Property { [Fact] public async void Should_Get_Simple_Property_Value() diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index 909f67155f..04c42cba55 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -72,7 +72,8 @@ - + + From 7072f0a66c5c234ed67f7a86a8ff76e532bfc814 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Sep 2015 23:42:19 +0200 Subject: [PATCH 16/54] Don't allow !ed expressions to be set. --- .../Perspex.Markup/Binding/LogicalNotNode.cs | 5 +++++ .../ExpressionObserverTests_Negation.cs | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs b/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs index 55244e28eb..ca35ea5f12 100644 --- a/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs +++ b/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs @@ -14,6 +14,11 @@ namespace Perspex.Markup.Binding { } + public override bool SetValue(object value) + { + throw new NotSupportedException("Cannot set a negated binding."); + } + public override IDisposable Subscribe(IObserver observer) { return Next.Select(x => Negate(x)).Subscribe(observer); diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs index b38fb809bb..614f27b34c 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs @@ -1,6 +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. +using System; using System.Reactive.Linq; using Perspex.Markup.Binding; using Xunit; @@ -64,6 +65,16 @@ namespace Perspex.Markup.UnitTests.Binding Assert.Equal(false, result.Value); } + [Fact] + public async void Should_Return_Empty_For_String_Not_Convertible_To_Boolean() + { + var data = new { Foo = "foo" }; + var target = new ExpressionObserver(data, "!Foo"); + var result = await target.Take(1); + + Assert.False(result.HasValue); + } + [Fact] public async void Should_Return_Empty_For_Value_Not_Convertible_To_Boolean() { @@ -73,5 +84,14 @@ namespace Perspex.Markup.UnitTests.Binding Assert.False(result.HasValue); } + + [Fact] + public void SetValue_Should_Throw() + { + var data = new { Foo = "foo" }; + var target = new ExpressionObserver(data, "!Foo"); + + Assert.Throws(() => target.SetValue("bar")); + } } } From e3b6ce41863b61643a3485dfdb5c94e8be4ffecf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 1 Oct 2015 01:08:37 +0200 Subject: [PATCH 17/54] Add support for indexers in binding expressions. --- .../Binding/ElementAccessorNode.cs | 107 +++++++++++++++++ .../Perspex.Markup/Binding/ExpressionNode.cs | 10 +- .../Binding/ExpressionNodeBuilder.cs | 36 ++++++ .../Perspex.Markup/Binding/LogicalNotNode.cs | 9 -- .../Binding/PropertyAccessorNode.cs | 16 +-- .../Perspex.Markup/Perspex.Markup.csproj | 1 + .../Binding/ExpressionNodeBuilderTests.cs | 70 +++++++++-- .../ExpressionObserverTests_Indexer.cs | 113 ++++++++++++++++++ .../Perspex.Markup.UnitTests.csproj | 1 + 9 files changed, 334 insertions(+), 29 deletions(-) create mode 100644 src/Markup/Perspex.Markup/Binding/ElementAccessorNode.cs create mode 100644 tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Indexer.cs diff --git a/src/Markup/Perspex.Markup/Binding/ElementAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/ElementAccessorNode.cs new file mode 100644 index 0000000000..d8d4e9979c --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ElementAccessorNode.cs @@ -0,0 +1,107 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Reflection; + +namespace Perspex.Markup.Binding +{ + internal class ElementAccessorNode : ExpressionNode + { + private int[] _intArgs; + + public ElementAccessorNode(ExpressionNode next, IList arguments) + : base(next) + { + Arguments = arguments; + + var intArgs = Arguments.OfType().ToArray(); + + if (intArgs.Length == arguments.Count) + { + _intArgs = intArgs; + } + } + + public IList Arguments { get; } + + protected override void SubscribeAndUpdate(object target) + { + CurrentValue = GetValue(target); + + var incc = target as INotifyCollectionChanged; + + if (incc != null) + { + incc.CollectionChanged += CollectionChanged; + } + } + + protected override void Unsubscribe(object target) + { + var incc = target as INotifyCollectionChanged; + + if (incc != null) + { + incc.CollectionChanged -= CollectionChanged; + } + } + + private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + bool update = false; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + update = _intArgs[0] >= e.NewStartingIndex; + break; + case NotifyCollectionChangedAction.Remove: + update = _intArgs[0] >= e.OldStartingIndex; + break; + case NotifyCollectionChangedAction.Replace: + update = _intArgs[0] >= e.NewStartingIndex && + _intArgs[0] < e.NewStartingIndex + e.NewItems.Count; + break; + case NotifyCollectionChangedAction.Move: + update = (_intArgs[0] >= e.NewStartingIndex && + _intArgs[0] < e.NewStartingIndex + e.NewItems.Count) || + (_intArgs[0] >= e.OldStartingIndex && + _intArgs[0] < e.OldStartingIndex + e.OldItems.Count); + break; + case NotifyCollectionChangedAction.Reset: + update = true; + break; + } + + if (update) + { + CurrentValue = GetValue(sender); + } + } + + private ExpressionValue GetValue(object target) + { + var typeInfo = target.GetType().GetTypeInfo(); + var list = target as IList; + + if (typeInfo.IsArray && _intArgs != null) + { + return new ExpressionValue(((Array)target).GetValue(_intArgs)); + } + else if (target is IList && _intArgs?.Length == 1) + { + if (_intArgs[0] < list.Count) + { + return new ExpressionValue(list[_intArgs[0]]); + } + } + + return ExpressionValue.None; + } + } +} diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs index ff40507e7e..4bb4356c30 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs @@ -95,9 +95,13 @@ namespace Perspex.Markup.Binding } } - protected abstract void SubscribeAndUpdate(object target); - - protected abstract void Unsubscribe(object target); + protected virtual void SubscribeAndUpdate(object target) + { + CurrentValue = new ExpressionValue(target); + } + protected virtual void Unsubscribe(object target) + { + } } } diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs index b37892be4c..abcba10452 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs @@ -39,6 +39,7 @@ namespace Perspex.Markup.Binding var identifier = syntax as IdentifierNameSyntax; var memberAccess = syntax as MemberAccessExpressionSyntax; var unaryExpression = syntax as PrefixUnaryExpressionSyntax; + var elementAccess = syntax as ElementAccessExpressionSyntax; if (expressionStatement != null) { @@ -58,6 +59,11 @@ namespace Perspex.Markup.Binding next = Build(expression, unaryExpression.Operand, next); next = new LogicalNotNode(next); } + else if (elementAccess != null) + { + next = Build(expression, elementAccess, next); + next = Build(expression, elementAccess.Expression, next); + } else { throw new Exception($"Invalid expression: {expression}"); @@ -65,5 +71,35 @@ namespace Perspex.Markup.Binding return next; } + + private static ExpressionNode Build(string expression, ElementAccessExpressionSyntax syntax, ExpressionNode next) + { + var argList = syntax.ArgumentList as BracketedArgumentListSyntax; + + if (argList != null) + { + var args = new List(); + + foreach (var arg in argList.Arguments) + { + var literal = arg.Expression as LiteralExpressionSyntax; + + if (literal != null) + { + args.Add(literal.Token.Value); + } + else + { + throw new Exception($"Invalid expression: {expression}"); + } + } + + return new ElementAccessorNode(next, args); + } + else + { + throw new Exception($"Invalid expression: {expression}"); + } + } } } diff --git a/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs b/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs index ca35ea5f12..6ea29deffa 100644 --- a/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs +++ b/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs @@ -24,15 +24,6 @@ namespace Perspex.Markup.Binding return Next.Select(x => Negate(x)).Subscribe(observer); } - protected override void SubscribeAndUpdate(object target) - { - CurrentValue = new ExpressionValue(target); - } - - protected override void Unsubscribe(object target) - { - } - private ExpressionValue Negate(ExpressionValue v) { if (v.HasValue) diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index da54212165..0a4b4b737e 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -65,14 +65,6 @@ namespace Perspex.Markup.Binding CurrentValue = result; } - private void PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == PropertyName) - { - CurrentValue = new ExpressionValue(_propertyInfo.GetValue(Target)); - } - } - protected override void Unsubscribe(object target) { var inpc = target as INotifyPropertyChanged; @@ -82,5 +74,13 @@ namespace Perspex.Markup.Binding inpc.PropertyChanged -= PropertyChanged; } } + + private void PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == PropertyName) + { + CurrentValue = new ExpressionValue(_propertyInfo.GetValue(Target)); + } + } } } diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj index fd08d6e577..7cd19704b7 100644 --- a/src/Markup/Perspex.Markup/Perspex.Markup.csproj +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -37,6 +37,7 @@ + diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs index a6759d4a1f..18cd6c3578 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Collections.Generic; +using System.Linq; using Perspex.Markup.Binding; using Xunit; @@ -24,9 +25,9 @@ namespace Perspex.Markup.UnitTests.Binding var result = ToList(ExpressionNodeBuilder.Build("Foo.Bar.Baz")); Assert.Equal(3, result.Count); - Assert.IsType(result[0]); - Assert.IsType(result[1]); - Assert.IsType(result[2]); + AssertIsProperty(result[0], "Foo"); + AssertIsProperty(result[1], "Bar"); + AssertIsProperty(result[2], "Baz"); } [Fact] @@ -36,9 +37,9 @@ namespace Perspex.Markup.UnitTests.Binding Assert.Equal(4, result.Count); Assert.IsType(result[0]); - Assert.IsType(result[1]); - Assert.IsType(result[2]); - Assert.IsType(result[3]); + AssertIsProperty(result[1], "Foo"); + AssertIsProperty(result[2], "Bar"); + AssertIsProperty(result[3], "Baz"); } [Fact] @@ -49,9 +50,60 @@ namespace Perspex.Markup.UnitTests.Binding Assert.Equal(5, result.Count); Assert.IsType(result[0]); Assert.IsType(result[1]); - Assert.IsType(result[2]); - Assert.IsType(result[3]); - Assert.IsType(result[4]); + AssertIsProperty(result[2], "Foo"); + AssertIsProperty(result[3], "Bar"); + AssertIsProperty(result[4], "Baz"); + } + + [Fact] + public void Should_Build_Indexed_Property() + { + var result = ToList(ExpressionNodeBuilder.Build("Foo[5]")); + + Assert.Equal(2, result.Count); + AssertIsProperty(result[0], "Foo"); + AssertIsIndexer(result[1], 5); + Assert.IsType(result[1]); + Assert.Equal(new object[] { 5 }, ((ElementAccessorNode)result[1]).Arguments.ToArray()); + } + + [Fact] + public void Should_Build_Multiple_Indexed_Property() + { + var result = ToList(ExpressionNodeBuilder.Build("Foo[5, 6]")); + + Assert.Equal(2, result.Count); + AssertIsProperty(result[0], "Foo"); + Assert.IsType(result[1]); + Assert.Equal(new object[] { 5, 6 }, ((ElementAccessorNode)result[1]).Arguments.ToArray()); + } + + [Fact] + public void Should_Build_Indexed_Property_In_Chain() + { + var result = ToList(ExpressionNodeBuilder.Build("Foo.Bar[5, 6].Baz")); + + Assert.Equal(4, result.Count); + AssertIsProperty(result[0], "Foo"); + AssertIsProperty(result[1], "Bar"); + Assert.IsType(result[2]); + AssertIsProperty(result[3], "Baz"); + } + + private void AssertIsProperty(ExpressionNode node, string name) + { + Assert.IsType(node); + + var p = (PropertyAccessorNode)node; + Assert.Equal(name, p.PropertyName); + } + + private void AssertIsIndexer(ExpressionNode node, params object[] args) + { + Assert.IsType(node); + + var e = (ElementAccessorNode)node; + Assert.Equal(e.Arguments.ToArray(), args.ToArray()); } private List ToList(ExpressionNode node) diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Indexer.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Indexer.cs new file mode 100644 index 0000000000..f544652c52 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Indexer.cs @@ -0,0 +1,113 @@ +// 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.Collections.ObjectModel; +using System.Reactive.Linq; +using Perspex.Markup.Binding; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionObserverTests_Indexer + { + [Fact] + public async void Should_Get_Array_Value() + { + var data = new { Foo = new [] { "foo", "bar" } }; + var target = new ExpressionObserver(data, "Foo[1]"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal("bar", result.Value); + } + + [Fact] + public async void Should_Get_MultiDimensional_Array_Value() + { + var data = new { Foo = new[,] { { "foo", "bar" }, { "baz", "qux" } } }; + var target = new ExpressionObserver(data, "Foo[1, 1]"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal("qux", result.Value); + } + + [Fact] + public async void Should_Get_List_Value() + { + var data = new { Foo = new List { "foo", "bar" } }; + var target = new ExpressionObserver(data, "Foo[1]"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal("bar", result.Value); + } + + [Fact] + public void Should_Track_INCC_Add() + { + var data = new { Foo = new ObservableCollection { "foo", "bar" } }; + var target = new ExpressionObserver(data, "Foo[2]"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Foo.Add("baz"); + + Assert.Equal(new[] { null, "baz" }, result); + } + + [Fact] + public void Should_Track_INCC_Remove() + { + var data = new { Foo = new ObservableCollection { "foo", "bar" } }; + var target = new ExpressionObserver(data, "Foo[0]"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Foo.RemoveAt(0); + + Assert.Equal(new[] { "foo", "bar" }, result); + } + + [Fact] + public void Should_Track_INCC_Replace() + { + var data = new { Foo = new ObservableCollection { "foo", "bar" } }; + var target = new ExpressionObserver(data, "Foo[1]"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Foo[1] = "baz"; + + Assert.Equal(new[] { "bar", "baz" }, result); + } + + [Fact] + public void Should_Track_INCC_Move() + { + var data = new { Foo = new ObservableCollection { "foo", "bar" } }; + var target = new ExpressionObserver(data, "Foo[1]"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Foo.Move(0, 1); + + Assert.Equal(new[] { "bar", "foo" }, result); + } + + [Fact] + public void Should_Track_INCC_Reset() + { + var data = new { Foo = new ObservableCollection { "foo", "bar" } }; + var target = new ExpressionObserver(data, "Foo[1]"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Foo.Clear(); + + Assert.Equal(new[] { "bar", null }, result); + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index 04c42cba55..eae2018e22 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -72,6 +72,7 @@ + From 0f5af3db6335a1d1b5e6a23453bd46e9d7d3f705 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 1 Oct 2015 01:08:37 +0200 Subject: [PATCH 18/54] Use Sprache for parsing expressions. Use sprache instead of Roslyn for parsing expressions. One test still failing. --- Perspex.v2.ncrunchsolution | Bin 2342 -> 1836 bytes .../Binding/ElementAccessorNode.cs | 107 +++++++++++++++++ .../Perspex.Markup/Binding/ExpressionNode.cs | 10 +- .../Binding/ExpressionNodeBuilder.cs | 51 ++++++-- .../Perspex.Markup/Binding/LogicalNotNode.cs | 9 -- .../Binding/PropertyAccessorNode.cs | 16 +-- .../CSharp/BracketedArgumentListSyntax.cs | 18 +++ .../Parsers/CSharp/CSharpSyntaxTree.cs | 17 +++ .../CSharp/ElementAccessExpressionSyntax.cs | 19 +++ .../CSharp/ExpressionStatementSyntax.cs | 15 +++ .../Parsers/CSharp/ExpressionSyntax.cs | 9 ++ .../CSharp/Grammar/ExpressionGrammar.cs | 64 ++++++++++ .../CSharp/Grammar/IdentifierGrammar.cs | 41 +++++++ .../Parsers/CSharp/Grammar/LiteralGrammar.cs | 20 ++++ .../Parsers/CSharp/IdentifierSyntax.cs | 17 +++ .../Parsers/CSharp/LiteralExpressionSyntax.cs | 17 +++ .../CSharp/MemberAccessExpressionSyntax.cs | 17 +++ .../CSharp/PrefixUnaryExpressionSyntax.cs | 23 ++++ .../Parsers/CSharp/SyntaxKind.cs | 10 ++ .../Parsers/CSharp/SyntaxNode.cs | 9 ++ .../Parsers/CSharp/SyntaxToken.cs | 15 +++ .../Perspex.Markup/Perspex.Markup.csproj | 34 +++--- src/Markup/Perspex.Markup/packages.config | 6 +- .../Binding/ExpressionNodeBuilderTests.cs | 81 +++++++++++-- .../ExpressionObserverTests_Indexer.cs | 113 ++++++++++++++++++ .../Perspex.Markup.UnitTests.csproj | 1 + 26 files changed, 677 insertions(+), 62 deletions(-) create mode 100644 src/Markup/Perspex.Markup/Binding/ElementAccessorNode.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/BracketedArgumentListSyntax.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/CSharpSyntaxTree.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/ElementAccessExpressionSyntax.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionStatementSyntax.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionSyntax.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/ExpressionGrammar.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/IdentifierGrammar.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/LiteralGrammar.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/IdentifierSyntax.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/LiteralExpressionSyntax.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/MemberAccessExpressionSyntax.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/PrefixUnaryExpressionSyntax.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxKind.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxNode.cs create mode 100644 src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxToken.cs create mode 100644 tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Indexer.cs diff --git a/Perspex.v2.ncrunchsolution b/Perspex.v2.ncrunchsolution index 56e7a57022a3819f4a5ae943d50d65dc44d2efa5..fe4feb2cb68b687b4720bc1a47cbec77763ab35f 100644 GIT binary patch delta 19 acmZ1`w1#g(3>&izgZ^YjHsi@X9J2s8Rt13o literal 2342 zcmds(O>fgc5Qg7#CH@0vq=pg+35m*5R7i!?R23~B2Va8gHnfh7ybc9^Jn+nH9j8@$ z2u?1@+V$>y?7Tbkde?t`@9R{R3N6&=E2B~e-d`}gL|SXQ)FgVM9X;jQ*BE`FTpww* z!mV$j7wDcps_p#W5$VExPw=mF#W*9T0t!5t4{o4u;A!s88Vpn8u zLDp>V_mO>~LXBov&A>4CJQe!xcpt;InYBCAW6zb<>jtc~uBoiCuAl|7*fQ{p%6`LY z>{iEEm5x4fCggg9zQlfxG=md1HZ~Xw!!7@;Dt#jLMSbX)Y-g2P+^6g??sAi;??&p2 zd%}5OxzDzYdNs}_Ft=RYZmtb=7pX7q&r4U0|1kv!M_4LFqCVokyx?JOkQmcr?ZHn*P##QcZ}G@#R-|SDe@lmUiFR%J;wH z!HVP;db?!?+%K{1X_q@7XN4)UT@AIz`z8G=-a{ldPtmrgL%T*wv_xm3_E6F$Z@4uFtEElM}B@?OX|dmqz}ze;G$;jDhig#G arguments) + : base(next) + { + Arguments = arguments; + + var intArgs = Arguments.OfType().ToArray(); + + if (intArgs.Length == arguments.Count) + { + _intArgs = intArgs; + } + } + + public IList Arguments { get; } + + protected override void SubscribeAndUpdate(object target) + { + CurrentValue = GetValue(target); + + var incc = target as INotifyCollectionChanged; + + if (incc != null) + { + incc.CollectionChanged += CollectionChanged; + } + } + + protected override void Unsubscribe(object target) + { + var incc = target as INotifyCollectionChanged; + + if (incc != null) + { + incc.CollectionChanged -= CollectionChanged; + } + } + + private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + bool update = false; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + update = _intArgs[0] >= e.NewStartingIndex; + break; + case NotifyCollectionChangedAction.Remove: + update = _intArgs[0] >= e.OldStartingIndex; + break; + case NotifyCollectionChangedAction.Replace: + update = _intArgs[0] >= e.NewStartingIndex && + _intArgs[0] < e.NewStartingIndex + e.NewItems.Count; + break; + case NotifyCollectionChangedAction.Move: + update = (_intArgs[0] >= e.NewStartingIndex && + _intArgs[0] < e.NewStartingIndex + e.NewItems.Count) || + (_intArgs[0] >= e.OldStartingIndex && + _intArgs[0] < e.OldStartingIndex + e.OldItems.Count); + break; + case NotifyCollectionChangedAction.Reset: + update = true; + break; + } + + if (update) + { + CurrentValue = GetValue(sender); + } + } + + private ExpressionValue GetValue(object target) + { + var typeInfo = target.GetType().GetTypeInfo(); + var list = target as IList; + + if (typeInfo.IsArray && _intArgs != null) + { + return new ExpressionValue(((Array)target).GetValue(_intArgs)); + } + else if (target is IList && _intArgs?.Length == 1) + { + if (_intArgs[0] < list.Count) + { + return new ExpressionValue(list[_intArgs[0]]); + } + } + + return ExpressionValue.None; + } + } +} diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs index ff40507e7e..4bb4356c30 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs @@ -95,9 +95,13 @@ namespace Perspex.Markup.Binding } } - protected abstract void SubscribeAndUpdate(object target); - - protected abstract void Unsubscribe(object target); + protected virtual void SubscribeAndUpdate(object target) + { + CurrentValue = new ExpressionValue(target); + } + protected virtual void Unsubscribe(object target) + { + } } } diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs index b37892be4c..4667bfbf13 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs @@ -3,10 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Perspex.Markup.Parsers.CSharp; namespace Perspex.Markup.Binding { @@ -19,9 +16,7 @@ namespace Perspex.Markup.Binding throw new ArgumentException("'expression' may not be empty."); } - var syntaxTree = CSharpSyntaxTree.ParseText(expression, new CSharpParseOptions(kind: SourceCodeKind.Interactive)); - var syntaxRoot = syntaxTree.GetRoot(); - var syntax = syntaxRoot.ChildNodes().SingleOrDefault()?.ChildNodes()?.SingleOrDefault(); + var syntax = CSharpSyntaxTree.ParseExpression(expression); if (syntax != null) { @@ -36,9 +31,10 @@ namespace Perspex.Markup.Binding private static ExpressionNode Build(string expression, SyntaxNode syntax, ExpressionNode next) { var expressionStatement = syntax as ExpressionStatementSyntax; - var identifier = syntax as IdentifierNameSyntax; + var identifier = syntax as IdentifierSyntax; var memberAccess = syntax as MemberAccessExpressionSyntax; var unaryExpression = syntax as PrefixUnaryExpressionSyntax; + var elementAccess = syntax as ElementAccessExpressionSyntax; if (expressionStatement != null) { @@ -46,18 +42,23 @@ namespace Perspex.Markup.Binding } else if (identifier != null) { - next = new PropertyAccessorNode(next, identifier.Identifier.ValueText); + next = new PropertyAccessorNode(next, identifier.Name); } else if (memberAccess != null) { - next = new PropertyAccessorNode(next, memberAccess.Name.Identifier.ValueText); next = Build(expression, memberAccess.Expression, next); + next = new PropertyAccessorNode(next, memberAccess.Member.Name); } else if (unaryExpression != null && unaryExpression.Kind() == SyntaxKind.LogicalNotExpression) { next = Build(expression, unaryExpression.Operand, next); next = new LogicalNotNode(next); } + else if (elementAccess != null) + { + next = Build(expression, elementAccess, next); + next = Build(expression, elementAccess.Expression, next); + } else { throw new Exception($"Invalid expression: {expression}"); @@ -65,5 +66,35 @@ namespace Perspex.Markup.Binding return next; } + + private static ExpressionNode Build(string expression, ElementAccessExpressionSyntax syntax, ExpressionNode next) + { + var argList = syntax.ArgumentList as BracketedArgumentListSyntax; + + if (argList != null) + { + var args = new List(); + + foreach (var arg in argList.Arguments) + { + var literal = arg as LiteralExpressionSyntax; + + if (literal != null) + { + args.Add(literal.Value); + } + else + { + throw new Exception($"Invalid expression: {expression}"); + } + } + + return new ElementAccessorNode(next, args); + } + else + { + throw new Exception($"Invalid expression: {expression}"); + } + } } } diff --git a/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs b/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs index ca35ea5f12..6ea29deffa 100644 --- a/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs +++ b/src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs @@ -24,15 +24,6 @@ namespace Perspex.Markup.Binding return Next.Select(x => Negate(x)).Subscribe(observer); } - protected override void SubscribeAndUpdate(object target) - { - CurrentValue = new ExpressionValue(target); - } - - protected override void Unsubscribe(object target) - { - } - private ExpressionValue Negate(ExpressionValue v) { if (v.HasValue) diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index da54212165..0a4b4b737e 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -65,14 +65,6 @@ namespace Perspex.Markup.Binding CurrentValue = result; } - private void PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == PropertyName) - { - CurrentValue = new ExpressionValue(_propertyInfo.GetValue(Target)); - } - } - protected override void Unsubscribe(object target) { var inpc = target as INotifyPropertyChanged; @@ -82,5 +74,13 @@ namespace Perspex.Markup.Binding inpc.PropertyChanged -= PropertyChanged; } } + + private void PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == PropertyName) + { + CurrentValue = new ExpressionValue(_propertyInfo.GetValue(Target)); + } + } } } diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/BracketedArgumentListSyntax.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/BracketedArgumentListSyntax.cs new file mode 100644 index 0000000000..52a53313be --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/BracketedArgumentListSyntax.cs @@ -0,0 +1,18 @@ +// 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.Collections.Generic; +using System.Linq; + +namespace Perspex.Markup.Parsers.CSharp +{ + internal class BracketedArgumentListSyntax + { + public BracketedArgumentListSyntax(IEnumerable arguments) + { + Arguments = arguments.ToList(); + } + + public IList Arguments { get; } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/CSharpSyntaxTree.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/CSharpSyntaxTree.cs new file mode 100644 index 0000000000..19a6f8ac07 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/CSharpSyntaxTree.cs @@ -0,0 +1,17 @@ +// 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.Markup.Parsers.CSharp.Grammar; +using Sprache; + +namespace Perspex.Markup.Parsers.CSharp +{ + public class CSharpSyntaxTree + { + internal static ExpressionStatementSyntax ParseExpression(string expression) + { + return ExpressionGrammar.ExpressionStatement().Parse(expression); + } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/ElementAccessExpressionSyntax.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/ElementAccessExpressionSyntax.cs new file mode 100644 index 0000000000..6568e6f1a7 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/ElementAccessExpressionSyntax.cs @@ -0,0 +1,19 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Perspex.Markup.Parsers.CSharp +{ + internal class ElementAccessExpressionSyntax : ExpressionSyntax + { + public ElementAccessExpressionSyntax( + ExpressionSyntax expression, + BracketedArgumentListSyntax argumentList) + { + Expression = expression; + ArgumentList = argumentList; + } + + public ExpressionSyntax Expression { get; } + public BracketedArgumentListSyntax ArgumentList { get; } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionStatementSyntax.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionStatementSyntax.cs new file mode 100644 index 0000000000..6c653eaea5 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionStatementSyntax.cs @@ -0,0 +1,15 @@ +// 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.Markup.Parsers.CSharp +{ + internal class ExpressionStatementSyntax : SyntaxNode + { + public ExpressionStatementSyntax(ExpressionSyntax expression) + { + Expression = expression; + } + + public ExpressionSyntax Expression { get; } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionSyntax.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionSyntax.cs new file mode 100644 index 0000000000..403bc7b6b6 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionSyntax.cs @@ -0,0 +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. + +namespace Perspex.Markup.Parsers.CSharp +{ + internal class ExpressionSyntax : SyntaxNode + { + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/ExpressionGrammar.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/ExpressionGrammar.cs new file mode 100644 index 0000000000..71f847d312 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/ExpressionGrammar.cs @@ -0,0 +1,64 @@ +// 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.Collections.Generic; +using System.Linq; +using Sprache; + +namespace Perspex.Markup.Parsers.CSharp.Grammar +{ + internal class ExpressionGrammar + { + public static Parser ExpressionStatement() + { + return from expression in Expression().End() + select new ExpressionStatementSyntax(expression); + } + + public static Parser Expression() + { + return LiteralGrammar.Literal() + .Or(PrefixUnary()) + .Or(MemberAccess()) + .Or(ElementAccess()) + .Or(IdentifierGrammar.Identifier()); + } + + public static Parser MemberAccess() + { + return from identifier in IdentifierGrammar.Identifier() + from dot in Parse.Char('.') + from expression in Expression() + select new MemberAccessExpressionSyntax(expression, identifier); + } + + public static Parser ElementAccess() + { + return from expression in IdentifierGrammar.Identifier() + from arguments in BracketedArgumentList('[', ']') + select new ElementAccessExpressionSyntax(expression, arguments); + } + + public static Parser BracketedArgumentList( + char openBracket, + char closeBracket) + { + return from open in Parse.Char(openBracket) + from arguments in Arguments() + from close in Parse.Char(closeBracket) + select new BracketedArgumentListSyntax(arguments); + } + + public static Parser> Arguments() + { + return Expression().DelimitedBy(Parse.Char(',').Token()); + } + + public static Parser PrefixUnary() + { + return from bang in Parse.Char('!') + from operand in Expression() + select new PrefixUnaryExpressionSyntax(operand, SyntaxKind.LogicalNotExpression); + } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/IdentifierGrammar.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/IdentifierGrammar.cs new file mode 100644 index 0000000000..015a3f55c9 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/IdentifierGrammar.cs @@ -0,0 +1,41 @@ +using System.Globalization; +using System.Linq; +using Sprache; + +namespace Perspex.Markup.Parsers.CSharp.Grammar +{ + internal class IdentifierGrammar + { + private static readonly Parser CombiningCharacter = Parse.Char( + c => + { + var cat = CharUnicodeInfo.GetUnicodeCategory(c); + return cat == UnicodeCategory.NonSpacingMark || + cat == UnicodeCategory.SpacingCombiningMark; + }, + "Connecting Character"); + + private static readonly Parser ConnectingCharacter = Parse.Char( + c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.ConnectorPunctuation, + "Connecting Character"); + + private static readonly Parser FormattingCharacter = Parse.Char( + c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.Format, + "Connecting Character"); + + private static readonly Parser IdentifierStart = Parse.Letter.Or(Parse.Char('_')); + + private static readonly Parser IdentifierChar = Parse + .LetterOrDigit + .Or(ConnectingCharacter) + .Or(CombiningCharacter) + .Or(FormattingCharacter); + + public static Parser Identifier() + { + return from start in IdentifierStart.Once().Text() + from @char in IdentifierChar.Many().Text() + select new IdentifierSyntax(start + @char); + } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/LiteralGrammar.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/LiteralGrammar.cs new file mode 100644 index 0000000000..9cc4ea5705 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/LiteralGrammar.cs @@ -0,0 +1,20 @@ +using System.Globalization; +using System.Linq; +using Sprache; + +namespace Perspex.Markup.Parsers.CSharp.Grammar +{ + internal class LiteralGrammar + { + public static Parser Literal() + { + return Integer(); + } + + public static Parser Integer() + { + return from number in Parse.Number + select new LiteralExpressionSyntax(int.Parse(number), number); + } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/IdentifierSyntax.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/IdentifierSyntax.cs new file mode 100644 index 0000000000..dc676652d7 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/IdentifierSyntax.cs @@ -0,0 +1,17 @@ +// 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.Markup.Parsers.CSharp +{ + internal class IdentifierSyntax : ExpressionSyntax + { + public IdentifierSyntax(string name) + { + Name = name; + } + + public String Name { get; } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/LiteralExpressionSyntax.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/LiteralExpressionSyntax.cs new file mode 100644 index 0000000000..bc741e8a77 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/LiteralExpressionSyntax.cs @@ -0,0 +1,17 @@ +// 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.Markup.Parsers.CSharp +{ + internal class LiteralExpressionSyntax : ExpressionSyntax + { + public LiteralExpressionSyntax(object value, string valueText) + { + Value = value; + ValueText = valueText; + } + + public object Value { get; } + public string ValueText { get; } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/MemberAccessExpressionSyntax.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/MemberAccessExpressionSyntax.cs new file mode 100644 index 0000000000..3b019613af --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/MemberAccessExpressionSyntax.cs @@ -0,0 +1,17 @@ +// 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.Markup.Parsers.CSharp +{ + internal class MemberAccessExpressionSyntax : ExpressionSyntax + { + public MemberAccessExpressionSyntax(ExpressionSyntax expression, IdentifierSyntax member) + { + Expression = expression; + Member = member; + } + + public ExpressionSyntax Expression { get; } + public IdentifierSyntax Member { get; } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/PrefixUnaryExpressionSyntax.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/PrefixUnaryExpressionSyntax.cs new file mode 100644 index 0000000000..f0e292ba33 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/PrefixUnaryExpressionSyntax.cs @@ -0,0 +1,23 @@ +// 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.Markup.Parsers.CSharp +{ + internal class PrefixUnaryExpressionSyntax : ExpressionSyntax + { + private SyntaxKind _kind; + + public PrefixUnaryExpressionSyntax(ExpressionSyntax operand, SyntaxKind kind) + { + Operand = operand; + _kind = kind; + } + + public ExpressionSyntax Operand { get; } + + public SyntaxKind Kind() + { + return _kind; + } + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxKind.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxKind.cs new file mode 100644 index 0000000000..121bc48b9f --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxKind.cs @@ -0,0 +1,10 @@ +// 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.Markup.Parsers.CSharp +{ + internal enum SyntaxKind + { + LogicalNotExpression, + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxNode.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxNode.cs new file mode 100644 index 0000000000..ea9e71d870 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxNode.cs @@ -0,0 +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. + +namespace Perspex.Markup.Parsers.CSharp +{ + internal class SyntaxNode + { + } +} diff --git a/src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxToken.cs b/src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxToken.cs new file mode 100644 index 0000000000..e66aa4bde0 --- /dev/null +++ b/src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxToken.cs @@ -0,0 +1,15 @@ +// 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.Markup.Parsers.CSharp +{ + internal class SyntaxToken + { + public SyntaxToken(string valueText) + { + ValueText = valueText; + } + + public string ValueText { get; } + } +} diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj index fd08d6e577..9b9d696c95 100644 --- a/src/Markup/Perspex.Markup/Perspex.Markup.csproj +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -37,22 +37,28 @@ + + + + + + + + + + + + + + - - ..\..\..\packages\Microsoft.CodeAnalysis.Common.1.1.0-beta1-20150812-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.CSharp.1.1.0-beta1-20150812-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll - True - - - ..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + ..\..\..\packages\Sprache.2.0.0.47\lib\portable-net4+netcore45+win8+wp8+sl5+MonoAndroid1+MonoTouch1\Sprache.dll True @@ -71,18 +77,10 @@ ..\..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll True - - ..\..\..\packages\System.Reflection.Metadata.1.1.0-alpha-00009\lib\portable-net45+win8\System.Reflection.Metadata.dll - True - - - - - From 3cb479769df5d4736eb6354e13840ea69376aa7d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 02:04:36 +0200 Subject: [PATCH 26/54] Use string XamlBindingDefinition.SourcePropertyPath. --- .../DataBinding/PerspexPropertyBinder.cs | 3 +- .../DataBinding/XamlBindingDefinition.cs | 28 +++++++++---------- .../MarkupExtensions/BindingExtension.cs | 8 +----- .../BindingDefinitionBuilder.cs | 6 ++-- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs index 27f93d53e9..9b53ef52c7 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs +++ b/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using OmniXaml.TypeConversion; +using Perspex.Markup.Xaml.DataBinding.ChangeTracking; namespace Perspex.Markup.Xaml.DataBinding { @@ -47,7 +48,7 @@ namespace Perspex.Markup.Xaml.DataBinding var binding = new XamlBinding(_typeConverterProvider) { BindingMode = xamlBinding.BindingMode, - SourcePropertyPath = xamlBinding.SourcePropertyPath, + SourcePropertyPath = new PropertyPath(xamlBinding.SourcePropertyPath), Target = xamlBinding.Target, TargetProperty = xamlBinding.TargetProperty }; diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs index cce86455d6..62ebcf39ca 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs +++ b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs @@ -2,31 +2,29 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Perspex.Controls; -using Perspex.Markup.Xaml.DataBinding.ChangeTracking; namespace Perspex.Markup.Xaml.DataBinding { public class XamlBindingDefinition { - private readonly PropertyPath _sourcePropertyPath; - private readonly BindingMode _bindingMode; - private readonly Control _target; - private readonly PerspexProperty _targetProperty; - - public XamlBindingDefinition(Control target, PerspexProperty targetProperty, PropertyPath sourcePropertyPath, BindingMode bindingMode) + public XamlBindingDefinition( + Control target, + PerspexProperty targetProperty, + string sourcePropertyPath, + BindingMode bindingMode) { - _target = target; - _targetProperty = targetProperty; - _sourcePropertyPath = sourcePropertyPath; - _bindingMode = bindingMode; + Target = target; + TargetProperty = targetProperty; + SourcePropertyPath = sourcePropertyPath; + BindingMode = bindingMode; } - public Control Target => _target; + public Control Target { get; } - public PerspexProperty TargetProperty => _targetProperty; + public PerspexProperty TargetProperty { get; } - public PropertyPath SourcePropertyPath => _sourcePropertyPath; + public string SourcePropertyPath { get; } - public BindingMode BindingMode => _bindingMode; + public BindingMode BindingMode { get; } } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 9777ba3c9d..593d245dba 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -27,13 +27,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions var targetPropertyName = targetProperty.Name; var perspexProperty = target.GetRegisteredProperties().First(property => property.Name == targetPropertyName); - return new XamlBindingDefinition - ( - target, - perspexProperty, - new PropertyPath(Path), - Mode == BindingMode.Default ? BindingMode.OneWay : Mode - ); + return new XamlBindingDefinition(target, perspexProperty, Path, Mode); } /// The source path (for CLR bindings). diff --git a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs b/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs index e968db3615..066a1ea12b 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs @@ -1,24 +1,22 @@ // 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.Controls; using Perspex.Markup.Xaml.DataBinding; -using Perspex.Markup.Xaml.DataBinding.ChangeTracking; namespace Perspex.Xaml.Base.UnitTest { public class BindingDefinitionBuilder { private readonly BindingMode _bindingMode; - private readonly PropertyPath _sourcePropertyPath; + private readonly string _sourcePropertyPath; private Control _target; private PerspexProperty _targetProperty; public BindingDefinitionBuilder() { _bindingMode = BindingMode.Default; - _sourcePropertyPath = new PropertyPath(string.Empty); + _sourcePropertyPath = string.Empty; } public BindingDefinitionBuilder WithNullTarget() From f0f22f06ad2b9df3f244f5f08ae8211c07f0752a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 02:16:15 +0200 Subject: [PATCH 27/54] Select correct binding mode. --- src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs index af0d33de6a..837413a26f 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs @@ -37,21 +37,22 @@ namespace Perspex.Markup.Xaml.DataBinding { var bindingSource = new DataContextChangeSynchronizer.BindingSource(SourcePropertyPath, dataContext); var bindingTarget = new DataContextChangeSynchronizer.BindingTarget(Target, TargetProperty); + var mode = BindingMode == BindingMode.Default ? TargetProperty.DefaultBindingMode : BindingMode; _changeSynchronizer = new DataContextChangeSynchronizer(bindingSource, bindingTarget, _typeConverterProvider); - if (BindingMode == BindingMode.TwoWay) + if (mode == BindingMode.TwoWay) { _changeSynchronizer.StartUpdatingTargetWhenSourceChanges(); _changeSynchronizer.StartUpdatingSourceWhenTargetChanges(); } - if (BindingMode == BindingMode.OneWay) + if (mode == BindingMode.OneWay || mode == BindingMode.Default) { _changeSynchronizer.StartUpdatingTargetWhenSourceChanges(); } - if (BindingMode == BindingMode.OneWayToSource) + if (mode == BindingMode.OneWayToSource) { _changeSynchronizer.StartUpdatingSourceWhenTargetChanges(); } From d55e7fa9d0a80b3e27a6e030e3f64bb14f07abce Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 02:56:52 +0200 Subject: [PATCH 28/54] Search base classes for properties. --- .../Binding/PropertyAccessorNode.cs | 23 ++++++++++++++++++- .../ExpressionObserverTests_Property.cs | 15 ++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index 08ecafac6a..c1a45d8784 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -46,7 +46,7 @@ namespace Perspex.Markup.Binding if (target != null) { - _propertyInfo = target.GetType().GetTypeInfo().GetDeclaredProperty(PropertyName); + _propertyInfo = FindProperty(target, PropertyName); if (_propertyInfo != null) { @@ -82,6 +82,27 @@ namespace Perspex.Markup.Binding } } + private static PropertyInfo FindProperty(object target, string propertyName) + { + var typeInfo = target.GetType().GetTypeInfo(); + + do + { + var result = typeInfo.GetDeclaredProperty(propertyName); + + if (result != null) + { + return result; + } + else + { + typeInfo = typeInfo.BaseType?.GetTypeInfo(); + } + } while (typeInfo != null); + + return null; + } + private void ReadValue(object target) { var value = _propertyInfo.GetValue(target); diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs index ccda547bd5..ac159a2deb 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs @@ -22,6 +22,17 @@ namespace Perspex.Markup.UnitTests.Binding Assert.Equal("foo", result.Value); } + [Fact] + public async void Should_Get_Simple_Property_From_Base_Class() + { + var data = new Class3 { Foo = "foo" }; + var target = new ExpressionObserver(data, "Foo"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal("foo", result.Value); + } + [Fact] public async void Should_Get_Simple_Property_Chain() { @@ -235,6 +246,10 @@ namespace Perspex.Markup.UnitTests.Binding } } + private class Class3 : Class1 + { + } + private class WithoutBar : NotifyingBase, INext { } From 52975c84328ed86e552054ce746772d407dbbf8f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 10:29:36 +0200 Subject: [PATCH 29/54] Basic binding using ExpressionObserver. --- samples/XamlTestApplication/MainViewModel.cs | 52 ------ .../ViewModels/MainWindowViewModel.cs | 58 ++++++ .../ViewModels/TestItem.cs | 17 ++ .../ViewModels/TestNode.cs | 14 ++ .../XamlTestApplication/Views/MainWindow.cs | 9 +- .../XamlTestApplication/Views/MainWindow.paml | 16 +- .../XamlTestApplication.csproj | 4 +- .../Context/PerspexXamlMemberValuePlugin.cs | 22 ++- .../ObservablePropertyBranch.cs | 109 ------------ .../ChangeTracking/PropertyMountPoint.cs | 52 ------ .../ChangeTracking/PropertyPath.cs | 36 ---- .../ChangeTracking/TargettedProperty.cs | 41 ----- .../DataContextChangeSynchronizer.cs | 165 ------------------ .../DataBinding/IPerspexPropertyBinder.cs | 3 + .../DataBinding/PerspexPropertyBinder.cs | 8 +- .../DataBinding/XamlBinding.cs | 57 +++--- .../DataBinding/XamlBindingDefinition.cs | 31 ++-- .../MarkupExtensions/BindingExtension.cs | 12 +- .../Perspex.Markup.Xaml.csproj | 9 +- .../Properties/AssemblyInfo.cs | 1 + .../BindingDefinitionBuilder.cs | 8 +- .../ChangeBranchTest.cs | 72 -------- .../DataContextChangeSynchronizerTest.cs | 103 ----------- .../Perspex.Markup.Xaml.UnitTests.csproj | 3 - .../PropertyMountPointTest.cs | 18 -- .../XamlBindingTest.cs | 6 +- 26 files changed, 169 insertions(+), 757 deletions(-) delete mode 100644 samples/XamlTestApplication/MainViewModel.cs create mode 100644 samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs create mode 100644 samples/XamlTestApplication/ViewModels/TestItem.cs create mode 100644 samples/XamlTestApplication/ViewModels/TestNode.cs delete mode 100644 src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs delete mode 100644 src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs delete mode 100644 src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs delete mode 100644 src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs delete mode 100644 src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs delete mode 100644 tests/Perspex.Markup.Xaml.UnitTests/ChangeBranchTest.cs delete mode 100644 tests/Perspex.Markup.Xaml.UnitTests/DataContextChangeSynchronizerTest.cs delete mode 100644 tests/Perspex.Markup.Xaml.UnitTests/PropertyMountPointTest.cs diff --git a/samples/XamlTestApplication/MainViewModel.cs b/samples/XamlTestApplication/MainViewModel.cs deleted file mode 100644 index 450c8f422f..0000000000 --- a/samples/XamlTestApplication/MainViewModel.cs +++ /dev/null @@ -1,52 +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.Collections.Generic; -using ReactiveUI; - -namespace XamlTestApplication -{ - public class MainViewModel : ReactiveObject - { - private string _name; - - public MainViewModel() - { - Name = "Jos\u00E9 Manuel"; - People = new List - { - new Person("a little bit of Monica in my life"), - new Person("a little bit of Erica by my side"), - new Person("a little bit of Rita is all I need"), - new Person("a little bit of Tina is what I see"), - new Person("a little bit of Sandra in the sun"), - new Person("a little bit of Mary all night long"), - new Person("a little bit of Jessica here I am"), - }; - } - - public string Name - { - get { return _name; } - set { this.RaiseAndSetIfChanged(ref _name, value); } - } - - public List People { get; set; } - } - - public class Person - { - private string _name; - - public Person(string name) - { - _name = name; - } - - public string Name - { - get { return _name; } - set { _name = value; } - } - } -} \ No newline at end of file diff --git a/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs b/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000000..b786ee8f9b --- /dev/null +++ b/samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,58 @@ +// 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.Collections.Generic; + +namespace XamlTestApplication.ViewModels +{ + public class MainWindowViewModel + { + public MainWindowViewModel() + { + Items = new List(); + + for (int i = 0; i < 10; ++i) + { + Items.Add(new TestItem($"Item {i}", $"Item {i} Value")); + } + + Nodes = new List + { + new TestNode + { + Header = "Root", + SubHeader = "Root Item", + Children = new[] + { + new TestNode + { + Header = "Child 1", + SubHeader = "Child 1 Value", + }, + new TestNode + { + Header = "Child 2", + SubHeader = "Child 2 Value", + Children = new[] + { + new TestNode + { + Header = "Grandchild", + SubHeader = "Grandchild Value", + }, + new TestNode + { + Header = "Grandmaster Flash", + SubHeader = "White Lines", + }, + } + }, + } + } + }; + } + + public List Items { get; } + public List Nodes { get; } + } +} \ No newline at end of file diff --git a/samples/XamlTestApplication/ViewModels/TestItem.cs b/samples/XamlTestApplication/ViewModels/TestItem.cs new file mode 100644 index 0000000000..30ed5d03ee --- /dev/null +++ b/samples/XamlTestApplication/ViewModels/TestItem.cs @@ -0,0 +1,17 @@ +// 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 XamlTestApplication.ViewModels +{ + public class TestItem + { + public TestItem(string header, string subheader) + { + Header = header; + SubHeader = subheader; + } + + public string Header { get; } + public string SubHeader { get; } + } +} \ No newline at end of file diff --git a/samples/XamlTestApplication/ViewModels/TestNode.cs b/samples/XamlTestApplication/ViewModels/TestNode.cs new file mode 100644 index 0000000000..953bfd0f58 --- /dev/null +++ b/samples/XamlTestApplication/ViewModels/TestNode.cs @@ -0,0 +1,14 @@ +// 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.Collections.Generic; + +namespace XamlTestApplication.ViewModels +{ + public class TestNode + { + public string Header { get; set; } + public string SubHeader { get; set; } + public IEnumerable Children { get; set; } + } +} \ No newline at end of file diff --git a/samples/XamlTestApplication/Views/MainWindow.cs b/samples/XamlTestApplication/Views/MainWindow.cs index dc218641bf..d18985a898 100644 --- a/samples/XamlTestApplication/Views/MainWindow.cs +++ b/samples/XamlTestApplication/Views/MainWindow.cs @@ -1,15 +1,10 @@ // 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.IO; -using System.Reflection; -using System.Resources; -using OmniXaml; using Perspex.Controls; using Perspex.Diagnostics; using Perspex.Markup.Xaml; +using XamlTestApplication.ViewModels; namespace XamlTestApplication.Views { @@ -18,7 +13,7 @@ namespace XamlTestApplication.Views public MainWindow() { InitializeComponent(); - + DataContext = new MainWindowViewModel(); DevTools.Attach(this); } diff --git a/samples/XamlTestApplication/Views/MainWindow.paml b/samples/XamlTestApplication/Views/MainWindow.paml index 0794d15aff..d6ab54e858 100644 --- a/samples/XamlTestApplication/Views/MainWindow.paml +++ b/samples/XamlTestApplication/Views/MainWindow.paml @@ -48,20 +48,8 @@ - - - - - - - - - - - - - - + + diff --git a/samples/XamlTestApplication/XamlTestApplication.csproj b/samples/XamlTestApplication/XamlTestApplication.csproj index d70e2e3297..107df97bcc 100644 --- a/samples/XamlTestApplication/XamlTestApplication.csproj +++ b/samples/XamlTestApplication/XamlTestApplication.csproj @@ -77,9 +77,11 @@ - + + + diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index 423ca66e23..f978ecaa69 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs @@ -54,22 +54,26 @@ namespace Perspex.Markup.Xaml.Context po.SetValue(pp, value); } - private void HandleXamlBindingDefinition(XamlBindingDefinition xamlBindingDefinition) + private void HandleXamlBindingDefinition(XamlBindingDefinition def) { - PerspexObject subjectObject = xamlBindingDefinition.Target; - _propertyBinder.Create(xamlBindingDefinition); + var binding = new XamlBinding(_propertyBinder.TypeConverterProvider) + { + BindingMode = def.BindingMode, + SourcePropertyPath = def.SourcePropertyPath, + Target = def.Target, + TargetProperty = def.TargetProperty, + }; - var observableForDataContext = subjectObject.GetObservable(Control.DataContextProperty); - observableForDataContext.Where(o => o != null).Subscribe(_ => BindToDataContextWhenItsSet(xamlBindingDefinition)); + binding.Bind(); } private void BindToDataContextWhenItsSet(XamlBindingDefinition definition) { - var target = definition.Target; - var dataContext = target.DataContext; + // var target = definition.Target; + // var dataContext = target.DataContext; - var binding = _propertyBinder.GetBinding(target, definition.TargetProperty); - binding.BindToDataContext(dataContext); + // var binding = _propertyBinder.GetBinding(target, definition.TargetProperty); + // binding.BindToDataContext(dataContext); } // ReSharper disable once MemberCanBePrivate.Global diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs deleted file mode 100644 index 2756065280..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs +++ /dev/null @@ -1,109 +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.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using System.Reflection; -using Glass; - -namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking -{ - public class ObservablePropertyBranch - { - private readonly object _instance; - private readonly PropertyPath _propertyPath; - private readonly PropertyMountPoint _mountPoint; - - public ObservablePropertyBranch(object instance, PropertyPath propertyPath) - { - Guard.ThrowIfNull(instance, nameof(instance)); - Guard.ThrowIfNull(propertyPath, nameof(propertyPath)); - - _instance = instance; - _propertyPath = propertyPath; - _mountPoint = new PropertyMountPoint(instance, propertyPath); - var properties = GetPropertiesThatRaiseNotifications(); - Values = CreateUnifiedObservableFromNodes(properties); - } - - public IObservable Values { get; private set; } - - private IObservable CreateUnifiedObservableFromNodes(IEnumerable subscriptions) - { - return subscriptions.Select(GetObservableFromProperty).Merge(); - } - - private IObservable GetObservableFromProperty(PropertyDefinition subscription) - { - return Observable.FromEventPattern( - parentOnPropertyChanged => subscription.Parent.PropertyChanged += parentOnPropertyChanged, - parentOnPropertyChanged => subscription.Parent.PropertyChanged -= parentOnPropertyChanged) - .Where(pattern => pattern.EventArgs.PropertyName == subscription.PropertyName) - .Select(pattern => _mountPoint.Value); - } - - private IEnumerable GetPropertiesThatRaiseNotifications() - { - return GetSubscriptionsRecursive(_instance, _propertyPath, 0); - } - - private IEnumerable GetSubscriptionsRecursive(object current, PropertyPath propertyPath, int i) - { - var subscriptions = new List(); - var inpc = current as INotifyPropertyChanged; - - if (inpc == null) - { - return subscriptions; - } - - var nextPropertyName = propertyPath.Chunks[i]; - subscriptions.Add(new PropertyDefinition(inpc, nextPropertyName)); - - if (i < _propertyPath.Chunks.Length) - { - var currentObjectTypeInfo = current.GetType().GetTypeInfo(); - var nextProperty = currentObjectTypeInfo.GetDeclaredProperty(nextPropertyName); - var nextInstance = nextProperty.GetValue(current); - - if (i < _propertyPath.Chunks.Length - 1) - { - subscriptions.AddRange(GetSubscriptionsRecursive(nextInstance, propertyPath, i + 1)); - } - } - - return subscriptions; - } - - public object Value - { - get - { - return _mountPoint.Value; - } - - set - { - _mountPoint.Value = value; - } - } - - public Type Type => _mountPoint.ProperyType; - - private class PropertyDefinition - { - public PropertyDefinition(INotifyPropertyChanged parent, string propertyName) - { - Parent = parent; - PropertyName = propertyName; - } - - public INotifyPropertyChanged Parent { get; } - - public string PropertyName { get; } - } - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs deleted file mode 100644 index 8ea903fa55..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs +++ /dev/null @@ -1,52 +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.Reflection; -using Glass; - -namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking -{ - public class PropertyMountPoint - { - private readonly TargettedProperty _referencedTargettedProperty; - - public PropertyMountPoint(object origin, PropertyPath propertyPath) - { - Guard.ThrowIfNull(origin, nameof(origin)); - Guard.ThrowIfNull(propertyPath, nameof(propertyPath)); - - _referencedTargettedProperty = GetReferencedPropertyInfo(origin, propertyPath, 0); - } - - private static TargettedProperty GetReferencedPropertyInfo(object current, PropertyPath propertyPath, int level) - { - var typeInfo = current.GetType().GetTypeInfo(); - var leftPropertyInfo = typeInfo.GetDeclaredProperty(propertyPath.Chunks[level]); - - if (level == propertyPath.Chunks.Length - 1) - { - return new TargettedProperty(current, leftPropertyInfo); - } - - var nextInstance = leftPropertyInfo.GetValue(current); - - return GetReferencedPropertyInfo(nextInstance, propertyPath, level + 1); - } - - public object Value - { - get - { - return _referencedTargettedProperty.Value; - } - - set - { - _referencedTargettedProperty.Value = value; - } - } - - public Type ProperyType => _referencedTargettedProperty.PropertyType; - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs deleted file mode 100644 index 3558645b1b..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs +++ /dev/null @@ -1,36 +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. - -namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking -{ - public class PropertyPath - { - private string[] _chunks; - - private PropertyPath(PropertyPath propertyPath) - { - _chunks = propertyPath.Chunks; - } - - public PropertyPath(string path) - { - _chunks = path.Split('.'); - } - - public string[] Chunks - { - get { return _chunks; } - set { _chunks = value; } - } - - public PropertyPath Clone() - { - return new PropertyPath(this); - } - - public override string ToString() - { - return string.Join(".", _chunks); - } - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs deleted file mode 100644 index fec5edc904..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs +++ /dev/null @@ -1,41 +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.Reflection; -using Glass; - -namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking -{ - internal class TargettedProperty - { - private readonly object _instance; - private readonly PropertyInfo _propertyInfo; - - public TargettedProperty(object instance, PropertyInfo propertyInfo) - { - Guard.ThrowIfNull(instance, nameof(instance)); - Guard.ThrowIfNull(propertyInfo, nameof(propertyInfo)); - - _instance = instance; - _propertyInfo = propertyInfo; - } - - public object Value - { - get - { - return _propertyInfo.GetValue(_instance); - } - - set - { - _propertyInfo.SetValue(_instance, value); - } - } - - public Type PropertyType => _propertyInfo.PropertyType; - - public string Name => _propertyInfo.Name; - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs deleted file mode 100644 index 27723b5f75..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs +++ /dev/null @@ -1,165 +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.Globalization; -using System.Reactive.Linq; -using System.Reflection; -using Glass; -using OmniXaml.TypeConversion; -using Perspex.Markup.Xaml.DataBinding.ChangeTracking; - -namespace Perspex.Markup.Xaml.DataBinding -{ - public class DataContextChangeSynchronizer - { - private readonly BindingTarget _bindingTarget; - private readonly ITypeConverter _targetPropertyTypeConverter; - private readonly TargetBindingEndpoint _bindingEndpoint; - private readonly ObservablePropertyBranch _sourceEndpoint; - - public DataContextChangeSynchronizer(BindingSource bindingSource, BindingTarget bindingTarget, ITypeConverterProvider typeConverterProvider) - { - _bindingTarget = bindingTarget; - Guard.ThrowIfNull(bindingTarget.Object, nameof(bindingTarget.Object)); - Guard.ThrowIfNull(bindingTarget.Property, nameof(bindingTarget.Property)); - Guard.ThrowIfNull(bindingSource.SourcePropertyPath, nameof(bindingSource.SourcePropertyPath)); - Guard.ThrowIfNull(bindingSource.Source, nameof(bindingSource.Source)); - Guard.ThrowIfNull(typeConverterProvider, nameof(typeConverterProvider)); - - _bindingEndpoint = new TargetBindingEndpoint(bindingTarget.Object, bindingTarget.Property); - _sourceEndpoint = new ObservablePropertyBranch(bindingSource.Source, bindingSource.SourcePropertyPath); - _targetPropertyTypeConverter = typeConverterProvider.GetTypeConverter(bindingTarget.Property.PropertyType); - } - - public class BindingTarget - { - private readonly PerspexObject _obj; - private readonly PerspexProperty _property; - - public BindingTarget(PerspexObject @object, PerspexProperty property) - { - _obj = @object; - _property = property; - } - - public PerspexObject Object => _obj; - - public PerspexProperty Property => _property; - - public object Value - { - get { return _obj.GetValue(_property); } - set { _obj.SetValue(_property, value); } - } - } - - public class BindingSource - { - private readonly PropertyPath _sourcePropertyPath; - private readonly object _source; - - public BindingSource(PropertyPath sourcePropertyPath, object source) - { - _sourcePropertyPath = sourcePropertyPath; - _source = source; - } - - public PropertyPath SourcePropertyPath => _sourcePropertyPath; - - public object Source => _source; - } - - public void StartUpdatingTargetWhenSourceChanges() - { - // TODO: commenting out this line will make the existing value to be skipped from the SourceValues. This is not supposed to happen. Is it? - _bindingTarget.Value = ConvertedValue(_sourceEndpoint.Value, _bindingTarget.Property.PropertyType); - - // We use the native Bind method from PerspexObject to subscribe to the SourceValues observable - _bindingTarget.Object.Bind(_bindingTarget.Property, SourceValues); - } - - public void StartUpdatingSourceWhenTargetChanges() - { - // We subscribe to the TargetValues and each time we have a new value, we update the source with it - TargetValues.Subscribe(newValue => _sourceEndpoint.Value = newValue); - } - - private IObservable SourceValues - { - get - { - return _sourceEndpoint.Values.Select(originalValue => ConvertedValue(originalValue, _bindingTarget.Property.PropertyType)); - } - } - - private IObservable TargetValues - { - get - { - return _bindingEndpoint.Object - .GetObservable(_bindingEndpoint.Property).Select(o => ConvertedValue(o, _sourceEndpoint.Type)); - } - } - - private bool CanAssignWithoutConversion - { - get - { - var sourceTypeInfo = _sourceEndpoint.Type.GetTypeInfo(); - var targetTypeInfo = _bindingEndpoint.Property.PropertyType.GetTypeInfo(); - var compatible = targetTypeInfo.IsAssignableFrom(sourceTypeInfo); - return compatible; - } - } - - private object ConvertedValue(object originalValue, Type propertyType) - { - object converted; - if (TryConvert(originalValue, propertyType, out converted)) - { - return converted; - } - - return null; - } - - private bool TryConvert(object originalValue, Type targetType, out object finalValue) - { - if (originalValue != null) - { - if (CanAssignWithoutConversion) - { - finalValue = originalValue; - return true; - } - - if (_targetPropertyTypeConverter != null) - { - if (_targetPropertyTypeConverter.CanConvertTo(null, targetType)) - { - object convertedValue = _targetPropertyTypeConverter.ConvertTo( - null, - CultureInfo.InvariantCulture, - originalValue, - targetType); - - if (convertedValue != null) - { - finalValue = convertedValue; - return true; - } - } - } - } - else - { - finalValue = null; - return true; - } - - finalValue = null; - return false; - } - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs index d7879dc29b..40d349e00d 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs +++ b/src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs @@ -2,11 +2,14 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Collections.Generic; +using OmniXaml.TypeConversion; namespace Perspex.Markup.Xaml.DataBinding { public interface IPerspexPropertyBinder { + ITypeConverterProvider TypeConverterProvider { get; } + XamlBinding GetBinding(PerspexObject po, PerspexProperty pp); IEnumerable GetBindings(PerspexObject source); diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs index 27f93d53e9..916e3edd85 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs +++ b/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs @@ -10,16 +10,16 @@ namespace Perspex.Markup.Xaml.DataBinding { public class PerspexPropertyBinder : IPerspexPropertyBinder { - private readonly ITypeConverterProvider _typeConverterProvider; - private readonly HashSet _bindings; public PerspexPropertyBinder(ITypeConverterProvider typeConverterProvider) { - _typeConverterProvider = typeConverterProvider; + TypeConverterProvider = typeConverterProvider; _bindings = new HashSet(); } + public ITypeConverterProvider TypeConverterProvider { get; } + public XamlBinding GetBinding(PerspexObject po, PerspexProperty pp) { return _bindings.First(xamlBinding => xamlBinding.Target == po && xamlBinding.TargetProperty == pp); @@ -44,7 +44,7 @@ namespace Perspex.Markup.Xaml.DataBinding throw new InvalidOperationException(); } - var binding = new XamlBinding(_typeConverterProvider) + var binding = new XamlBinding(TypeConverterProvider) { BindingMode = xamlBinding.BindingMode, SourcePropertyPath = xamlBinding.SourcePropertyPath, diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs index af0d33de6a..c23a5bce22 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs @@ -2,16 +2,15 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Diagnostics; +using System.Reactive.Linq; using OmniXaml.TypeConversion; -using Perspex.Markup.Xaml.DataBinding.ChangeTracking; +using Perspex.Markup.Binding; namespace Perspex.Markup.Xaml.DataBinding { public class XamlBinding { private readonly ITypeConverterProvider _typeConverterProvider; - private DataContextChangeSynchronizer _changeSynchronizer; public XamlBinding(ITypeConverterProvider typeConverterProvider) { @@ -22,43 +21,43 @@ namespace Perspex.Markup.Xaml.DataBinding public PerspexProperty TargetProperty { get; set; } - public PropertyPath SourcePropertyPath { get; set; } + public object Source { get; set; } + + public string SourcePropertyPath { get; set; } public BindingMode BindingMode { get; set; } - public void BindToDataContext(object dataContext) + public void Bind() { - if (dataContext == null) - { - return; - } + var path = SourcePropertyPath; + var source = Source; - try + if (source == null) { - var bindingSource = new DataContextChangeSynchronizer.BindingSource(SourcePropertyPath, dataContext); - var bindingTarget = new DataContextChangeSynchronizer.BindingTarget(Target, TargetProperty); - - _changeSynchronizer = new DataContextChangeSynchronizer(bindingSource, bindingTarget, _typeConverterProvider); - - if (BindingMode == BindingMode.TwoWay) - { - _changeSynchronizer.StartUpdatingTargetWhenSourceChanges(); - _changeSynchronizer.StartUpdatingSourceWhenTargetChanges(); - } - - if (BindingMode == BindingMode.OneWay) + if (!string.IsNullOrWhiteSpace(path)) { - _changeSynchronizer.StartUpdatingTargetWhenSourceChanges(); + path = "DataContext." + path; } - if (BindingMode == BindingMode.OneWayToSource) - { - _changeSynchronizer.StartUpdatingSourceWhenTargetChanges(); - } + source = Target; } - catch (Exception e) + + var observable = new ExpressionObserver(source, path); + var mode = BindingMode == BindingMode.Default ? + TargetProperty.DefaultBindingMode : BindingMode; + + switch (mode) { - Debug.WriteLine(e); + case BindingMode.Default: + case BindingMode.OneWay: + Target.Bind(TargetProperty, observable.Select(x => x.Value)); + break; + case BindingMode.TwoWay: + throw new NotImplementedException(); + case BindingMode.OneTime: + throw new NotImplementedException(); + case BindingMode.OneWayToSource: + throw new NotImplementedException(); } } } diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs index cce86455d6..4cbd3bfe1e 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs +++ b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs @@ -2,31 +2,26 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Perspex.Controls; -using Perspex.Markup.Xaml.DataBinding.ChangeTracking; namespace Perspex.Markup.Xaml.DataBinding { public class XamlBindingDefinition { - private readonly PropertyPath _sourcePropertyPath; - private readonly BindingMode _bindingMode; - private readonly Control _target; - private readonly PerspexProperty _targetProperty; - - public XamlBindingDefinition(Control target, PerspexProperty targetProperty, PropertyPath sourcePropertyPath, BindingMode bindingMode) + public XamlBindingDefinition( + Control target, + PerspexProperty targetProperty, + string sourcePropertyPath, + BindingMode bindingMode) { - _target = target; - _targetProperty = targetProperty; - _sourcePropertyPath = sourcePropertyPath; - _bindingMode = bindingMode; + Target = target; + TargetProperty = targetProperty; + SourcePropertyPath = sourcePropertyPath; + BindingMode = bindingMode; } - public Control Target => _target; - - public PerspexProperty TargetProperty => _targetProperty; - - public PropertyPath SourcePropertyPath => _sourcePropertyPath; - - public BindingMode BindingMode => _bindingMode; + public Control Target { get; } + public PerspexProperty TargetProperty { get; } + public string SourcePropertyPath { get; } + public BindingMode BindingMode { get; } } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 9777ba3c9d..55f2b1ee4a 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -5,7 +5,6 @@ using System.Linq; using OmniXaml; using Perspex.Controls; using Perspex.Markup.Xaml.DataBinding; -using Perspex.Markup.Xaml.DataBinding.ChangeTracking; namespace Perspex.Markup.Xaml.MarkupExtensions { @@ -26,19 +25,10 @@ namespace Perspex.Markup.Xaml.MarkupExtensions var targetProperty = extensionContext.TargetProperty; var targetPropertyName = targetProperty.Name; var perspexProperty = target.GetRegisteredProperties().First(property => property.Name == targetPropertyName); - - return new XamlBindingDefinition - ( - target, - perspexProperty, - new PropertyPath(Path), - Mode == BindingMode.Default ? BindingMode.OneWay : Mode - ); + return new XamlBindingDefinition(target, perspexProperty, Path, Mode); } - /// The source path (for CLR bindings). public string Path { get; set; } - public BindingMode Mode { get; set; } } } \ 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 f6b9dcf204..d3de32e339 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -54,10 +54,6 @@ - - - - @@ -228,7 +224,6 @@ - @@ -289,6 +284,10 @@ {F1BAA01A-F176-4C6A-B39D-5B40BB1B148F} Perspex.Styling + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + diff --git a/src/Perspex.Controls/Properties/AssemblyInfo.cs b/src/Perspex.Controls/Properties/AssemblyInfo.cs index bf1ddab5d3..7c0a189889 100644 --- a/src/Perspex.Controls/Properties/AssemblyInfo.cs +++ b/src/Perspex.Controls/Properties/AssemblyInfo.cs @@ -9,5 +9,6 @@ using Perspex.Metadata; [assembly: InternalsVisibleTo("Perspex.Controls.UnitTests")] [assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls")] +[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Presenters")] [assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Primitives")] [assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Shapes")] \ No newline at end of file diff --git a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs b/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs index e968db3615..5c85e79202 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs @@ -4,21 +4,19 @@ using System; using Perspex.Controls; using Perspex.Markup.Xaml.DataBinding; -using Perspex.Markup.Xaml.DataBinding.ChangeTracking; namespace Perspex.Xaml.Base.UnitTest { public class BindingDefinitionBuilder { private readonly BindingMode _bindingMode; - private readonly PropertyPath _sourcePropertyPath; + private readonly string _sourcePropertyPath; private Control _target; - private PerspexProperty _targetProperty; public BindingDefinitionBuilder() { _bindingMode = BindingMode.Default; - _sourcePropertyPath = new PropertyPath(string.Empty); + _sourcePropertyPath = string.Empty; } public BindingDefinitionBuilder WithNullTarget() @@ -33,7 +31,7 @@ namespace Perspex.Xaml.Base.UnitTest bindingMode: _bindingMode, sourcePropertyPath: _sourcePropertyPath, target: _target, - targetProperty: _targetProperty); + targetProperty: null); } } } \ No newline at end of file diff --git a/tests/Perspex.Markup.Xaml.UnitTests/ChangeBranchTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/ChangeBranchTest.cs deleted file mode 100644 index 202cfed999..0000000000 --- a/tests/Perspex.Markup.Xaml.UnitTests/ChangeBranchTest.cs +++ /dev/null @@ -1,72 +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 Perspex.Markup.Xaml.DataBinding.ChangeTracking; -using Perspex.Xaml.Base.UnitTest.SampleModel; -using Xunit; - -namespace Perspex.Xaml.Base.UnitTest -{ - public class ChangeBranchTest - { - [Fact] - public void GetValueOfMemberOfStruct() - { - var level1 = new Level1(); - level1.DateTime = new DateTime(1, 2, 3, 4, 5, 6); - - var branch = new ObservablePropertyBranch(level1, new PropertyPath("DateTime.Minute")); - - var day = branch.Value; - Assert.Equal(day, branch.Value); - } - - [Fact] - public void OnePathOnly() - { - var level1 = new Level1(); - - var branch = new ObservablePropertyBranch(level1, new PropertyPath("Text")); - var newValue = "Hey now"; - branch.Value = newValue; - - Assert.Equal(level1.Text, newValue); - } - - [Fact] - public void SettingValueToUnderlyingProperty_ChangesTheValueInBranch() - { - var level1 = new Level1(); - - level1.Level2.Level3.Property = 3; - - var branch = new ObservablePropertyBranch(level1, new PropertyPath("Level2.Level3.Property")); - Assert.Equal(3, branch.Value); - } - - [Fact] - public void SettingValueToBranch_ChangesTheUnderlyingProperty() - { - var level1 = new Level1(); - - var branch = new ObservablePropertyBranch(level1, new PropertyPath("Level2.Level3.Property")); - branch.Value = 3; - Assert.Equal(3, level1.Level2.Level3.Property); - } - - [Fact] - public void SettingValueProperty_RaisesChangeInBranch() - { - var level1 = new Level1(); - - var branch = new ObservablePropertyBranch(level1, new PropertyPath("Level2.Level3.Property")); - bool received = false; - ObservableExtensions.Subscribe(branch.Values, v => received = ((int)v == 3)); - - level1.Level2.Level3.Property = 3; - - Assert.True(received); - } - } -} diff --git a/tests/Perspex.Markup.Xaml.UnitTests/DataContextChangeSynchronizerTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/DataContextChangeSynchronizerTest.cs deleted file mode 100644 index 0b93a2421d..0000000000 --- a/tests/Perspex.Markup.Xaml.UnitTests/DataContextChangeSynchronizerTest.cs +++ /dev/null @@ -1,103 +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 Perspex.Controls; -using GitHubClient.ViewModels; -using Perspex.Markup.Xaml.DataBinding; -using Perspex.Markup.Xaml.DataBinding.ChangeTracking; -using OmniXaml.Builder; -using OmniXaml.TypeConversion; -using OmniXaml.TypeConversion.BuiltInConverters; -using Perspex.Xaml.Base.UnitTest.SampleModel; -using Xunit; - -namespace Perspex.Xaml.Base.UnitTest -{ - public class DataContextChangeSynchronizerTest - { - private readonly TypeConverterProvider _repo; - private readonly SamplePerspexObject _guiObject; - private readonly ViewModelMock _viewModel; - - public DataContextChangeSynchronizerTest() - { - _repo = new TypeConverterProvider(); - _guiObject = new SamplePerspexObject(); - _viewModel = new ViewModelMock(); - } - - [Fact] - public void SameTypesFromUIToModel() - { - var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.IntProperty), _repo); - synchronizer.StartUpdatingSourceWhenTargetChanges(); - - const int someValue = 4; - _guiObject.Int = someValue; - - Assert.Equal(someValue, _viewModel.IntProp); - } - - [Fact] - public void DifferentTypesFromUIToModel() - { - var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.StringProperty), _repo); - synchronizer.StartUpdatingSourceWhenTargetChanges(); - - _guiObject.String = "2"; - - Assert.Equal(2, _viewModel.IntProp); - } - - [Fact] - public void DifferentTypesAndNonConvertibleValueFromUIToModel() - { - var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.StringProperty), _repo); - synchronizer.StartUpdatingSourceWhenTargetChanges(); - - _guiObject.String = ""; - - Assert.Equal(default(int), _viewModel.IntProp); - } - - - [Fact] - public void DifferentTypesFromModelToUI() - { - var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.StringProperty), _repo); - synchronizer.StartUpdatingTargetWhenSourceChanges(); - - _viewModel.IntProp = 2; - - Assert.Equal("2", _guiObject.String); - } - - [Fact] - public void SameTypesFromModelToUI() - { - var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.IntProperty), _repo); - synchronizer.StartUpdatingTargetWhenSourceChanges(); - - _viewModel.IntProp = 2; - - Assert.Equal(2, _guiObject.Int); - } - - [Fact] - public void GrokysTest() - { - var mainWindowViewModel = new MainWindowViewModel(); - var contentControl = new ContentControl(); - - var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("Content"), mainWindowViewModel), new DataContextChangeSynchronizer.BindingTarget(contentControl, ContentControl.ContentProperty), _repo); - - synchronizer.StartUpdatingTargetWhenSourceChanges(); - - var logInViewModel = new LogInViewModel(); - mainWindowViewModel.Content = logInViewModel; - - Assert.Equal(logInViewModel, contentControl.Content); - } - } -} 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 3d489dd682..b74704c7fa 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -88,9 +88,7 @@ - - @@ -105,7 +103,6 @@ - diff --git a/tests/Perspex.Markup.Xaml.UnitTests/PropertyMountPointTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/PropertyMountPointTest.cs deleted file mode 100644 index 1b15094af5..0000000000 --- a/tests/Perspex.Markup.Xaml.UnitTests/PropertyMountPointTest.cs +++ /dev/null @@ -1,18 +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 Perspex.Markup.Xaml.DataBinding.ChangeTracking; -using System; -using Xunit; - -namespace Perspex.Xaml.Base.UnitTest -{ - public class PropertyMountPointTest - { - [Fact] - public void SourceAndPathAreNull() - { - Assert.Throws(() => new PropertyMountPoint(null, null)); - } - } -} diff --git a/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs index d7ee56245f..fcd90f6333 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs @@ -13,9 +13,9 @@ namespace Perspex.Xaml.Base.UnitTest [Fact] public void TestNullDataContext() { - var t = new Mock(); - var sut = new XamlBinding(t.Object); - sut.BindToDataContext(null); + //var t = new Mock(); + //var sut = new XamlBinding(t.Object); + //sut.BindTo(null); } } } From 82be7441ad1d99385caea37fdbc6f804c74f33a7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 10:50:36 +0200 Subject: [PATCH 30/54] Added ExpressionSubject. --- .../Binding/ExpressionObserver.cs | 4 +- .../Binding/ExpressionSubject.cs | 48 +++++++++++++++++++ .../Perspex.Markup/Perspex.Markup.csproj | 1 + 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs index bd0b08b7c3..c50868b549 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs @@ -2,15 +2,13 @@ // 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.Linq; using System.Reactive; using System.Reactive.Disposables; namespace Perspex.Markup.Binding { /// - /// Observes the value of an expression on a root object. + /// Observes and sets the value of an expression on an object. /// public class ExpressionObserver : ObservableBase { diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs b/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs new file mode 100644 index 0000000000..e2e94daf7c --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs @@ -0,0 +1,48 @@ +// 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.Markup.Binding +{ + /// + /// Turns an into a subject that can be bound two-ways. + /// + public class ExpressionSubject : ISubject + { + private ExpressionObserver _inner; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public ExpressionSubject(ExpressionObserver inner) + { + _inner = inner; + } + + /// + public void OnCompleted() + { + } + + /// + public void OnError(Exception error) + { + } + + /// + public void OnNext(object value) + { + _inner.SetValue(value); + } + + /// + public IDisposable Subscribe(IObserver observer) + { + return _inner.Select(x => x.Value).Distinct().Subscribe(observer); + } + } +} diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj index aee9048774..d22201ee9f 100644 --- a/src/Markup/Perspex.Markup/Perspex.Markup.csproj +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -36,6 +36,7 @@ + From 4d5abf80e7dbd64833baa16b100afa115de00a4c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 11:43:08 +0200 Subject: [PATCH 31/54] Added a binding test application. --- Perspex.sln | 7 + samples/BindingTest/App.config | 6 + samples/BindingTest/App.cs | 31 ++++ samples/BindingTest/BindingTest.csproj | 142 ++++++++++++++++++ samples/BindingTest/MainWindow.paml | 6 + samples/BindingTest/MainWindow.paml.cs | 21 +++ .../BindingTest/Properties/AssemblyInfo.cs | 36 +++++ .../ViewModels/MainWindowViewModel.cs | 15 ++ samples/BindingTest/packages.config | 4 + 9 files changed, 268 insertions(+) create mode 100644 samples/BindingTest/App.config create mode 100644 samples/BindingTest/App.cs create mode 100644 samples/BindingTest/BindingTest.csproj create mode 100644 samples/BindingTest/MainWindow.paml create mode 100644 samples/BindingTest/MainWindow.paml.cs create mode 100644 samples/BindingTest/Properties/AssemblyInfo.cs create mode 100644 samples/BindingTest/ViewModels/MainWindowViewModel.cs create mode 100644 samples/BindingTest/packages.config diff --git a/Perspex.sln b/Perspex.sln index 3b97ab21cf..1dc7c56517 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -100,6 +100,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup", "src\Marku EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup.UnitTests", "tests\Perspex.Markup.UnitTests\Perspex.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 @@ -247,6 +249,10 @@ Global {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Debug|Any CPU.Build.0 = Debug|Any CPU {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Release|Any CPU.ActiveCfg = Release|Any CPU {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Release|Any CPU.Build.0 = Release|Any CPU + {08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -274,5 +280,6 @@ Global {E4D9629C-F168-4224-3F51-A5E482FFBC42} = {A689DEF5-D50F-4975-8B72-124C9EB54066} {6417E941-21BC-467B-A771-0DE389353CE6} = {8B6A8209-894F-4BA1-B880-965FD453982C} {8EF392D5-1416-45AA-9956-7CBBC3229E8A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {08B3E6B9-1CD5-443C-9F61-6D49D1C5F162} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection EndGlobal diff --git a/samples/BindingTest/App.config b/samples/BindingTest/App.config new file mode 100644 index 0000000000..8324aa6ff1 --- /dev/null +++ b/samples/BindingTest/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/BindingTest/App.cs b/samples/BindingTest/App.cs new file mode 100644 index 0000000000..df2ba5a586 --- /dev/null +++ b/samples/BindingTest/App.cs @@ -0,0 +1,31 @@ +using System; +using Perspex; +using Perspex.Controls; +using Perspex.Diagnostics; +using Perspex.Themes.Default; + +namespace BindingTest +{ + public class App : Application + { + public App() + { + RegisterServices(); + InitializeSubsystems((int)Environment.OSVersion.Platform); + Styles = new DefaultTheme(); + } + + public static void AttachDevTools(Window window) + { + DevTools.Attach(window); + } + + private static void Main() + { + var app = new App(); + var window = new MainWindow(); + window.Show(); + app.Run(window); + } + } +} diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj new file mode 100644 index 0000000000..0e0925adb2 --- /dev/null +++ b/samples/BindingTest/BindingTest.csproj @@ -0,0 +1,142 @@ + + + + + Debug + AnyCPU + {08B3E6B9-1CD5-443C-9F61-6D49D1C5F162} + WinExe + Properties + BindingTest + BindingTest + v4.6 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + ..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll + True + + + + + + + + + + + + + + MainWindow.paml + + + + + + + + Designer + MSBuild:Compile + + + + + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Perspex.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Perspex.Application + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {7062ae20-5dcc-4442-9645-8195bdece63e} + Perspex.Diagnostics + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {6417b24e-49c2-4985-8db2-3ab9d898ec91} + Perspex.ReactiveUI + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} + Perspex.Themes.Default + + + {3e908f67-5543-4879-a1dc-08eace79b3cd} + Perspex.Direct2D1 + + + {811a76cf-1cf6-440f-963b-bbe31bd72a82} + Perspex.Win32 + + + + + \ No newline at end of file diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml new file mode 100644 index 0000000000..2c7b0681d1 --- /dev/null +++ b/samples/BindingTest/MainWindow.paml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/BindingTest/MainWindow.paml.cs b/samples/BindingTest/MainWindow.paml.cs new file mode 100644 index 0000000000..a8e64485cb --- /dev/null +++ b/samples/BindingTest/MainWindow.paml.cs @@ -0,0 +1,21 @@ +using BindingTest.ViewModels; +using Perspex.Controls; +using Perspex.Markup.Xaml; + +namespace BindingTest +{ + public class MainWindow : Window + { + public MainWindow() + { + this.InitializeComponent(); + this.DataContext = new MainWindowViewModel(); + App.AttachDevTools(this); + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} diff --git a/samples/BindingTest/Properties/AssemblyInfo.cs b/samples/BindingTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..648b1cb406 --- /dev/null +++ b/samples/BindingTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BindingTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BindingTest")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("08b3e6b9-1cd5-443c-9f61-6d49d1c5f162")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000000..e8ba113eb8 --- /dev/null +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,15 @@ +using ReactiveUI; + +namespace BindingTest.ViewModels +{ + public class MainWindowViewModel : ReactiveObject + { + private string _simpleBinding = "Simple Binding"; + + public string SimpleBinding + { + get { return _simpleBinding; } + set { this.RaiseAndSetIfChanged(ref _simpleBinding, value); } + } + } +} diff --git a/samples/BindingTest/packages.config b/samples/BindingTest/packages.config new file mode 100644 index 0000000000..85a1a3cdc5 --- /dev/null +++ b/samples/BindingTest/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 907505f1941842ff3e92da56df745f0ebb475c85 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 12:12:40 +0200 Subject: [PATCH 32/54] Get simple two-way bindings working. --- samples/BindingTest/App.cs | 9 +++ samples/BindingTest/BindingTest.csproj | 8 +++ samples/BindingTest/packages.config | 1 + .../DataBinding/XamlBinding.cs | 3 +- .../Binding/ExpressionSubject.cs | 2 +- src/Perspex.Base/PerspexObject.cs | 62 ++++++++++++++++--- src/Perspex.Controls/TextBlock.cs | 2 +- .../Perspex.Markup.Xaml.UnitTests.csproj | 7 ++- .../packages.config | 2 +- 9 files changed, 82 insertions(+), 14 deletions(-) diff --git a/samples/BindingTest/App.cs b/samples/BindingTest/App.cs index df2ba5a586..79222817cd 100644 --- a/samples/BindingTest/App.cs +++ b/samples/BindingTest/App.cs @@ -3,6 +3,8 @@ using Perspex; using Perspex.Controls; using Perspex.Diagnostics; using Perspex.Themes.Default; +using Serilog; +using Serilog.Filters; namespace BindingTest { @@ -13,6 +15,13 @@ namespace BindingTest RegisterServices(); InitializeSubsystems((int)Environment.OSVersion.Platform); Styles = new DefaultTheme(); + + Log.Logger = new LoggerConfiguration() + .Filter.ByIncludingOnly(Matching.WithProperty("Area", "Property")) + .Filter.ByIncludingOnly(Matching.WithProperty("Property", x => x == "Text")) + .MinimumLevel.Verbose() + .WriteTo.Trace(outputTemplate: "[{Id:X8}] [{SourceContext}] {Message}") + .CreateLogger(); } public static void AttachDevTools(Window window) diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index 0e0925adb2..f98cc1dd08 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -36,6 +36,14 @@ + + ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.dll + True + + + ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.FullNetFx.dll + True + ..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll True diff --git a/samples/BindingTest/packages.config b/samples/BindingTest/packages.config index 85a1a3cdc5..6a1eddec98 100644 --- a/samples/BindingTest/packages.config +++ b/samples/BindingTest/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs index c23a5bce22..8bf09ec63c 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs @@ -53,7 +53,8 @@ namespace Perspex.Markup.Xaml.DataBinding Target.Bind(TargetProperty, observable.Select(x => x.Value)); break; case BindingMode.TwoWay: - throw new NotImplementedException(); + Target.BindTwoWay(TargetProperty, new ExpressionSubject(observable)); + break; case BindingMode.OneTime: throw new NotImplementedException(); case BindingMode.OneWayToSource: diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs b/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs index e2e94daf7c..cd076c9ab7 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs @@ -42,7 +42,7 @@ namespace Perspex.Markup.Binding /// public IDisposable Subscribe(IObserver observer) { - return _inner.Select(x => x.Value).Distinct().Subscribe(observer); + return _inner.Select(x => x.Value).Subscribe(observer); } } } diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 7a0e211f2c..0c27fe9453 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Reactive.Subjects; using System.Reflection; using Perspex.Reactive; using Perspex.Utilities; @@ -492,6 +493,7 @@ namespace Perspex throw new ArgumentException($"The property {property.Name} is readonly."); } + LogPropertySet(property, value, priority); property.Setter(this, value); } else @@ -524,14 +526,9 @@ namespace Perspex _values.Add(property, v); } + LogPropertySet(property, value, priority); v.SetValue(value, (int)priority); } - - _propertyLog.Verbose( - "Set {Property} to {$Value} with priority {Priority}", - property, - value, - priority); } /// @@ -557,6 +554,7 @@ namespace Perspex throw new ArgumentException($"The property {property.Name} is readonly."); } + LogPropertySet(property, value, priority); property.Setter(this, value); } else @@ -658,7 +656,7 @@ namespace Perspex } /// - /// Initialites a two-way bind between s. + /// Initiates a two-way binding between s. /// /// The property on this object. /// The source object. @@ -676,11 +674,46 @@ namespace Perspex PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue) { + _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) + { + _propertyLog.Verbose( + "Bound two way {Property} to {Binding} with priority {Priority}", + property, + source, + priority); + + return new CompositeDisposable( + Bind(property, source), + GetObservable(property).Subscribe(source)); + } + /// /// Forces the specified property to be revalidated. /// @@ -931,6 +964,21 @@ namespace Perspex return string.Format("{0}.{1}", GetType().Name, property.Name); } + /// + /// Logs a property set message. + /// + /// The property. + /// The new value. + /// The priority. + private void LogPropertySet(PerspexProperty property, object value, BindingPriority priority) + { + _propertyLog.Verbose( + "Set {Property} to {$Value} with priority {Priority}", + property, + value, + priority); + } + /// /// Throws an exception indicating that the specified property is not registered on this /// object. diff --git a/src/Perspex.Controls/TextBlock.cs b/src/Perspex.Controls/TextBlock.cs index 607754dcb2..904e2d81d7 100644 --- a/src/Perspex.Controls/TextBlock.cs +++ b/src/Perspex.Controls/TextBlock.cs @@ -65,7 +65,7 @@ namespace Perspex.Controls /// Defines the property. /// public static readonly PerspexProperty TextProperty = - PerspexProperty.Register(nameof(Text)); + PerspexProperty.Register(nameof(Text), defaultBindingMode: BindingMode.TwoWay); /// /// Defines the property. 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 b74704c7fa..6c851a1e87 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -46,13 +46,14 @@ ..\..\packages\Octokit.0.14.0\lib\net45\Octokit.dll + + ..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll + True + ..\..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll - - ..\..\packages\Splat.1.6.1\lib\Net45\Splat.dll - ..\..\packages\Sprache.2.0.0.47\lib\portable-net4+netcore45+win8+wp8+sl5+MonoAndroid1+MonoTouch1\Sprache.dll diff --git a/tests/Perspex.Markup.Xaml.UnitTests/packages.config b/tests/Perspex.Markup.Xaml.UnitTests/packages.config index c7f996e816..f0bea65dab 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/packages.config +++ b/tests/Perspex.Markup.Xaml.UnitTests/packages.config @@ -6,7 +6,7 @@ - + From 8457645eb456d0796b703068079a4fe6c15d5fd7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 12:32:41 +0200 Subject: [PATCH 33/54] Implemented OneWayToSource binding. --- samples/BindingTest/MainWindow.paml | 7 +++++-- src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs | 3 ++- src/Perspex.Base/BindingDescriptor.cs | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml index 2c7b0681d1..38e7c14d40 100644 --- a/samples/BindingTest/MainWindow.paml +++ b/samples/BindingTest/MainWindow.paml @@ -1,6 +1,9 @@  - + - + + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs index 8bf09ec63c..330a835fc6 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs @@ -58,7 +58,8 @@ namespace Perspex.Markup.Xaml.DataBinding case BindingMode.OneTime: throw new NotImplementedException(); case BindingMode.OneWayToSource: - throw new NotImplementedException(); + Target.GetObservable(TargetProperty).Subscribe(new ExpressionSubject(observable)); + break; } } } diff --git a/src/Perspex.Base/BindingDescriptor.cs b/src/Perspex.Base/BindingDescriptor.cs index 1f491ec029..0006843ae3 100644 --- a/src/Perspex.Base/BindingDescriptor.cs +++ b/src/Perspex.Base/BindingDescriptor.cs @@ -27,7 +27,7 @@ namespace Perspex TwoWay, /// - /// Copies the target to the source one time and then disposes of the binding. + /// Updates the target when the application starts or when the data context changes. /// OneTime, From 072aa3c96de3fcf4b9c94fba398123801870ac50 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 12:48:00 +0200 Subject: [PATCH 34/54] Allow ExpressionObserver root to be changed. --- .../Binding/ExpressionObserver.cs | 26 +++++++++++++++--- .../ExpressionObserverTests_Property.cs | 27 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs index c50868b549..fc7a4b2663 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs @@ -12,6 +12,7 @@ namespace Perspex.Markup.Binding /// public class ExpressionObserver : ObservableBase { + private object _root; private int _count; private ExpressionNode _node; @@ -22,7 +23,7 @@ namespace Perspex.Markup.Binding /// The expression. public ExpressionObserver(object root, string expression) { - Root = root; + _root = root; _node = ExpressionNodeBuilder.Build(expression); } @@ -49,9 +50,28 @@ namespace Perspex.Markup.Binding } /// - /// Gets the root object that the expression is being observed on. + /// Gets or sets the root object that the expression is being observed on. /// - public object Root { get; } + public object Root + { + get + { + return _root; + } + + set + { + if (_root != value) + { + _root = value; + + if (_count > 0) + { + _node.Target = _root; + } + } + } + } /// protected override IDisposable SubscribeCore(IObserver observer) diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs index ac159a2deb..3518cfa52d 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs @@ -200,6 +200,33 @@ namespace Perspex.Markup.UnitTests.Binding Assert.Throws(() => target.SetValue(1.2)); } + [Fact] + public async void Should_Handle_Null_Root() + { + var target = new ExpressionObserver(null, "Foo"); + var result = await target.Take(1); + + Assert.False(result.HasValue); + } + + [Fact] + public void Can_Replace_Root() + { + var first = new Class1 { Foo = "foo" }; + var second = new Class1 { Foo = "bar" }; + var target = new ExpressionObserver(first, "Foo"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + target.Root = second; + target.Root = null; + + Assert.Equal(new[] { "foo", "bar", null }, result); + + Assert.Equal(0, first.SubscriptionCount); + Assert.Equal(0, second.SubscriptionCount); + } + private interface INext { int SubscriptionCount { get; } From d100f49ba8e261c51a7aeaf10630c2d5138093cd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 12:52:17 +0200 Subject: [PATCH 35/54] Renamed namespace DataBinding -> Binding. --- .../IPerspexPropertyBinder.cs | 2 +- .../PerspexPropertyBinder.cs | 2 +- .../SourceBindingEndpoint.cs | 2 +- .../TargetBindingEndpoint.cs | 2 +- .../{DataBinding => Binding}/XamlBinding.cs | 2 +- .../XamlBindingDefinition.cs | 2 +- .../Context/PerspexTypeRepository.cs | 2 +- .../Context/PerspexWiringContext.cs | 2 +- .../Perspex.Markup.Xaml/Context/PerspexXamlMember.cs | 2 +- .../Context/PerspexXamlMemberValuePlugin.cs | 2 +- .../Perspex.Markup.Xaml/Context/PerspexXamlType.cs | 2 +- .../MarkupExtensions/BindingExtension.cs | 2 +- .../Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj | 12 ++++++------ .../Templates/TreeDataTemplate.cs | 2 +- tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs | 2 +- .../BindingDefinitionBuilder.cs | 2 +- .../Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs | 2 +- 17 files changed, 22 insertions(+), 22 deletions(-) rename src/Markup/Perspex.Markup.Xaml/{DataBinding => Binding}/IPerspexPropertyBinder.cs (92%) rename src/Markup/Perspex.Markup.Xaml/{DataBinding => Binding}/PerspexPropertyBinder.cs (97%) rename src/Markup/Perspex.Markup.Xaml/{DataBinding => Binding}/SourceBindingEndpoint.cs (94%) rename src/Markup/Perspex.Markup.Xaml/{DataBinding => Binding}/TargetBindingEndpoint.cs (91%) rename src/Markup/Perspex.Markup.Xaml/{DataBinding => Binding}/XamlBinding.cs (97%) rename src/Markup/Perspex.Markup.Xaml/{DataBinding => Binding}/XamlBindingDefinition.cs (94%) diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/Binding/IPerspexPropertyBinder.cs similarity index 92% rename from src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs rename to src/Markup/Perspex.Markup.Xaml/Binding/IPerspexPropertyBinder.cs index 40d349e00d..a98f32e7f1 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/IPerspexPropertyBinder.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using OmniXaml.TypeConversion; -namespace Perspex.Markup.Xaml.DataBinding +namespace Perspex.Markup.Xaml.Binding { public interface IPerspexPropertyBinder { diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs similarity index 97% rename from src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs rename to src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs index 916e3edd85..b0e2029ee0 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using OmniXaml.TypeConversion; -namespace Perspex.Markup.Xaml.DataBinding +namespace Perspex.Markup.Xaml.Binding { public class PerspexPropertyBinder : IPerspexPropertyBinder { diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/SourceBindingEndpoint.cs b/src/Markup/Perspex.Markup.Xaml/Binding/SourceBindingEndpoint.cs similarity index 94% rename from src/Markup/Perspex.Markup.Xaml/DataBinding/SourceBindingEndpoint.cs rename to src/Markup/Perspex.Markup.Xaml/Binding/SourceBindingEndpoint.cs index 3ed6304049..60b03f31ce 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/SourceBindingEndpoint.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/SourceBindingEndpoint.cs @@ -4,7 +4,7 @@ using System; using System.ComponentModel; -namespace Perspex.Markup.Xaml.DataBinding +namespace Perspex.Markup.Xaml.Binding { public class SourceBindingEndpoint { diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/TargetBindingEndpoint.cs b/src/Markup/Perspex.Markup.Xaml/Binding/TargetBindingEndpoint.cs similarity index 91% rename from src/Markup/Perspex.Markup.Xaml/DataBinding/TargetBindingEndpoint.cs rename to src/Markup/Perspex.Markup.Xaml/Binding/TargetBindingEndpoint.cs index aff0f63ec6..316dcddcc4 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/TargetBindingEndpoint.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/TargetBindingEndpoint.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.Markup.Xaml.DataBinding +namespace Perspex.Markup.Xaml.Binding { public class TargetBindingEndpoint { diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs similarity index 97% rename from src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs rename to src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs index 330a835fc6..8204b55d17 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs @@ -6,7 +6,7 @@ using System.Reactive.Linq; using OmniXaml.TypeConversion; using Perspex.Markup.Binding; -namespace Perspex.Markup.Xaml.DataBinding +namespace Perspex.Markup.Xaml.Binding { public class XamlBinding { diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs similarity index 94% rename from src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs rename to src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs index 4cbd3bfe1e..da0c6d633e 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs @@ -3,7 +3,7 @@ using Perspex.Controls; -namespace Perspex.Markup.Xaml.DataBinding +namespace Perspex.Markup.Xaml.Binding { public class XamlBindingDefinition { diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs index 4d32ee8658..f0dcf0a444 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs @@ -5,7 +5,7 @@ using System; using Glass; using OmniXaml; using OmniXaml.Typing; -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; namespace Perspex.Markup.Xaml.Context { diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs index 933a39dbe4..c5373e031a 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs @@ -15,7 +15,7 @@ using Perspex.Controls; using Perspex.Input; using Perspex.Markup.Xaml.Templates; using Perspex.Markup.Xaml.Converters; -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; using Perspex.Markup.Xaml.MarkupExtensions; using Perspex.Media; using Perspex.Media.Imaging; diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs index dc4c4cbde2..172911a6f2 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.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. -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; using OmniXaml; using OmniXaml.Typing; diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index f978ecaa69..908fcd22fe 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs @@ -7,7 +7,7 @@ using Glass; using OmniXaml.ObjectAssembler; using OmniXaml.Typing; using Perspex.Controls; -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; using Perspex.Styling; namespace Perspex.Markup.Xaml.Context diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs index 827f6f3827..29de09c241 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs @@ -4,7 +4,7 @@ using System; using OmniXaml; using OmniXaml.Typing; -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; namespace Perspex.Markup.Xaml.Context { diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 55f2b1ee4a..5c124de956 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -4,7 +4,7 @@ using System.Linq; using OmniXaml; using Perspex.Controls; -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; namespace Perspex.Markup.Xaml.MarkupExtensions { diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index a117474005..7bfc7dc51a 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -38,6 +38,12 @@ Properties\SharedAssemblyInfo.cs + + + + + + @@ -224,12 +230,6 @@ - - - - - - diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs index e66d12ba75..23955b7682 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -6,7 +6,7 @@ using System.Collections; using OmniXaml.Attributes; using Perspex.Controls; using Perspex.Controls.Templates; -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; namespace Perspex.Markup.Xaml.Templates { diff --git a/tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs index 4a421bce2f..a39f4b0667 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs @@ -3,7 +3,7 @@ using System; using Moq; -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; using OmniXaml.TypeConversion; using Xunit; diff --git a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs b/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs index 985850e3e9..7e986201e3 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Perspex.Controls; -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; namespace Perspex.Xaml.Base.UnitTest { diff --git a/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs index fcd90f6333..6e0d14d5cf 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Moq; -using Perspex.Markup.Xaml.DataBinding; +using Perspex.Markup.Xaml.Binding; using OmniXaml.TypeConversion; using Xunit; From 4f09de4dfe419c18c2a36ad743a4b16f90dddf46 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 13:42:12 +0200 Subject: [PATCH 36/54] Started adding XamlBinding unit tests. --- .../Binding/XamlBinding.cs | 26 ++++- src/Perspex.Base/IObservablePropertyBag.cs | 38 ++++++ .../Binding/XamlBindingTests.cs | 109 ++++++++++++++++++ .../Perspex.Markup.Xaml.UnitTests.csproj | 1 + .../SelectorTests_Child.cs | 11 ++ .../SelectorTests_Descendent.cs | 11 ++ .../TestControlBase.cs | 11 ++ .../TestTemplatedControl.cs | 11 ++ 8 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs index 8204b55d17..1bd2e86fd5 100644 --- a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs @@ -3,6 +3,7 @@ using System; using System.Reactive.Linq; +using System.Reactive.Subjects; using OmniXaml.TypeConversion; using Perspex.Markup.Binding; @@ -12,12 +13,16 @@ namespace Perspex.Markup.Xaml.Binding { private readonly ITypeConverterProvider _typeConverterProvider; + public XamlBinding() + { + } + public XamlBinding(ITypeConverterProvider typeConverterProvider) { _typeConverterProvider = typeConverterProvider; } - public PerspexObject Target { get; set; } + public IObservablePropertyBag Target { get; set; } public PerspexProperty TargetProperty { get; set; } @@ -28,6 +33,11 @@ namespace Perspex.Markup.Xaml.Binding public BindingMode BindingMode { get; set; } public void Bind() + { + Bind(new ExpressionSubject(CreateExpressionObserver())); + } + + public ExpressionObserver CreateExpressionObserver() { var path = SourcePropertyPath; var source = Source; @@ -42,23 +52,27 @@ namespace Perspex.Markup.Xaml.Binding source = Target; } - var observable = new ExpressionObserver(source, path); - var mode = BindingMode == BindingMode.Default ? + return new ExpressionObserver(source, path); + } + + internal void Bind(ISubject subject) + { + var mode = BindingMode == BindingMode.Default ? TargetProperty.DefaultBindingMode : BindingMode; switch (mode) { case BindingMode.Default: case BindingMode.OneWay: - Target.Bind(TargetProperty, observable.Select(x => x.Value)); + Target.Bind(TargetProperty, subject); break; case BindingMode.TwoWay: - Target.BindTwoWay(TargetProperty, new ExpressionSubject(observable)); + Target.BindTwoWay(TargetProperty, subject); break; case BindingMode.OneTime: throw new NotImplementedException(); case BindingMode.OneWayToSource: - Target.GetObservable(TargetProperty).Subscribe(new ExpressionSubject(observable)); + Target.GetObservable(TargetProperty).Subscribe(subject); break; } } diff --git a/src/Perspex.Base/IObservablePropertyBag.cs b/src/Perspex.Base/IObservablePropertyBag.cs index 3bc8e41178..451cb63a89 100644 --- a/src/Perspex.Base/IObservablePropertyBag.cs +++ b/src/Perspex.Base/IObservablePropertyBag.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive.Subjects; namespace Perspex { @@ -39,6 +40,43 @@ namespace Perspex 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 . /// diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs new file mode 100644 index 0000000000..7955438b0b --- /dev/null +++ b/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs @@ -0,0 +1,109 @@ +// 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; +using Moq; +using Perspex.Controls; +using Perspex.Markup.Xaml.Binding; +using Xunit; + +namespace Perspex.Markup.Xaml.UnitTests.Binding +{ + public class XamlBindingTests + { + [Fact] + public void OneWay_Binding_Should_Be_Set_Up() + { + var target = CreateTarget(); + var binding = new XamlBinding + { + Target = target.Object, + TargetProperty = TextBox.TextProperty, + SourcePropertyPath = "Foo", + BindingMode = BindingMode.OneWay, + }; + + binding.Bind(); + + target.Verify(x => x.Bind( + TextBox.TextProperty, + It.IsAny>(), + BindingPriority.LocalValue)); + } + + [Fact] + public void TwoWay_Binding_Should_Be_Set_Up() + { + var target = CreateTarget(); + var binding = new XamlBinding + { + Target = target.Object, + TargetProperty = TextBox.TextProperty, + SourcePropertyPath = "Foo", + BindingMode = BindingMode.TwoWay, + }; + + binding.Bind(); + + target.Verify(x => x.BindTwoWay( + TextBox.TextProperty, + It.IsAny>(), + BindingPriority.LocalValue)); + } + + [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 XamlBinding + { + Target = target.Object, + TargetProperty = TextBox.TextProperty, + SourcePropertyPath = "Foo", + BindingMode = BindingMode.OneWayToSource, + }; + + binding.Bind(expression.Object); + + textObservable.Verify(x => x.Subscribe(expression.Object)); + } + + [Fact] + public void Default_BindingMode_Should_Be_Used() + { + var target = CreateTarget(null); + var binding = new XamlBinding + { + Target = target.Object, + TargetProperty = TextBox.TextProperty, + SourcePropertyPath = "Foo", + }; + + binding.Bind(); + + // Default for TextBox.Text is two-way. + target.Verify(x => x.BindTwoWay( + TextBox.TextProperty, + It.IsAny>(), + BindingPriority.LocalValue)); + } + + 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((PerspexProperty)Control.DataContextProperty)).Returns(dataContext); + result.Setup(x => x.GetObservable((PerspexProperty)TextBox.TextProperty)).Returns(text); + return 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 6c851a1e87..e014a04a48 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -89,6 +89,7 @@ + diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index 845a712465..1a682b28b1 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Reactive.Linq; +using System.Reactive.Subjects; using System.Threading.Tasks; using Perspex.Collections; using Perspex.Styling; @@ -141,6 +142,16 @@ namespace Perspex.Styling.UnitTests { 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 74b2a3345a..3eb660f8a8 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Reactive.Linq; +using System.Reactive.Subjects; using System.Threading.Tasks; using Perspex.Collections; using Perspex.Styling; @@ -173,6 +174,16 @@ namespace Perspex.Styling.UnitTests { throw new NotImplementedException(); } + + public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue) + { + throw new NotImplementedException(); + } + + public IDisposable BindTwoWay(PerspexProperty property, ISubject source, BindingPriority priority = BindingPriority.LocalValue) + { + throw new NotImplementedException(); + } } public class TestLogical1 : TestLogical diff --git a/tests/Perspex.Styling.UnitTests/TestControlBase.cs b/tests/Perspex.Styling.UnitTests/TestControlBase.cs index 8b621fe533..3c1612ee76 100644 --- a/tests/Perspex.Styling.UnitTests/TestControlBase.cs +++ b/tests/Perspex.Styling.UnitTests/TestControlBase.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive.Subjects; namespace Perspex.Styling.UnitTests { @@ -81,5 +82,15 @@ namespace Perspex.Styling.UnitTests { 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(); + } } } diff --git a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs index 8fd47a27fb..6b4d32d2ef 100644 --- a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs +++ b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Reactive.Subjects; namespace Perspex.Styling.UnitTests { @@ -87,5 +88,15 @@ namespace Perspex.Styling.UnitTests { 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(); + } } } From 1064fbd18683f89521e5a85e0b15e9df316f5ac1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 14:41:51 +0200 Subject: [PATCH 37/54] Implement OneTime binding. --- samples/BindingTest/MainWindow.paml | 8 ++--- .../Binding/XamlBinding.cs | 27 ++++++---------- .../Binding/XamlBindingTests.cs | 32 +++++++++++++++++++ 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml index 38e7c14d40..3ac2621738 100644 --- a/samples/BindingTest/MainWindow.paml +++ b/samples/BindingTest/MainWindow.paml @@ -1,9 +1,9 @@  - - - - + + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs index 1bd2e86fd5..3e3e978f7c 100644 --- a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs @@ -5,6 +5,7 @@ using System; using System.Reactive.Linq; using System.Reactive.Subjects; using OmniXaml.TypeConversion; +using Perspex.Controls; using Perspex.Markup.Binding; namespace Perspex.Markup.Xaml.Binding @@ -26,8 +27,6 @@ namespace Perspex.Markup.Xaml.Binding public PerspexProperty TargetProperty { get; set; } - public object Source { get; set; } - public string SourcePropertyPath { get; set; } public BindingMode BindingMode { get; set; } @@ -39,20 +38,10 @@ namespace Perspex.Markup.Xaml.Binding public ExpressionObserver CreateExpressionObserver() { - var path = SourcePropertyPath; - var source = Source; - - if (source == null) - { - if (!string.IsNullOrWhiteSpace(path)) - { - path = "DataContext." + path; - } - - source = Target; - } - - return new ExpressionObserver(source, path); + var result = new ExpressionObserver(null, SourcePropertyPath); + var dataContext = Target.GetObservable(Control.DataContextProperty); + dataContext.Subscribe(x => result.Root = x); + return result; } internal void Bind(ISubject subject) @@ -70,7 +59,11 @@ namespace Perspex.Markup.Xaml.Binding Target.BindTwoWay(TargetProperty, subject); break; case BindingMode.OneTime: - throw new NotImplementedException(); + Target.GetObservable(Control.DataContextProperty).Subscribe(dataContext => + { + subject.Take(1).Subscribe(x => Target.SetValue(TargetProperty, x)); + }); + break; case BindingMode.OneWayToSource: Target.GetObservable(TargetProperty).Subscribe(subject); break; diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs index 7955438b0b..321df1b475 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs @@ -53,6 +53,37 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding BindingPriority.LocalValue)); } + [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 binding = new XamlBinding + { + Target = target.Object, + TargetProperty = TextBox.TextProperty, + SourcePropertyPath = "Foo", + BindingMode = BindingMode.OneTime, + }; + + binding.Bind(expression); + + 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)); + } + [Fact] public void OneWayToSource_Binding_Should_Be_Set_Up() { @@ -101,6 +132,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding 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; From 0c837f87a89dfc953bd17af2153456aa1ade018e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 15:51:33 +0200 Subject: [PATCH 38/54] Add demo of binding to array index. Tried to add a button to shuffle ObservableCollection, but Command binding doesn't seem to work. --- samples/BindingTest/App.cs | 1 - samples/BindingTest/BindingTest.csproj | 17 +++++++++++++ samples/BindingTest/MainWindow.paml | 18 +++++++++----- .../ViewModels/MainWindowViewModel.cs | 24 ++++++++++++++++++- samples/BindingTest/ViewModels/TestItem.cs | 15 ++++++++++++ samples/BindingTest/packages.config | 5 ++++ src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- src/Perspex.Base/PerspexObject.cs | 2 +- 8 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 samples/BindingTest/ViewModels/TestItem.cs diff --git a/samples/BindingTest/App.cs b/samples/BindingTest/App.cs index 79222817cd..8399166015 100644 --- a/samples/BindingTest/App.cs +++ b/samples/BindingTest/App.cs @@ -18,7 +18,6 @@ namespace BindingTest Log.Logger = new LoggerConfiguration() .Filter.ByIncludingOnly(Matching.WithProperty("Area", "Property")) - .Filter.ByIncludingOnly(Matching.WithProperty("Property", x => x == "Text")) .MinimumLevel.Verbose() .WriteTo.Trace(outputTemplate: "[{Id:X8}] [{SourceContext}] {Message}") .CreateLogger(); diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index f98cc1dd08..62914fdc13 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -50,6 +50,22 @@ + + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + True + @@ -64,6 +80,7 @@ + diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml index 3ac2621738..e42dd640e0 100644 --- a/samples/BindingTest/MainWindow.paml +++ b/samples/BindingTest/MainWindow.paml @@ -1,9 +1,15 @@  - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index e8ba113eb8..59e4b42f3e 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -1,4 +1,6 @@ -using ReactiveUI; +using System; +using System.Collections.ObjectModel; +using ReactiveUI; namespace BindingTest.ViewModels { @@ -6,6 +8,26 @@ namespace BindingTest.ViewModels { private string _simpleBinding = "Simple Binding"; + public MainWindowViewModel() + { + Items = new ObservableCollection + { + new TestItem { StringValue = "Foo" }, + new TestItem { StringValue = "Bar" }, + new TestItem { StringValue = "Baz" }, + }; + + ShuffleItems = ReactiveCommand.Create(); + ShuffleItems.Subscribe(_ => + { + var r = new Random(); + Items[r.Next(Items.Count)] = Items[r.Next(Items.Count)]; + }); + } + + public ObservableCollection Items { get; } + public ReactiveCommand ShuffleItems { get; } + public string SimpleBinding { get { return _simpleBinding; } diff --git a/samples/BindingTest/ViewModels/TestItem.cs b/samples/BindingTest/ViewModels/TestItem.cs new file mode 100644 index 0000000000..759f8ac974 --- /dev/null +++ b/samples/BindingTest/ViewModels/TestItem.cs @@ -0,0 +1,15 @@ +using ReactiveUI; + +namespace BindingTest.ViewModels +{ + public class TestItem : ReactiveObject + { + private string stringValue = "String Value"; + + public string StringValue + { + get { return stringValue; } + set { this.RaiseAndSetIfChanged(ref this.stringValue, value); } + } + } +} diff --git a/samples/BindingTest/packages.config b/samples/BindingTest/packages.config index 6a1eddec98..2bac0ad760 100644 --- a/samples/BindingTest/packages.config +++ b/samples/BindingTest/packages.config @@ -1,5 +1,10 @@  + + + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 49e6ec001f..c890902fba 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 49e6ec001f5873cf2290e0bc1f6f06ca9b9cf808 +Subproject commit c890902fbaecb4752ed8e9fde2571396794fe06d diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 0c27fe9453..4d0ec855fd 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -187,7 +187,7 @@ namespace Perspex if (sourceBinding == null && mode > BindingMode.OneWay) { - throw new InvalidOperationException("Can only bind OneWay to plain IObservable."); + mode = BindingMode.OneWay; } switch (mode) From 3730d6067fe064d6f80e2de9405065bbcb1c5321 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 16:01:06 +0200 Subject: [PATCH 39/54] Add workaround for ReactiveCommand. --- src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index c1a45d8784..3bdfb2ff3a 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -7,6 +7,7 @@ using System.Reactive.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using System.Windows.Input; namespace Perspex.Markup.Binding { @@ -107,10 +108,12 @@ namespace Perspex.Markup.Binding { var value = _propertyInfo.GetValue(target); var observable = value as IObservable; + var command = value as ICommand; var task = value as Task; bool set = false; - if (observable != null) + // ReactiveCommand is an IObservable but we want to bind to it, not its value. + if (observable != null && command == null) { CurrentValue = ExpressionValue.None; set = true; From 3fee7587bc3c3943836f23f5e2420f2eb985b03a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 16:01:39 +0200 Subject: [PATCH 40/54] Make the shuffle more noticable. --- samples/BindingTest/ViewModels/MainWindowViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index 59e4b42f3e..d9647d0ee2 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -21,7 +21,7 @@ namespace BindingTest.ViewModels ShuffleItems.Subscribe(_ => { var r = new Random(); - Items[r.Next(Items.Count)] = Items[r.Next(Items.Count)]; + Items[1] = Items[r.Next(Items.Count)]; }); } From ed4f46a6cf88f3c8c759b694c02a652705f24804 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 18:10:01 +0200 Subject: [PATCH 41/54] Don't throw when binding produces invalid value. --- src/Perspex.Base/PriorityValue.cs | 26 +++++++++---------- .../PerspexObjectTests_Binding.cs | 9 +++---- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/Perspex.Base/PriorityValue.cs b/src/Perspex.Base/PriorityValue.cs index 53ecd8da99..011f07cfa0 100644 --- a/src/Perspex.Base/PriorityValue.cs +++ b/src/Perspex.Base/PriorityValue.cs @@ -212,25 +212,23 @@ namespace Perspex /// The priority level that the value came from. private void UpdateValue(object value, int priority) { - if (!TypeUtilities.TryCast(_valueType, value, out value)) + if (TypeUtilities.TryCast(_valueType, value, out value)) { - throw new InvalidOperationException(string.Format( - "Invalid value for Property '{0}': {1} ({2})", - _name, - value, - value?.GetType().FullName ?? "(null)")); - } + var old = _value; - var old = _value; + if (_validate != null) + { + value = _validate(value); + } - if (_validate != null) + ValuePriority = priority; + _value = value; + _changed.OnNext(Tuple.Create(old, _value)); + } + else { - value = _validate(value); + // TODO: Log error. } - - ValuePriority = priority; - _value = value; - _changed.OnNext(Tuple.Create(old, _value)); } /// diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs index 7c8e60543d..49534082a9 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs @@ -59,14 +59,11 @@ namespace Perspex.Base.UnitTests } [Fact] - public void Bind_Throws_Exception_For_Invalid_Value_Type() + public void Bind_Ignores_Invalid_Value_Type() { Class1 target = new Class1(); - - Assert.Throws(() => - { - target.Bind((PerspexProperty)Class1.FooProperty, Observable.Return((object)123)); - }); + target.Bind((PerspexProperty)Class1.FooProperty, Observable.Return((object)123)); + Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } [Fact] From d3e374d0c6c4f455147c209520f6d6cec88988e2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 18:10:47 +0200 Subject: [PATCH 42/54] Update OmniXAML --- src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index c890902fba..708f8fdf02 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit c890902fbaecb4752ed8e9fde2571396794fe06d +Subproject commit 708f8fdf0236029b3f8e87961390cc726260ade9 From a5ec3588c531477f43c8b35c38fb8ce4e4b4a725 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 18:11:21 +0200 Subject: [PATCH 43/54] Test binding negation in BindingTest. --- samples/BindingTest/MainWindow.paml | 17 ++++++++++++----- .../ViewModels/MainWindowViewModel.cs | 15 +++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml index e42dd640e0..739651b21e 100644 --- a/samples/BindingTest/MainWindow.paml +++ b/samples/BindingTest/MainWindow.paml @@ -1,15 +1,22 @@  - - - - - + + + + + + + + + + !BooleanString + !!BooleanString + \ No newline at end of file diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index d9647d0ee2..f62add3712 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -6,7 +6,8 @@ namespace BindingTest.ViewModels { public class MainWindowViewModel : ReactiveObject { - private string _simpleBinding = "Simple Binding"; + private string _booleanString = "True"; + private string _stringValue = "Simple Binding"; public MainWindowViewModel() { @@ -28,10 +29,16 @@ namespace BindingTest.ViewModels public ObservableCollection Items { get; } public ReactiveCommand ShuffleItems { get; } - public string SimpleBinding + public string BooleanString { - get { return _simpleBinding; } - set { this.RaiseAndSetIfChanged(ref _simpleBinding, value); } + get { return _booleanString; } + set { this.RaiseAndSetIfChanged(ref _booleanString, value); } + } + + public string StringValue + { + get { return _stringValue; } + set { this.RaiseAndSetIfChanged(ref _stringValue, value); } } } } From 9aa3c18c58cb031cc4133ff79629b84ccc3ea63e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 18:20:39 +0200 Subject: [PATCH 44/54] Fixed shuffle. --- samples/BindingTest/ViewModels/MainWindowViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index f62add3712..8722568aee 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -22,7 +22,7 @@ namespace BindingTest.ViewModels ShuffleItems.Subscribe(_ => { var r = new Random(); - Items[1] = Items[r.Next(Items.Count)]; + Items.Move(r.Next(Items.Count), 1); }); } From 14ca308900acd8eaa2fb7b253f04ba38e017a9a6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 18:31:08 +0200 Subject: [PATCH 45/54] Log binding path. --- .../Binding/ExpressionObserver.cs | 11 ++++++++- .../Binding/ExpressionSubject.cs | 5 +++- .../Perspex.Markup/Perspex.Markup.csproj | 6 +++++ src/Perspex.Base/PerspexObject.cs | 24 +++++++++++++------ .../Perspex.Markup.UnitTests.csproj | 4 ++++ 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs index fc7a4b2663..ff98e82aeb 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs @@ -10,7 +10,7 @@ namespace Perspex.Markup.Binding /// /// Observes and sets the value of an expression on an object. /// - public class ExpressionObserver : ObservableBase + public class ExpressionObserver : ObservableBase, IDescription { private object _root; private int _count; @@ -25,6 +25,7 @@ namespace Perspex.Markup.Binding { _root = root; _node = ExpressionNodeBuilder.Build(expression); + Expression = expression; } /// @@ -49,6 +50,11 @@ namespace Perspex.Markup.Binding } } + /// + /// Gets the expression being observed. + /// + public string Expression { get; } + /// /// Gets or sets the root object that the expression is being observed on. /// @@ -73,6 +79,9 @@ namespace Perspex.Markup.Binding } } + /// + string IDescription.Description => Expression; + /// protected override IDisposable SubscribeCore(IObserver observer) { diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs b/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs index cd076c9ab7..ac5700e512 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs @@ -10,7 +10,7 @@ namespace Perspex.Markup.Binding /// /// Turns an into a subject that can be bound two-ways. /// - public class ExpressionSubject : ISubject + public class ExpressionSubject : ISubject, IDescription { private ExpressionObserver _inner; @@ -23,6 +23,9 @@ namespace Perspex.Markup.Binding _inner = inner; } + /// + string IDescription.Description => _inner.Expression; + /// public void OnCompleted() { diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj index d22201ee9f..a7e6bf61cd 100644 --- a/src/Markup/Perspex.Markup/Perspex.Markup.csproj +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -71,6 +71,12 @@ + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + --> + diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs index b0e2029ee0..9e4eee4b84 100644 --- a/src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs @@ -22,38 +22,41 @@ namespace Perspex.Markup.Xaml.Binding public XamlBinding GetBinding(PerspexObject po, PerspexProperty pp) { - return _bindings.First(xamlBinding => xamlBinding.Target == po && xamlBinding.TargetProperty == pp); + throw new NotImplementedException(); + //return _bindings.First(xamlBinding => xamlBinding.Target == po && xamlBinding.TargetProperty == pp); } public IEnumerable GetBindings(PerspexObject source) { - return from binding in _bindings - where binding.Target == source - select binding; + throw new NotImplementedException(); + //return from binding in _bindings + // where binding.Target == source + // select binding; } public XamlBinding Create(XamlBindingDefinition xamlBinding) { - if (xamlBinding.Target == null) - { - throw new InvalidOperationException(); - } - - if (xamlBinding.TargetProperty == null) - { - throw new InvalidOperationException(); - } - - var binding = new XamlBinding(TypeConverterProvider) - { - BindingMode = xamlBinding.BindingMode, - SourcePropertyPath = xamlBinding.SourcePropertyPath, - Target = xamlBinding.Target, - TargetProperty = xamlBinding.TargetProperty - }; - - _bindings.Add(binding); - return binding; + throw new NotImplementedException(); + //if (xamlBinding.Target == null) + //{ + // throw new InvalidOperationException(); + //} + + //if (xamlBinding.TargetProperty == null) + //{ + // throw new InvalidOperationException(); + //} + + //var binding = new XamlBinding(TypeConverterProvider) + //{ + // BindingMode = xamlBinding.BindingMode, + // SourcePropertyPath = xamlBinding.SourcePropertyPath, + // Target = xamlBinding.Target, + // TargetProperty = xamlBinding.TargetProperty + //}; + + //_bindings.Add(binding); + //return binding; } } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs index 3e3e978f7c..3bf709e928 100644 --- a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs @@ -23,49 +23,46 @@ namespace Perspex.Markup.Xaml.Binding _typeConverterProvider = typeConverterProvider; } - public IObservablePropertyBag Target { get; set; } - - public PerspexProperty TargetProperty { get; set; } - public string SourcePropertyPath { get; set; } public BindingMode BindingMode { get; set; } - public void Bind() + public void Bind(IObservablePropertyBag instance, PerspexProperty property) { - Bind(new ExpressionSubject(CreateExpressionObserver())); + var subject = new ExpressionSubject(CreateExpressionObserver(instance)); + Bind(instance, property, subject); } - public ExpressionObserver CreateExpressionObserver() + public ExpressionObserver CreateExpressionObserver(IObservablePropertyBag instance) { var result = new ExpressionObserver(null, SourcePropertyPath); - var dataContext = Target.GetObservable(Control.DataContextProperty); + var dataContext = instance.GetObservable(Control.DataContextProperty); dataContext.Subscribe(x => result.Root = x); return result; } - internal void Bind(ISubject subject) + internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject subject) { var mode = BindingMode == BindingMode.Default ? - TargetProperty.DefaultBindingMode : BindingMode; + property.DefaultBindingMode : BindingMode; switch (mode) { case BindingMode.Default: case BindingMode.OneWay: - Target.Bind(TargetProperty, subject); + target.Bind(property, subject); break; case BindingMode.TwoWay: - Target.BindTwoWay(TargetProperty, subject); + target.BindTwoWay(property, subject); break; case BindingMode.OneTime: - Target.GetObservable(Control.DataContextProperty).Subscribe(dataContext => + target.GetObservable(Control.DataContextProperty).Subscribe(dataContext => { - subject.Take(1).Subscribe(x => Target.SetValue(TargetProperty, x)); + subject.Take(1).Subscribe(x => target.SetValue(property, x)); }); break; case BindingMode.OneWayToSource: - Target.GetObservable(TargetProperty).Subscribe(subject); + target.GetObservable(property).Subscribe(subject); break; } } diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs index da0c6d633e..c571036d4d 100644 --- a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs @@ -8,19 +8,13 @@ namespace Perspex.Markup.Xaml.Binding public class XamlBindingDefinition { public XamlBindingDefinition( - Control target, - PerspexProperty targetProperty, string sourcePropertyPath, BindingMode bindingMode) { - Target = target; - TargetProperty = targetProperty; SourcePropertyPath = sourcePropertyPath; BindingMode = bindingMode; } - public Control Target { get; } - public PerspexProperty TargetProperty { get; } public string SourcePropertyPath { get; } public BindingMode BindingMode { get; } } diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index 908fcd22fe..1bb324c5a8 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using System.Reactive.Linq; using Glass; using OmniXaml.ObjectAssembler; @@ -27,7 +28,7 @@ namespace Perspex.Markup.Xaml.Context { if (value is XamlBindingDefinition) { - HandleXamlBindingDefinition((XamlBindingDefinition)value); + HandleXamlBindingDefinition(instance, (XamlBindingDefinition)value); } else if (IsPerspexProperty) { @@ -54,17 +55,32 @@ namespace Perspex.Markup.Xaml.Context po.SetValue(pp, value); } - private void HandleXamlBindingDefinition(XamlBindingDefinition def) + private void HandleXamlBindingDefinition(object instance, XamlBindingDefinition def) { + var perspexObject = instance as PerspexObject; + + if (perspexObject == null) + { + throw new InvalidOperationException( + $"Cannot bind to an object of type '{instance.GetType()}"); + } + + var property = perspexObject.GetRegisteredProperties() + .FirstOrDefault(x => x.Name == _xamlMember.Name); + + if (property == null) + { + throw new InvalidOperationException( + $"Cannot find '{_xamlMember.Name}' on '{instance.GetType()}"); + } + var binding = new XamlBinding(_propertyBinder.TypeConverterProvider) { BindingMode = def.BindingMode, SourcePropertyPath = def.SourcePropertyPath, - Target = def.Target, - TargetProperty = def.TargetProperty, }; - binding.Bind(); + binding.Bind(perspexObject, property); } private void BindToDataContextWhenItsSet(XamlBindingDefinition definition) diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 5c124de956..7abc09ef89 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -21,11 +21,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions public override object ProvideValue(MarkupExtensionContext extensionContext) { - var target = extensionContext.TargetObject as Control; - var targetProperty = extensionContext.TargetProperty; - var targetPropertyName = targetProperty.Name; - var perspexProperty = target.GetRegisteredProperties().First(property => property.Name == targetPropertyName); - return new XamlBindingDefinition(target, perspexProperty, Path, Mode); + return new XamlBindingDefinition(Path, Mode); } public string Path { get; set; } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs deleted file mode 100644 index a39f4b0667..0000000000 --- a/tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs +++ /dev/null @@ -1,27 +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 Moq; -using Perspex.Markup.Xaml.Binding; -using OmniXaml.TypeConversion; -using Xunit; - -namespace Perspex.Xaml.Base.UnitTest -{ - public class BinderTest - { - [Fact] - public void NullTarget_Throws() - { - var typeConverter = new Mock(); - var perspexPropertyBinder = new PerspexPropertyBinder(typeConverter.Object); - var bindingDefinitionBuilder = new BindingDefinitionBuilder(); - var binding = bindingDefinitionBuilder - .WithNullTarget() - .Build(); - - var exception = Assert.Throws(() => perspexPropertyBinder.Create(binding)); - } - } -} diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs index 321df1b475..97e0dc7741 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs @@ -19,13 +19,11 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding var target = CreateTarget(); var binding = new XamlBinding { - Target = target.Object, - TargetProperty = TextBox.TextProperty, SourcePropertyPath = "Foo", BindingMode = BindingMode.OneWay, }; - binding.Bind(); + binding.Bind(target.Object, TextBox.TextProperty); target.Verify(x => x.Bind( TextBox.TextProperty, @@ -39,13 +37,11 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding var target = CreateTarget(); var binding = new XamlBinding { - Target = target.Object, - TargetProperty = TextBox.TextProperty, SourcePropertyPath = "Foo", BindingMode = BindingMode.TwoWay, }; - binding.Bind(); + binding.Bind(target.Object, TextBox.TextProperty); target.Verify(x => x.BindTwoWay( TextBox.TextProperty, @@ -61,13 +57,11 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding var target = CreateTarget(dataContext: dataContext); var binding = new XamlBinding { - Target = target.Object, - TargetProperty = TextBox.TextProperty, SourcePropertyPath = "Foo", BindingMode = BindingMode.OneTime, }; - binding.Bind(expression); + binding.Bind(target.Object, TextBox.TextProperty, expression); target.Verify(x => x.SetValue( (PerspexProperty)TextBox.TextProperty, @@ -92,13 +86,11 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding var target = CreateTarget(text: textObservable.Object); var binding = new XamlBinding { - Target = target.Object, - TargetProperty = TextBox.TextProperty, SourcePropertyPath = "Foo", BindingMode = BindingMode.OneWayToSource, }; - binding.Bind(expression.Object); + binding.Bind(target.Object, TextBox.TextProperty, expression.Object); textObservable.Verify(x => x.Subscribe(expression.Object)); } @@ -109,12 +101,10 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding var target = CreateTarget(null); var binding = new XamlBinding { - Target = target.Object, - TargetProperty = TextBox.TextProperty, SourcePropertyPath = "Foo", }; - binding.Bind(); + binding.Bind(target.Object, TextBox.TextProperty); // Default for TextBox.Text is two-way. target.Verify(x => x.BindTwoWay( diff --git a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs b/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs deleted file mode 100644 index 7e986201e3..0000000000 --- a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs +++ /dev/null @@ -1,36 +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 Perspex.Controls; -using Perspex.Markup.Xaml.Binding; - -namespace Perspex.Xaml.Base.UnitTest -{ - public class BindingDefinitionBuilder - { - private readonly BindingMode _bindingMode; - private readonly string _sourcePropertyPath; - private Control _target; - - public BindingDefinitionBuilder() - { - _bindingMode = BindingMode.Default; - _sourcePropertyPath = string.Empty; - } - - public BindingDefinitionBuilder WithNullTarget() - { - _target = null; - return this; - } - - public XamlBindingDefinition Build() - { - return new XamlBindingDefinition( - bindingMode: _bindingMode, - sourcePropertyPath: _sourcePropertyPath, - target: _target, - targetProperty: null); - } - } -} \ No newline at end of file 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 e014a04a48..2ac180603f 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -88,7 +88,6 @@ - @@ -103,7 +102,6 @@ - From 92535c390448de1fff9e88d2c902de82fb014771 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 19:52:39 +0200 Subject: [PATCH 50/54] Make TreeDataTemplate work. --- .../XamlTestApplication/Views/MainWindow.paml | 4 +- .../Context/PerspexObjectAssembler.cs | 1 + .../Context/PerspexXamlMemberValuePlugin.cs | 64 +++++++++++-------- .../Templates/TreeDataTemplate.cs | 8 ++- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/samples/XamlTestApplication/Views/MainWindow.paml b/samples/XamlTestApplication/Views/MainWindow.paml index 9fe6a760a8..149948db44 100644 --- a/samples/XamlTestApplication/Views/MainWindow.paml +++ b/samples/XamlTestApplication/Views/MainWindow.paml @@ -70,14 +70,14 @@ - + diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs index 06d78ab753..25df85a3cc 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs @@ -16,6 +16,7 @@ namespace Perspex.Markup.Xaml.Context { var mapping = new DeferredLoaderMapping(); mapping.Map(template => template.Content, new TemplateLoader()); + mapping.Map(template => template.Content, new TemplateLoader()); var assembler = new ObjectAssembler(wiringContext, new TopDownValueContext(), objectAssemblerSettings); _objectAssembler = new TemplateHostingObjectAssembler(assembler, mapping); diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index 1bb324c5a8..c63bbc9a24 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Reactive.Linq; +using System.Reflection; using Glass; using OmniXaml.ObjectAssembler; using OmniXaml.Typing; @@ -57,39 +58,46 @@ namespace Perspex.Markup.Xaml.Context private void HandleXamlBindingDefinition(object instance, XamlBindingDefinition def) { - var perspexObject = instance as PerspexObject; - - if (perspexObject == null) + if (_xamlMember.XamlType.UnderlyingType == typeof(XamlBindingDefinition)) { - throw new InvalidOperationException( - $"Cannot bind to an object of type '{instance.GetType()}"); - } + // TODO: This should search base classes. + var property = instance.GetType().GetTypeInfo().GetDeclaredProperty(_xamlMember.Name); - var property = perspexObject.GetRegisteredProperties() - .FirstOrDefault(x => x.Name == _xamlMember.Name); + if (property == null || !property.CanWrite) + { + throw new InvalidOperationException( + $"Cannot assign to '{_xamlMember.Name}' on '{instance.GetType()}"); + } - if (property == null) - { - throw new InvalidOperationException( - $"Cannot find '{_xamlMember.Name}' on '{instance.GetType()}"); + property.SetValue(instance, def); } - - var binding = new XamlBinding(_propertyBinder.TypeConverterProvider) + else { - BindingMode = def.BindingMode, - SourcePropertyPath = def.SourcePropertyPath, - }; - - binding.Bind(perspexObject, property); - } - - private void BindToDataContextWhenItsSet(XamlBindingDefinition definition) - { - // var target = definition.Target; - // var dataContext = target.DataContext; - - // var binding = _propertyBinder.GetBinding(target, definition.TargetProperty); - // binding.BindToDataContext(dataContext); + var perspexObject = instance as PerspexObject; + + if (perspexObject == null) + { + throw new InvalidOperationException( + $"Cannot bind to an object of type '{instance.GetType()}"); + } + + var property = perspexObject.GetRegisteredProperties() + .FirstOrDefault(x => x.Name == _xamlMember.Name); + + if (property == null) + { + throw new InvalidOperationException( + $"Cannot find '{_xamlMember.Name}' on '{instance.GetType()}"); + } + + var binding = new XamlBinding(_propertyBinder.TypeConverterProvider) + { + BindingMode = def.BindingMode, + SourcePropertyPath = def.SourcePropertyPath, + }; + + binding.Bind(perspexObject, property); + } } // ReSharper disable once MemberCanBePrivate.Global diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs index 44b264897a..0868dae69f 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -3,9 +3,10 @@ using System; using System.Collections; -using OmniXaml.Attributes; +using System.Reactive.Linq; using Perspex.Controls; using Perspex.Controls.Templates; +using Perspex.Markup.Binding; using Perspex.Markup.Xaml.Binding; namespace Perspex.Markup.Xaml.Templates @@ -14,7 +15,7 @@ namespace Perspex.Markup.Xaml.Templates { public Type DataType { get; set; } public TemplateContent Content { get; set; } - public XamlBinding ItemsSource { get; set; } + public XamlBindingDefinition ItemsSource { get; set; } public bool Match(object data) { @@ -30,7 +31,8 @@ namespace Perspex.Markup.Xaml.Templates { if (ItemsSource != null) { - // TODO: Get value of ItemsSource here. + var obs = new ExpressionObserver(item, ItemsSource.SourcePropertyPath); + return obs.Take(1).Wait().Value as IEnumerable; } return null; From 521771d2543a031c36de41926d7f9c20e23c7eed Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 20:24:17 +0200 Subject: [PATCH 51/54] Removed unused class/interface. --- .../Binding/IPerspexPropertyBinder.cs | 19 ------ .../Binding/PerspexPropertyBinder.cs | 62 ------------------- .../Context/PerspexTypeRepository.cs | 7 +-- .../Context/PerspexWiringContext.cs | 3 +- .../Context/PerspexXamlMember.cs | 8 +-- .../Context/PerspexXamlMemberValuePlugin.cs | 7 +-- .../Context/PerspexXamlType.cs | 10 +-- .../Perspex.Markup.Xaml.csproj | 2 - 8 files changed, 10 insertions(+), 108 deletions(-) delete mode 100644 src/Markup/Perspex.Markup.Xaml/Binding/IPerspexPropertyBinder.cs delete mode 100644 src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/IPerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/Binding/IPerspexPropertyBinder.cs deleted file mode 100644 index a98f32e7f1..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/Binding/IPerspexPropertyBinder.cs +++ /dev/null @@ -1,19 +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.Collections.Generic; -using OmniXaml.TypeConversion; - -namespace Perspex.Markup.Xaml.Binding -{ - public interface IPerspexPropertyBinder - { - ITypeConverterProvider TypeConverterProvider { get; } - - XamlBinding GetBinding(PerspexObject po, PerspexProperty pp); - - IEnumerable GetBindings(PerspexObject source); - - XamlBinding Create(XamlBindingDefinition xamlBinding); - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs deleted file mode 100644 index 9e4eee4b84..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs +++ /dev/null @@ -1,62 +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.Linq; -using OmniXaml.TypeConversion; - -namespace Perspex.Markup.Xaml.Binding -{ - public class PerspexPropertyBinder : IPerspexPropertyBinder - { - private readonly HashSet _bindings; - - public PerspexPropertyBinder(ITypeConverterProvider typeConverterProvider) - { - TypeConverterProvider = typeConverterProvider; - _bindings = new HashSet(); - } - - public ITypeConverterProvider TypeConverterProvider { get; } - - public XamlBinding GetBinding(PerspexObject po, PerspexProperty pp) - { - throw new NotImplementedException(); - //return _bindings.First(xamlBinding => xamlBinding.Target == po && xamlBinding.TargetProperty == pp); - } - - public IEnumerable GetBindings(PerspexObject source) - { - throw new NotImplementedException(); - //return from binding in _bindings - // where binding.Target == source - // select binding; - } - - public XamlBinding Create(XamlBindingDefinition xamlBinding) - { - throw new NotImplementedException(); - //if (xamlBinding.Target == null) - //{ - // throw new InvalidOperationException(); - //} - - //if (xamlBinding.TargetProperty == null) - //{ - // throw new InvalidOperationException(); - //} - - //var binding = new XamlBinding(TypeConverterProvider) - //{ - // BindingMode = xamlBinding.BindingMode, - // SourcePropertyPath = xamlBinding.SourcePropertyPath, - // Target = xamlBinding.Target, - // TargetProperty = xamlBinding.TargetProperty - //}; - - //_bindings.Add(binding); - //return binding; - } - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs index f0dcf0a444..44dda9b9f9 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs @@ -12,21 +12,18 @@ namespace Perspex.Markup.Xaml.Context public class PerspexTypeRepository : XamlTypeRepository { private readonly ITypeFactory _typeFactory; - private readonly IPerspexPropertyBinder _propertyBinder; public PerspexTypeRepository(IXamlNamespaceRegistry xamlNamespaceRegistry, ITypeFactory typeFactory, - ITypeFeatureProvider featureProvider, - IPerspexPropertyBinder propertyBinder) : base(xamlNamespaceRegistry, typeFactory, featureProvider) + ITypeFeatureProvider featureProvider) : base(xamlNamespaceRegistry, typeFactory, featureProvider) { _typeFactory = typeFactory; - _propertyBinder = propertyBinder; } public override XamlType GetXamlType(Type type) { Guard.ThrowIfNull(type, nameof(type)); - return new PerspexXamlType(type, this, _typeFactory, FeatureProvider, _propertyBinder); + return new PerspexXamlType(type, this, _typeFactory, FeatureProvider); } } } \ 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 e02512fe1e..3e5b5c24c2 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs @@ -43,8 +43,7 @@ namespace Perspex.Markup.Xaml.Context private static ITypeContext CreateTypeContext(ITypeFactory typeFactory, TypeFeatureProvider featureProvider) { var xamlNamespaceRegistry = CreateXamlNamespaceRegistry(); - var perspexPropertyBinder = new PerspexPropertyBinder(featureProvider.ConverterProvider); - var typeRepository = new PerspexTypeRepository(xamlNamespaceRegistry, typeFactory, featureProvider, perspexPropertyBinder); + var typeRepository = new PerspexTypeRepository(xamlNamespaceRegistry, typeFactory, featureProvider); typeRepository.RegisterMetadata(new Metadata().WithMemberDependency(x => x.Value, x => x.Property)); typeRepository.RegisterMetadata( diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs index 172911a6f2..89979885f0 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs @@ -9,21 +9,17 @@ namespace Perspex.Markup.Xaml.Context { public class PerspexXamlMember : XamlMember { - private readonly IPerspexPropertyBinder _propertyBinder; - public PerspexXamlMember(string name, XamlType owner, IXamlTypeRepository xamlTypeRepository, - ITypeFeatureProvider featureProvider, - IPerspexPropertyBinder propertyBinder) + ITypeFeatureProvider featureProvider) : base(name, owner, xamlTypeRepository, featureProvider) { - _propertyBinder = propertyBinder; } protected override IXamlMemberValuePlugin LookupXamlMemberValueConnector() { - return new PerspexXamlMemberValuePlugin(this, _propertyBinder); + return new PerspexXamlMemberValuePlugin(this); } public override string ToString() diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index c63bbc9a24..fd7a786649 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs @@ -17,12 +17,11 @@ namespace Perspex.Markup.Xaml.Context public class PerspexXamlMemberValuePlugin : MemberValuePlugin { private readonly XamlMember _xamlMember; - private readonly IPerspexPropertyBinder _propertyBinder; - public PerspexXamlMemberValuePlugin(XamlMember xamlMember, IPerspexPropertyBinder propertyBinder) : base(xamlMember) + public PerspexXamlMemberValuePlugin(XamlMember xamlMember) + : base(xamlMember) { _xamlMember = xamlMember; - _propertyBinder = propertyBinder; } public override void SetValue(object instance, object value) @@ -90,7 +89,7 @@ namespace Perspex.Markup.Xaml.Context $"Cannot find '{_xamlMember.Name}' on '{instance.GetType()}"); } - var binding = new XamlBinding(_propertyBinder.TypeConverterProvider) + var binding = new XamlBinding { BindingMode = def.BindingMode, SourcePropertyPath = def.SourcePropertyPath, diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs index 29de09c241..0d1fb218b1 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs @@ -10,22 +10,16 @@ namespace Perspex.Markup.Xaml.Context { public class PerspexXamlType : XamlType { - private readonly IPerspexPropertyBinder _propertyBinder; - public PerspexXamlType(Type type, IXamlTypeRepository typeRepository, ITypeFactory typeFactory, - ITypeFeatureProvider featureProvider, - IPerspexPropertyBinder propertyBinder) : base(type, typeRepository, typeFactory, featureProvider) + ITypeFeatureProvider featureProvider) : base(type, typeRepository, typeFactory, featureProvider) { - _propertyBinder = propertyBinder; } - protected IPerspexPropertyBinder PropertyBinder => _propertyBinder; - protected override XamlMember LookupMember(string name) { - return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider, _propertyBinder); + return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider); } public override string ToString() diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 7bfc7dc51a..987b630907 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -38,8 +38,6 @@ Properties\SharedAssemblyInfo.cs - - From 69d8468c61446254b364bae566b5700ef133e05f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 20:36:08 +0200 Subject: [PATCH 52/54] Retarget OmniXAML to perspex fork. --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 93467688c1..057007f213 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,4 +7,4 @@ branch = perspex-pcl [submodule "src/Markup/Perspex.Markup.Xaml/OmniXAML"] path = src/Markup/Perspex.Markup.Xaml/OmniXAML - url = https://github.com/SuperJMN/OmniXAML.git + url = https://github.com/Perspex/OmniXAML.git From 4f67ed057f841aca4d200d591c86c892964a534e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 20:42:13 +0200 Subject: [PATCH 53/54] Updated OmniXAML --- src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 708f8fdf02..42b0e3b6ef 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 708f8fdf0236029b3f8e87961390cc726260ade9 +Subproject commit 42b0e3b6efc0905457120752be38a6000898fffa From df47a491e130d6b6397f54b6908c31918ffefcf0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 21:00:35 +0200 Subject: [PATCH 54/54] Fixed .sln. --- Perspex.sln | 1 + 1 file changed, 1 insertion(+) diff --git a/Perspex.sln b/Perspex.sln index 2c4c334a9c..6ab0be278c 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -101,6 +101,7 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup.UnitTests", "tests\Perspex.Markup.UnitTests\Perspex.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlTestApplicationPcl", "samples\XamlTestApplicationPcl\XamlTestApplicationPcl.csproj", "{EA113F1A-D8D7-4142-9948-353270E7EBAE}" EndProject Global