diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation.xaml b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation.xaml new file mode 100644 index 000000000000..15db67c9baf0 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation.xaml.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation.xaml.cs new file mode 100644 index 000000000000..37abf81b6df7 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation.xaml.cs @@ -0,0 +1,47 @@ +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.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace Uno.UI.RuntimeTests +{ + public sealed partial class When_Refresh_Setter_BindingOnInvocation : UserControl + { + public When_Refresh_Setter_BindingOnInvocation() + { + InitializeComponent(); + } + } + + public class When_Refresh_Setter_BindingOnInvocation_Converter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + var ctrl = value as ContentControl; + + if (ctrl is not null) + { + return ctrl.Tag ?? -10; + } + + return -10; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation_ElementName.xaml b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation_ElementName.xaml new file mode 100644 index 000000000000..7116652ece8c --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation_ElementName.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation_ElementName.xaml.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation_ElementName.xaml.cs new file mode 100644 index 000000000000..090d2c0a9930 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/When_Refresh_Setter_BindingOnInvocation_ElementName.xaml.cs @@ -0,0 +1,47 @@ +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.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace Uno.UI.RuntimeTests +{ + public sealed partial class When_Refresh_Setter_BindingOnInvocation_ElementName : UserControl + { + public When_Refresh_Setter_BindingOnInvocation_ElementName() + { + InitializeComponent(); + } + } + + public class When_Refresh_Setter_BindingOnInvocation_ElementName_Converter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + var ctrl = value as FrameworkElement; + + if (ctrl is not null) + { + return ctrl.Tag ?? -10; + } + + return -10; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_Control.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_Control.cs index 324d9b1b7935..a9f81af50aa2 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_Control.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_Control.cs @@ -7,6 +7,8 @@ using Windows.UI.Xaml.Markup; using Microsoft.VisualStudio.TestTools.UnitTesting; using Private.Infrastructure; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml; namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml { @@ -27,32 +29,95 @@ public async Task When_SetChildTemplateUsingVisualState() Assert.AreEqual("Template loaded!", tb.Text); } - private const string _when_SetChildTemplateUsingVisualState = @" - "; + private const string _when_SetChildTemplateUsingVisualState = + """ + + """; + + [TestMethod] + [RunsOnUIThread] + public async Task When_Refresh_Setter_BindingOnInvocation() + { + var SUT = new When_Refresh_Setter_BindingOnInvocation(); + TestServices.WindowHelper.WindowContent = SUT; + await TestServices.WindowHelper.WaitForIdle(); + + SUT.root.Content = 42; + + var testTransform = (SUT.root.FindName("ContentElement") as FrameworkElement).RenderTransform as CompositeTransform; + + Assert.IsNotNull(testTransform); + + Assert.AreEqual(0, testTransform.TranslateX); + + VisualStateManager.GoToState(SUT.root, "Normal", false); + await TestServices.WindowHelper.WaitForIdle(); + Assert.AreEqual(-10, testTransform.TranslateY); + + SUT.root.Tag = 42; + VisualStateManager.GoToState(SUT.root, "Focused", false); + await TestServices.WindowHelper.WaitForIdle(); + Assert.AreEqual(42, testTransform.TranslateY); + + SUT.root.Tag = 43; + VisualStateManager.GoToState(SUT.root, "Normal", false); + await TestServices.WindowHelper.WaitForIdle(); + Assert.AreEqual(43, testTransform.TranslateY); + } + + [TestMethod] + [RunsOnUIThread] + public async Task When_Refresh_Setter_BindingOnInvocation_ElementName() + { + var SUT = new When_Refresh_Setter_BindingOnInvocation_ElementName(); + TestServices.WindowHelper.WindowContent = SUT; + await TestServices.WindowHelper.WaitForIdle(); + + SUT.root.Content = 42; + + var testTransform = (SUT.root.FindName("ContentElement") as FrameworkElement).RenderTransform as CompositeTransform; + + Assert.IsNotNull(testTransform); + + Assert.AreEqual(0, testTransform.TranslateX); + + VisualStateManager.GoToState(SUT.root, "Normal", false); + await TestServices.WindowHelper.WaitForIdle(); + Assert.AreEqual(-10, testTransform.TranslateY); + + SUT.sp01.Tag = 42; + VisualStateManager.GoToState(SUT.root, "Focused", false); + await TestServices.WindowHelper.WaitForIdle(); + Assert.AreEqual(42, testTransform.TranslateY); + + SUT.sp01.Tag = 43; + VisualStateManager.GoToState(SUT.root, "Normal", false); + await TestServices.WindowHelper.WaitForIdle(); + Assert.AreEqual(43, testTransform.TranslateY); + } } } diff --git a/src/Uno.UI/DataBinding/BindingExpression.cs b/src/Uno.UI/DataBinding/BindingExpression.cs index 26d0ed9e6b3c..62a57d72d951 100644 --- a/src/Uno.UI/DataBinding/BindingExpression.cs +++ b/src/Uno.UI/DataBinding/BindingExpression.cs @@ -280,6 +280,28 @@ internal void ResumeBinding() } } + /// + /// Refreshes the value to the target, as the bound source may not be observable + /// + internal void RefreshTarget() + { + ApplyElementName(); + + if ( + // If a listener is set, ApplyBindings has been invoked + _bindingPath.ValueChangedListener is not null + + // If this is not an x:Bind + && _updateSources is null + + // If there's a valid DataContext + && GetWeakDataContext() is { IsAlive: true } weakDataContext) + { + // Apply the source on the target again (e.g. to reevaluate converters) + _bindingPath.SetWeakDataContext(weakDataContext); + } + } + /// /// Turns UpdateSourceTrigger.Default to DependencyProperty's FrameworkPropertyMetadata.DefaultUpdateSourceTrigger /// diff --git a/src/Uno.UI/UI/Xaml/Setter.cs b/src/Uno.UI/UI/Xaml/Setter.cs index 39c75c02dffc..1da264fec7d5 100644 --- a/src/Uno.UI/UI/Xaml/Setter.cs +++ b/src/Uno.UI/UI/Xaml/Setter.cs @@ -34,6 +34,15 @@ public Setter() private DependencyProperty? _property; private TargetPropertyPath? _target; + // This property is not part of the WinUI API, but is + // required to determine if the value has a binding set + private static DependencyProperty InternalValueProperty { get; } + = DependencyProperty.Register( + nameof(Value), + typeof(object), + typeof(Setter), + new FrameworkPropertyMetadata(default(object))); + public object? Value { get @@ -145,6 +154,8 @@ internal void ApplyValue(DependencyPropertyValuePrecedences precedence, IFramewo if (path != null) { + RefreshBindingPath(); + if (ThemeResourceKey.HasValue && ResourceResolver.ApplyVisualStateSetter(ThemeResourceKey.Value, ThemeResourceContext, path, precedence, ResourceBindingUpdateReason)) { // Applied as theme binding, no need to do more @@ -157,6 +168,10 @@ internal void ApplyValue(DependencyPropertyValuePrecedences precedence, IFramewo } } + private void RefreshBindingPath() + // force binding value to re-evaluate the source and use converters + => GetBindingExpression(InternalValueProperty)?.RefreshTarget(); + private BindingPath? TryGetOrCreateBindingPath(DependencyPropertyValuePrecedences precedence, IFrameworkElement owner) { if (_bindingPath != null) diff --git a/src/Uno.UI/UI/Xaml/Style/mergedstyles.xaml b/src/Uno.UI/UI/Xaml/Style/mergedstyles.xaml index 95e9a4487a70..893ae9268d49 100644 --- a/src/Uno.UI/UI/Xaml/Style/mergedstyles.xaml +++ b/src/Uno.UI/UI/Xaml/Style/mergedstyles.xaml @@ -1,4 +1,4 @@ - + diff --git a/src/Uno.UI/UI/Xaml/VisualStateGroup.cs b/src/Uno.UI/UI/Xaml/VisualStateGroup.cs index af5cdec199c9..5f8887ffe6af 100644 --- a/src/Uno.UI/UI/Xaml/VisualStateGroup.cs +++ b/src/Uno.UI/UI/Xaml/VisualStateGroup.cs @@ -336,6 +336,8 @@ void ApplyTargetStateSetters() while (settersEnumerator.MoveNext()) { settersEnumerator.Current.ApplyValue(DependencyPropertyValuePrecedences.Animations, element); + + } } #if !HAS_EXPENSIVE_TRYFINALLY