diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index 81a21955c4..281e9f6b61 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -205,7 +205,9 @@ internal class BindingExpression : UntypedBindingExpressionBase, IDescription, I var error = dataValidationError is not null ? new BindingError(dataValidationError, BindingErrorType.DataValidationError) : null; - ConvertAndPublishValue(value, error); + + var forceUpdate = _mode == BindingMode.OneWay; + ConvertAndPublishValue(value, error, forceUpdate); } } else if (_mode == BindingMode.OneWayToSource && nodeIndex == _nodes.Count - 2 && value is not null) @@ -402,7 +404,7 @@ internal class BindingExpression : UntypedBindingExpressionBase, IDescription, I error); } - private void ConvertAndPublishValue(object? value, BindingError? error) + private void ConvertAndPublishValue(object? value, BindingError? error, bool forceUpdate = false) { var isTargetNullValue = false; @@ -450,7 +452,7 @@ internal class BindingExpression : UntypedBindingExpressionBase, IDescription, I value = ConvertFallback(FallbackValue, nameof(FallbackValue)); // Publish the value. - PublishValue(value, error); + PublishValue(value, error, forceUpdate); } private void WriteTargetValueToSource() diff --git a/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs b/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs index e45c88c457..874c3c2e13 100644 --- a/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs +++ b/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs @@ -406,7 +406,8 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, /// /// The new value, or . /// The new binding or data validation error. - private protected void PublishValue(object? value, BindingError? error = null) + /// If true, forces the value to be published even if it hasn't changed. + private protected void PublishValue(object? value, BindingError? error = null, bool forceUpdate = false) { Debug.Assert(value is not BindingNotification); Debug.Assert(value != BindingOperations.DoNothing); @@ -424,7 +425,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, value = null; } - var hasValueChanged = value != UnchangedValue && !TypeUtilities.IdentityEquals(value, GetValue(), TargetType); + var hasValueChanged = forceUpdate || (value != UnchangedValue && !TypeUtilities.IdentityEquals(value, GetValue(), TargetType)); var hasErrorChanged = error is not null || _error is not null; if (hasValueChanged) diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Mode.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Mode.cs index cf80b86386..c933eb5316 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Mode.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Mode.cs @@ -1,3 +1,5 @@ +using System.Reflection; +using System.Runtime.InteropServices; using Avalonia.Data; using Xunit; @@ -201,4 +203,23 @@ public partial class BindingExpressionTests Assert.Equal("foo", data.StringValue); } + + [Fact] + public void OneWay_Binding_Updates_Target_When_Changes_And_Source_Raises_PropertyChanged() + { + var data = new ViewModel { StringValue = "foo" }; + var target = CreateTarget( + x => x.StringValue, + dataContext: data, + mode: BindingMode.OneWay); + + Assert.Equal("foo", target.String); + + target.SetCurrentValue(TargetClass.StringProperty, "bar"); + Assert.Equal("bar", target.String); + + data.RaisePropertyChanged(nameof(data.StringValue)); + + Assert.Equal("foo", target.String); + } }