From 58db340d9a72454eba86828cdfef8dcadc26e72c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 24 Nov 2023 17:06:41 +0100 Subject: [PATCH] Added BindingExpressionBase.UpdateTarget. --- .../Data/BindingExpressionBase.cs | 6 +++++ .../Data/Core/BindingExpression.cs | 13 +++++++++++ .../Core/ExpressionNodes/ExpressionNode.cs | 5 ++++- .../Data/Core/BindingExpressionTests.cs | 22 +++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/BindingExpressionBase.cs b/src/Avalonia.Base/Data/BindingExpressionBase.cs index ad411130cf..24bed9ddd5 100644 --- a/src/Avalonia.Base/Data/BindingExpressionBase.cs +++ b/src/Avalonia.Base/Data/BindingExpressionBase.cs @@ -14,8 +14,14 @@ public abstract class BindingExpressionBase : IDisposable, ISetterInstance public virtual void Dispose() { + GC.SuppressFinalize(this); } + /// + /// Forces a data transfer from the binding source to the binding target. + /// + public virtual void UpdateTarget() { } + /// /// When overridden in a derived class, attaches the binding expression to a value store but /// does not start it. diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index 1203b3d0e3..f4fd076f90 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -131,6 +131,19 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri public ExpressionNode LeafNode => _nodes[_nodes.Count - 1]; public string? StringFormat => _uncommon?._stringFormat; + public override void UpdateTarget() + { + if (_nodes.Count == 0) + return; + + var source = _nodes[0].Source; + + for (var i = 0; i < _nodes.Count; ++i) + _nodes[i].SetSource(null, null); + + _nodes[0].SetSource(source, null); + } + /// /// Creates an from an expression tree. /// diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs index fc86213373..150502d67a 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs @@ -127,6 +127,9 @@ internal abstract class ExpressionNode if (source == AvaloniaProperty.UnsetValue) source = null; + if (source == oldSource) + return; + if (oldSource is not null) Unsubscribe(oldSource); @@ -139,7 +142,7 @@ internal abstract class ExpressionNode // binding change. _value = BindingExpression._nullReference; } - else if (source != oldSource) + else { try { OnSourceChanged(source, dataValidationError); } catch (Exception e) { SetError(e); } diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs index 094c3fc708..a7707c526d 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Reactive.Linq; using System.Threading.Tasks; +using Avalonia.Controls; using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.Data.Core; @@ -271,6 +272,22 @@ namespace Avalonia.Base.UnitTests.Data.Core GC.KeepAlive(data); } + [Fact] + public void Can_Use_UpdateTarget_To_Update_From_Non_INPC_Data() + { + var data = new NonInpcClass { StringValue = "foo" }; + var target = new Control { DataContext = data }; + var expression = target.Bind(Control.TagProperty, new Binding(nameof(data.StringValue))); + + Assert.Equal("foo", target.Tag); + + data.StringValue = "bar"; + Assert.Equal("foo", target.Tag); + + expression.UpdateTarget(); + Assert.Equal("bar", target.Tag); + } + private class Class1 : NotifyingBase { private string _stringValue; @@ -289,6 +306,11 @@ namespace Avalonia.Base.UnitTests.Data.Core } } + private class NonInpcClass + { + public string StringValue { get; set; } + } + private class TargetProperties : AvaloniaObject { public static readonly StyledProperty DoubleProperty =