Browse Source

Fix #17674: OneWay binding fails to update target after local change (#20964)

* Fix #20816: OneWay binding fails to update target after local change

When a OneWay binding target is modified locally (e.g., via user
interaction on a ToggleButton), subsequent PropertyChanged events from
the source were being ignored. This occurred because the binding
expression incorrectly suppressed the update.

This patch modifies UntypedBindingExpressionBase to ensure that OneWay
bindings always propagate changes from the source to the target,
overriding any previous local changes. Unit tests were also added to
prevent future regressions on this behavior.

Signed-off-by: Martim Claudino <martimffclaudino@tecnico.ulisboa.pt>

* fix: scope forceUpdate to OneWay mode, use SetCurrentValue in tests and clean usings

Signed-off-by: Martim Claudino <martimffclaudino@tecnico.ulisboa.pt>

---------

Signed-off-by: Martim Claudino <martimffclaudino@tecnico.ulisboa.pt>
release/latest
Martim Claudino 6 months ago
committed by Julien Lebosquain
parent
commit
e9fa5c4885
  1. 8
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  2. 5
      src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs
  3. 21
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Mode.cs

8
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()

5
src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs

@ -406,7 +406,8 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
/// </summary>
/// <param name="value">The new value, or <see cref="UnchangedValue"/>.</param>
/// <param name="error">The new binding or data validation error.</param>
private protected void PublishValue(object? value, BindingError? error = null)
/// <param name="forceUpdate">If true, forces the value to be published even if it hasn't changed.</param>
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)

21
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<ViewModel, string?>(
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);
}
}

Loading…
Cancel
Save