diff --git a/src/Uno.UI.Tests/BinderTests/Given_Binder.cs b/src/Uno.UI.Tests/BinderTests/Given_Binder.cs index 8f733ccb2cbe..d5b372990b92 100644 --- a/src/Uno.UI.Tests/BinderTests/Given_Binder.cs +++ b/src/Uno.UI.Tests/BinderTests/Given_Binder.cs @@ -1047,6 +1047,55 @@ public void When_Binding_OneWay_Overwritten_By_Local_Explicit_Source() Assert.AreEqual(22, slider.Value); } + [TestMethod] + public void When_Binding_And_ClearValue() + { + var SUT = new MyControl(); + + SUT.SetBinding(MyControl.MyPropertyProperty, new Binding { Path = new PropertyPath(".") }); + + SUT.DataContext = 42; + + Assert.AreEqual(42, SUT.MyProperty); + + SUT.ClearValue(MyControl.MyPropertyProperty); + + SUT.DataContext = 43; + + Assert.AreEqual(0, SUT.MyProperty); + } + + [TestMethod] + public void When_Binding_And_ClearValue_With_Events() + { + List myPropertyHistory = new(); + var SUT = new MyControl(); + + SUT.SetBinding(MyControl.MyPropertyProperty, new Binding { Path = new PropertyPath(".") }); + + SUT.RegisterPropertyChangedCallback( + MyControl.MyPropertyProperty, + (s, e) => { + myPropertyHistory.Add(SUT.MyProperty); + }); + + SUT.DataContext = 42; + + Assert.AreEqual(42, SUT.MyProperty); + Assert.AreEqual(1, myPropertyHistory.Count); + Assert.AreEqual(42, myPropertyHistory[0]); + + SUT.ClearValue(MyControl.MyPropertyProperty); + + Assert.AreEqual(2, myPropertyHistory.Count); + Assert.AreEqual(0, myPropertyHistory[1]); + + SUT.DataContext = 43; + + Assert.AreEqual(2, myPropertyHistory.Count); + Assert.AreEqual(0, SUT.MyProperty); + } + public partial class BaseTarget : DependencyObject { private List _dataContextChangedList = new List(); diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_StaticResource_And_VisualState_Setter_ClearValue.xaml b/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_StaticResource_And_VisualState_Setter_ClearValue.xaml new file mode 100644 index 000000000000..0833b8380d06 --- /dev/null +++ b/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_StaticResource_And_VisualState_Setter_ClearValue.xaml @@ -0,0 +1,41 @@ + + + + my static resource + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_StaticResource_And_VisualState_Setter_ClearValue.xaml.cs b/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_StaticResource_And_VisualState_Setter_ClearValue.xaml.cs new file mode 100644 index 000000000000..0279481e9e57 --- /dev/null +++ b/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_StaticResource_And_VisualState_Setter_ClearValue.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace Uno.UI.Tests.Windows_UI_Xaml.Controls +{ + public sealed partial class When_StaticResource_And_VisualState_Setter_ClearValue : UserControl + { + public When_StaticResource_And_VisualState_Setter_ClearValue() + { + this.InitializeComponent(); + } + } +} diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_TemplateBinding_And_VisualState_Setter_ClearValue.xaml b/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_TemplateBinding_And_VisualState_Setter_ClearValue.xaml new file mode 100644 index 000000000000..997726102828 --- /dev/null +++ b/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_TemplateBinding_And_VisualState_Setter_ClearValue.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_TemplateBinding_And_VisualState_Setter_ClearValue.xaml.cs b/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_TemplateBinding_And_VisualState_Setter_ClearValue.xaml.cs new file mode 100644 index 000000000000..75dce24fd6e1 --- /dev/null +++ b/src/Uno.UI.Tests/Windows_UI_Xaml/Controls/When_TemplateBinding_And_VisualState_Setter_ClearValue.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace Uno.UI.Tests.Windows_UI_Xaml.Controls +{ + public sealed partial class When_TemplateBinding_And_VisualState_Setter_ClearValue : UserControl + { + public When_TemplateBinding_And_VisualState_Setter_ClearValue() + { + this.InitializeComponent(); + } + } +} diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml/Given_ThemeResource.cs b/src/Uno.UI.Tests/Windows_UI_Xaml/Given_ThemeResource.cs index c09f07691f7d..65beee772e9b 100644 --- a/src/Uno.UI.Tests/Windows_UI_Xaml/Given_ThemeResource.cs +++ b/src/Uno.UI.Tests/Windows_UI_Xaml/Given_ThemeResource.cs @@ -591,6 +591,51 @@ public void ThemeResource_When_Setter_Override_State_From_Visual_Parent() Assert.AreEqual(Colors.Orange, (tb.Foreground as SolidColorBrush)?.Color); } + [TestMethod] + public void When_TemplateBinding_And_VisualState_Setter_ClearValue() + { + var SUT = new When_TemplateBinding_And_VisualState_Setter_ClearValue(); + SUT.topLevel.Content = "test"; + SUT.topLevel.ApplyTemplate(); + + var inner = SUT.FindName("innerContent") as ContentPresenter; + Assert.IsNotNull(inner); + Assert.AreEqual("Default Value", inner.Tag); + + VisualStateManager.GoToState(SUT.topLevel, "NewState", true); + Assert.AreEqual("42", inner.Tag); + + VisualStateManager.GoToState(SUT.topLevel, "DefaultState", true); + Assert.AreEqual("Default Value", inner.Tag); + + SUT.topLevel.Tag = "Updated value"; + Assert.AreEqual("Updated value", inner.Tag); + } + + [TestMethod] + public void When_StaticResource_And_VisualState_Setter_ClearValue() + { + var SUT = new When_StaticResource_And_VisualState_Setter_ClearValue(); + SUT.topLevel.Content = "test"; + SUT.topLevel.ApplyTemplate(); + + var inner = SUT.FindName("innerContent") as ContentPresenter; + Assert.IsNotNull(inner); + Assert.AreEqual("my static resource", inner.Tag); + + VisualStateManager.GoToState(SUT.topLevel, "NewState", true); + Assert.AreEqual("42", inner.Tag); + + VisualStateManager.GoToState(SUT.topLevel, "DefaultState", true); + Assert.AreEqual("my static resource", inner.Tag); + + VisualStateManager.GoToState(SUT.topLevel, "NewState", true); + Assert.AreEqual("42", inner.Tag); + + VisualStateManager.GoToState(SUT.topLevel, "DefaultState", true); + Assert.AreEqual("my static resource", inner.Tag); + } + /// /// Use Fluent styles for the duration of the test. /// diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 6288f4c78621..5d3a86a1c21f 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -16,6 +16,7 @@ using Uno.UI; using System.Collections; using System.Globalization; +using Windows.ApplicationModel.Calls; #if XAMARIN_ANDROID using View = Android.Views.View; @@ -464,6 +465,8 @@ private void InnerSetValue(DependencyProperty property, object? value, Dependenc // Resolve the stack once for the instance, for performance. propertyDetails ??= _properties.GetPropertyDetails(property); + TryClearBinding(value, propertyDetails); + var previousValue = GetValue(propertyDetails); var previousPrecedence = GetCurrentHighestValuePrecedence(propertyDetails); @@ -515,6 +518,21 @@ private void InnerSetValue(DependencyProperty property, object? value, Dependenc } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void TryClearBinding(object? value, DependencyPropertyDetails propertyDetails) + { + if (object.ReferenceEquals(value, DependencyProperty.UnsetValue)) + { + var hasTemplatedParentBinding = + propertyDetails.GetBinding()?.ParentBinding.RelativeSource?.Mode == RelativeSourceMode.TemplatedParent; + + if (!hasTemplatedParentBinding) + { + propertyDetails.ClearBinding(); + } + } + } + private void TryUpdateInheritedAttachedProperty(DependencyProperty property, DependencyPropertyDetails propertyDetails) { if (