diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index 9ee4787e47..f2f3ed9bfc 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -88,18 +88,21 @@ namespace Avalonia.Data.Core _subscriber(value); } - protected void ValueChanged(object value) + protected void ValueChanged(object value) => ValueChanged(value, true); + + private void ValueChanged(object value, bool notify) { var notification = value as BindingNotification; if (notification == null) { LastValue = new WeakReference(value); + if (Next != null) { - Next.Target = new WeakReference(value); + Next.Target = LastValue; } - else + else if (notify) { _subscriber(value); } @@ -110,7 +113,7 @@ namespace Avalonia.Data.Core if (Next != null) { - Next.Target = new WeakReference(notification.Value); + Next.Target = LastValue; } if (Next == null || notification.Error != null) @@ -136,6 +139,7 @@ namespace Avalonia.Data.Core } else { + ValueChanged(AvaloniaProperty.UnsetValue, notify:false); _listening = false; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs index ab82b7a28b..2f7256fa22 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs @@ -1,13 +1,12 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using Avalonia.Data; +using Portable.Xaml.Markup; namespace Avalonia.Markup.Xaml.MarkupExtensions { - using Portable.Xaml.Markup; - using System; - public class RelativeSourceExtension : MarkupExtension { public RelativeSourceExtension() @@ -24,10 +23,19 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions return new RelativeSource { Mode = Mode, + AncestorType = AncestorType, + AncestorLevel = AncestorLevel, + Tree = Tree, }; } [ConstructorArgument("mode")] - public RelativeSourceMode Mode { get; set; } + public RelativeSourceMode Mode { get; set; } = RelativeSourceMode.FindAncestor; + + public Type AncestorType { get; set; } + + public TreeType Tree { get; set; } + + public int AncestorLevel { get; set; } = 1; } } \ No newline at end of file diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs index f2ddec6f3c..4c572acab1 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs @@ -1,9 +1,13 @@ // Copyright (c) The Avalonia 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.Subjects; using Avalonia.Controls; using Avalonia.Data; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; +using Avalonia.Markup.Parsers; using Avalonia.UnitTests; using Xunit; @@ -162,5 +166,99 @@ namespace Avalonia.Markup.UnitTests.Data decorator2.Child = target; Assert.Equal("decorator2", target.Text); } + + [Fact] + public void Should_Update_When_Detached_And_Attached_To_Visual_Tree_With_BindingPath() + { + TextBlock target; + Decorator decorator1; + Decorator decorator2; + + var viewModel = new { Value = "Foo" }; + + var root1 = new TestRoot + { + Child = decorator1 = new Decorator + { + Name = "decorator1", + Child = target = new TextBlock(), + }, + DataContext = viewModel + }; + + var root2 = new TestRoot + { + Child = decorator2 = new Decorator + { + Name = "decorator2", + }, + DataContext = viewModel + }; + + var binding = new Binding + { + Path = "DataContext.Value", + RelativeSource = new RelativeSource + { + AncestorType = typeof(Decorator), + } + }; + + target.Bind(TextBox.TextProperty, binding); + Assert.Equal("Foo", target.Text); + + decorator1.Child = null; + Assert.Null(target.Text); + + decorator2.Child = target; + Assert.Equal("Foo", target.Text); + } + + [Fact] + public void Should_Update_When_Detached_And_Attached_To_Visual_Tree_With_ComplexBindingPath() + { + TextBlock target; + Decorator decorator1; + Decorator decorator2; + + var vm = new { Foo = new { Value = "Foo" } }; + + var root1 = new TestRoot + { + Child = decorator1 = new Decorator + { + Name = "decorator1", + Child = target = new TextBlock(), + }, + DataContext = vm + }; + + var root2 = new TestRoot + { + Child = decorator2 = new Decorator + { + Name = "decorator2", + }, + DataContext = vm + }; + + var binding = new Binding + { + Path = "DataContext.Foo.Value", + RelativeSource = new RelativeSource + { + AncestorType = typeof(Decorator), + } + }; + + target.Bind(TextBox.TextProperty, binding); + Assert.Equal("Foo", target.Text); + + decorator1.Child = null; + Assert.Null(target.Text); + + decorator2.Child = target; + Assert.Equal("Foo", target.Text); + } } } diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs index a97c998264..4f7264f2f2 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs @@ -1,10 +1,10 @@ -using Avalonia.Data; -using Avalonia.Markup.Parsers; -using System; +using System; using System.Collections.Generic; using System.Reactive.Linq; -using System.Text; +using System.Reactive.Subjects; using System.Threading.Tasks; +using Avalonia.Data; +using Avalonia.Markup.Parsers; using Xunit; namespace Avalonia.Markup.UnitTests.Parsers @@ -38,5 +38,55 @@ namespace Avalonia.Markup.UnitTests.Parsers GC.KeepAlive(data); } + + [Fact] + public void Should_Update_Value_After_Root_Changes() + { + var root = new { DataContext = new { Value = "Foo" } }; + var subject = new Subject(); + var obs = ExpressionObserverBuilder.Build(subject, "DataContext.Value"); + + var values = new List(); + obs.Subscribe(v => values.Add(v)); + + subject.OnNext(root); + subject.OnNext(null); + subject.OnNext(root); + + Assert.Equal("Foo", values[0]); + + Assert.IsType(values[1]); + var bn = values[1] as BindingNotification; + Assert.Equal(AvaloniaProperty.UnsetValue, bn.Value); + Assert.Equal(BindingErrorType.Error, bn.ErrorType); + + Assert.Equal(3, values.Count); + Assert.Equal("Foo", values[2]); + } + + [Fact] + public void Should_Update_Value_After_Root_Changes_With_ComplexPath() + { + var root = new { DataContext = new { Foo = new { Value = "Foo" } } }; + var subject = new Subject(); + var obs = ExpressionObserverBuilder.Build(subject, "DataContext.Foo.Value"); + + var values = new List(); + obs.Subscribe(v => values.Add(v)); + + subject.OnNext(root); + subject.OnNext(null); + subject.OnNext(root); + + Assert.Equal("Foo", values[0]); + + Assert.IsType(values[1]); + var bn = values[1] as BindingNotification; + Assert.Equal(AvaloniaProperty.UnsetValue, bn.Value); + Assert.Equal(BindingErrorType.Error, bn.ErrorType); + + Assert.Equal(3, values.Count); + Assert.Equal("Foo", values[2]); + } } }