diff --git a/src/Uno.UI.Tests/BinderTests/Given_Binder.inpc.cs b/src/Uno.UI.Tests/BinderTests/Given_Binder.inpc.cs index f68b41ae86b4..38587b321511 100644 --- a/src/Uno.UI.Tests/BinderTests/Given_Binder.inpc.cs +++ b/src/Uno.UI.Tests/BinderTests/Given_Binder.inpc.cs @@ -62,13 +62,13 @@ public void When_INPC_Update_SameReference_Converter() var master = new Binder_INPC_Base_Class(); SUT.DataContext = master; - Assert.AreEqual(2, master.ValueGetCount); + Assert.AreEqual(1, master.ValueGetCount); Assert.AreEqual(0, master.ValueSetCount); master.RaiseUpdated(); Assert.AreEqual(SUT.MyValue, master.Value); - Assert.AreEqual(4, master.ValueGetCount); + Assert.AreEqual(3, master.ValueGetCount); Assert.AreEqual(0, master.ValueSetCount); } diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_DataTemplate.cs b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_DataTemplate.cs index b7d2cb1438bc..3eb0bf7d108b 100644 --- a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_DataTemplate.cs +++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_DataTemplate.cs @@ -74,7 +74,7 @@ public void When_Updated_Property() var _MyProperty_Formatted_OneWay = SUT.FindName("_MyProperty_Formatted_OneWay") as TextBlock; Assert.AreEqual("Formatted Initial", _MyProperty_Formatted_OneWay.Text); - Assert.AreEqual(9, data.MyPropertyGetCounter); + Assert.AreEqual(7, data.MyPropertyGetCounter); data.MyProperty = "Other value"; @@ -84,7 +84,7 @@ public void When_Updated_Property() Assert.AreEqual("OTHER VALUE", _MyProperty_Function_OneWay.Text); Assert.AreEqual("Formatted Other value", _MyProperty_Formatted_OneWay.Text); - Assert.AreEqual(13, data.MyPropertyGetCounter); + Assert.AreEqual(11, data.MyPropertyGetCounter); } [TestMethod] @@ -111,7 +111,7 @@ public void When_Updated_With_Null() var _MyProperty_Formatted_OneWay = SUT.FindName("_MyProperty_Formatted_OneWay") as TextBlock; Assert.AreEqual("Formatted Initial", _MyProperty_Formatted_OneWay.Text); - Assert.AreEqual(9, data.MyPropertyGetCounter); + Assert.AreEqual(7, data.MyPropertyGetCounter); data.MyProperty = null; @@ -121,7 +121,7 @@ public void When_Updated_With_Null() Assert.IsNull(null, _MyProperty_Function_OneWay.Text); Assert.AreEqual("Formatted ", _MyProperty_Formatted_OneWay.Text); - Assert.AreEqual(13, data.MyPropertyGetCounter); + Assert.AreEqual(11, data.MyPropertyGetCounter); } [TestMethod] diff --git a/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs b/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs index 85af7cb78e74..cc1ce74d8e50 100644 --- a/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs +++ b/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs @@ -196,7 +196,7 @@ private void OnDataContextChanged() } } - private void OnPropertyChanged(object? newValue, bool shouldRaiseValueChanged) + private void OnPropertyChanged(object? previousValue, object? newValue, bool shouldRaiseValueChanged) { if (_isDataContextChanging && newValue == DependencyProperty.UnsetValue) { @@ -211,7 +211,12 @@ private void OnPropertyChanged(object? newValue, bool shouldRaiseValueChanged) Next.DataContext = newValue; } - if (shouldRaiseValueChanged) + if (shouldRaiseValueChanged + // If IgnoreINPCSameReferences is true, we will RaiseValueChanged only if previousValue != newValue + // If IgnoreINPCSameReferences is false, we are not going to compare previousValue and newValue (which is the correct behavior). + // In Uno 6, we should remove the following line. + && (!FeatureConfiguration.Binding.IgnoreINPCSameReferences || previousValue != newValue) + ) { // We should call RaiseValueChanged even if oldValue == newValue. // It's the responsibility of the user to only raise PropertyChanged event when needed. @@ -413,6 +418,13 @@ private IDisposable SubscribeToPropertyChanged() if (handlerDisposable != null) { + if (FeatureConfiguration.Binding.IgnoreINPCSameReferences) + { + // GetSourceValue calls into user code. + // Avoid this if PreviousValue isn't going to be used at all. + valueHandler.PreviousValue = GetSourceValue(); + } + // We need to keep the reference to the updatePropertyHandler // in this disposable. The reference is attached to the source's // object lifetime, to the target (bound) object. @@ -421,9 +433,11 @@ private IDisposable SubscribeToPropertyChanged() // weak with regards to the delegates that are provided. disposables.Add(() => { + object? previousValue = valueHandler.PreviousValue; + valueHandler = null; handlerDisposable.Dispose(); - OnPropertyChanged(DependencyProperty.UnsetValue, shouldRaiseValueChanged: false); + OnPropertyChanged(previousValue, DependencyProperty.UnsetValue, shouldRaiseValueChanged: false); }); } } @@ -509,6 +523,8 @@ public PropertyChangedValueHandler(BindingItem owner) _self = WeakReferencePool.RentSelfWeakReference(this); } + public object? PreviousValue { get; set; } + public ManagedWeakReference WeakReference => _self; @@ -516,7 +532,9 @@ public void NewValue() { var newValue = _owner.GetSourceValue(); - _owner.OnPropertyChanged(newValue, shouldRaiseValueChanged: true); + _owner.OnPropertyChanged(PreviousValue, newValue, shouldRaiseValueChanged: true); + + PreviousValue = newValue; } public void NewValue(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)