diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/RelativeSource.cs b/src/Markup/Perspex.Markup.Xaml/Binding/RelativeSource.cs new file mode 100644 index 0000000000..2c75144325 --- /dev/null +++ b/src/Markup/Perspex.Markup.Xaml/Binding/RelativeSource.cs @@ -0,0 +1,25 @@ +// 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.Binding +{ + public enum RelativeSourceMode + { + DataContext, + TemplatedParent, + } + + public class RelativeSource + { + public RelativeSource() + { + } + + public RelativeSource(RelativeSourceMode mode) + { + Mode = mode; + } + + public RelativeSourceMode Mode { get; set; } + } +} \ 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 61b6206f4e..54b4a59bdc 100644 --- a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs @@ -23,10 +23,11 @@ namespace Perspex.Markup.Xaml.Binding _typeConverterProvider = typeConverterProvider; } + public BindingMode Mode { get; set; } + public BindingPriority Priority { get; set; } + public RelativeSource RelativeSource { get; set; } public string SourcePropertyPath { get; set; } - public BindingMode BindingMode { get; set; } - public void Bind(IObservablePropertyBag instance, PerspexProperty property) { var subject = new ExpressionSubject( @@ -35,7 +36,18 @@ namespace Perspex.Markup.Xaml.Binding if (subject != null) { - Bind(instance, property, subject); + if (RelativeSource?.Mode != RelativeSourceMode.TemplatedParent) + { + Bind(instance, property, subject); + } + else + { + instance.GetObservable(Control.TemplatedParentProperty) + .Where(x => x != null) + .OfType() + .Take(1) + .Subscribe(x => BindToTemplatedParent((PerspexObject)instance, property, x)); + } } } @@ -62,8 +74,8 @@ namespace Perspex.Markup.Xaml.Binding internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject subject) { - var mode = BindingMode == BindingMode.Default ? - property.DefaultBindingMode : BindingMode; + var mode = Mode == BindingMode.Default ? + property.DefaultBindingMode : Mode; switch (mode) { @@ -85,5 +97,25 @@ namespace Perspex.Markup.Xaml.Binding break; } } + + private void BindToTemplatedParent( + PerspexObject instance, + PerspexProperty targetProperty, + PerspexObject templatedParent) + { + var sourceProperty = PerspexPropertyRegistry.Instance.FindRegistered( + templatedParent, + SourcePropertyPath); + + if (sourceProperty == null) + { + throw new InvalidOperationException( + $"The property {SourcePropertyPath} could not be found on {templatedParent.GetType()}."); + } + + instance.Bind(targetProperty, templatedParent.GetObservable(sourceProperty), BindingPriority.Style); + templatedParent.Bind(sourceProperty, instance.GetObservable(targetProperty), BindingPriority.Style); + } + } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs index b7f8f67ad0..4110169a7d 100644 --- a/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs +++ b/src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs @@ -1,24 +1,13 @@ // 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; - namespace Perspex.Markup.Xaml.Binding { public class XamlBindingDefinition { - public XamlBindingDefinition( - string sourcePropertyPath, - BindingMode bindingMode, - bool isTemplateBinding = false) - { - SourcePropertyPath = sourcePropertyPath; - BindingMode = bindingMode; - IsTemplateBinding = isTemplateBinding; - } - - public string SourcePropertyPath { get; } - public BindingMode BindingMode { get; } - public bool IsTemplateBinding { get; } + public BindingMode Mode { get; set; } + public BindingPriority Priority { get; set; } + public RelativeSource RelativeSource { get; set; } + public string SourcePropertyPath { get; set; } } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Binding/XamlTemplateBinding.cs b/src/Markup/Perspex.Markup.Xaml/Binding/XamlTemplateBinding.cs deleted file mode 100644 index 155d47841a..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/Binding/XamlTemplateBinding.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) The Perspex Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Reactive.Linq; -using OmniXaml.TypeConversion; -using Perspex.Controls; -using Perspex.Styling; - -namespace Perspex.Markup.Xaml.Binding -{ - public class XamlTemplateBinding - { - private readonly ITypeConverterProvider _typeConverterProvider; - - public XamlTemplateBinding() - { - } - - public XamlTemplateBinding(ITypeConverterProvider typeConverterProvider) - { - _typeConverterProvider = typeConverterProvider; - } - - public string SourcePropertyPath { get; set; } - - public BindingMode BindingMode { get; set; } - - public void Bind(PerspexObject instance, PerspexProperty targetProperty) - { - instance.GetObservable(Control.TemplatedParentProperty) - .Where(x => x != null) - .OfType() - .Take(1) - .Subscribe(x => BindToTemplatedParent(instance, targetProperty, x)); - } - - private void BindToTemplatedParent( - PerspexObject instance, - PerspexProperty targetProperty, - PerspexObject templatedParent) - { - var sourceProperty = PerspexPropertyRegistry.Instance.FindRegistered( - templatedParent, - SourcePropertyPath); - - if (sourceProperty == null) - { - throw new InvalidOperationException( - $"The property {SourcePropertyPath} could not be found on {templatedParent.GetType()}."); - } - - instance.Bind(targetProperty, templatedParent.GetObservable(sourceProperty), BindingPriority.Style); - templatedParent.Bind(sourceProperty, instance.GetObservable(targetProperty), BindingPriority.Style); - } - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index 280bcec5a6..4c26677c9b 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs @@ -109,26 +109,15 @@ namespace Perspex.Markup.Xaml.Context $"Cannot find '{propertyName}' on '{instance.GetType()}"); } - if (!def.IsTemplateBinding) + var binding = new XamlBinding { - var binding = new XamlBinding - { - BindingMode = def.BindingMode, - SourcePropertyPath = def.SourcePropertyPath, - }; + Mode = def.Mode, + Priority = def.Priority, + RelativeSource = def.RelativeSource, + SourcePropertyPath = def.SourcePropertyPath, + }; - binding.Bind(perspexObject, property); - } - else - { - var binding = new XamlTemplateBinding - { - BindingMode = def.BindingMode, - SourcePropertyPath = def.SourcePropertyPath, - }; - - binding.Bind(perspexObject, property); - } + binding.Bind(perspexObject, property); } } diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 7abc09ef89..1b401fb1df 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -21,7 +21,11 @@ namespace Perspex.Markup.Xaml.MarkupExtensions public override object ProvideValue(MarkupExtensionContext extensionContext) { - return new XamlBindingDefinition(Path, Mode); + return new XamlBindingDefinition + { + Mode = Mode, + SourcePropertyPath = Path, + }; } public string Path { get; set; } diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs index 6dbcc64c4f..bee224a409 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs @@ -19,7 +19,13 @@ namespace Perspex.Markup.Xaml.MarkupExtensions public override object ProvideValue(MarkupExtensionContext extensionContext) { - return new XamlBindingDefinition(Path, Mode, true); + return new XamlBindingDefinition + { + Mode = Mode, + Priority = BindingPriority.TemplatedParent, + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + SourcePropertyPath = Path, + }; } public string Path { get; set; } diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 954efb7293..24f6ee584f 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -38,6 +38,7 @@ Properties\SharedAssemblyInfo.cs + diff --git a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 7108c90158..3d43751130 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -404,13 +404,13 @@ namespace Perspex.Controls.UnitTests.Primitives var itemsBinding = new XamlBinding { SourcePropertyPath = "Items", - BindingMode = BindingMode.OneWay, + Mode = BindingMode.OneWay, }; var selectedItemsBinding = new XamlBinding { SourcePropertyPath = "SelectedItems", - BindingMode = BindingMode.OneWay, + Mode = BindingMode.OneWay, }; // Bind Items and SelectedItems to the VM. diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs index f78a7ed4ae..759c8e7ed5 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs @@ -20,7 +20,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding var binding = new XamlBinding { SourcePropertyPath = "Foo", - BindingMode = BindingMode.OneWay, + Mode = BindingMode.OneWay, }; binding.Bind(target.Object, TextBox.TextProperty); @@ -38,7 +38,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding var binding = new XamlBinding { SourcePropertyPath = "Foo", - BindingMode = BindingMode.TwoWay, + Mode = BindingMode.TwoWay, }; binding.Bind(target.Object, TextBox.TextProperty); @@ -58,7 +58,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding var binding = new XamlBinding { SourcePropertyPath = "Foo", - BindingMode = BindingMode.OneTime, + Mode = BindingMode.OneTime, }; binding.Bind(target.Object, TextBox.TextProperty, expression); @@ -87,7 +87,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding var binding = new XamlBinding { SourcePropertyPath = "Foo", - BindingMode = BindingMode.OneWayToSource, + Mode = BindingMode.OneWayToSource, }; binding.Bind(target.Object, TextBox.TextProperty, expression.Object); diff --git a/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs index ef286107ee..dfaef35925 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs @@ -31,13 +31,13 @@ namespace Perspex.Xaml.Base.UnitTest var fooBinding = new XamlBinding { SourcePropertyPath = "Foo", - BindingMode = BindingMode.TwoWay, + Mode = BindingMode.TwoWay, }; var barBinding = new XamlBinding { SourcePropertyPath = "Bar", - BindingMode = BindingMode.TwoWay, + Mode = BindingMode.TwoWay, }; // Bind Foo and Bar to the VM.