From be457256aae7a90fa8673794ce8b43cdf7edcc90 Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Mon, 4 Dec 2023 14:35:22 -0500 Subject: [PATCH 1/8] fix(sampleapp): debug POI resolution --- .../Controls/SamplePageLayout.cs | 5 +- .../Extensions/EnumerableExtensions.cs | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Controls/SamplePageLayout.cs b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Controls/SamplePageLayout.cs index b1004ed61..bdc92bc2a 100644 --- a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Controls/SamplePageLayout.cs +++ b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Controls/SamplePageLayout.cs @@ -247,13 +247,14 @@ x.Name is "M2MaterialContentPresenter" or "M3MaterialContentPresenter" && /// public ContentPresenter GetActivePresenter() { - return _design switch + var mode = (IsDesignAgnostic ? Design.Agnostic : _design); + return mode switch { Design.Material => this .GetFirstDescendant(x => x.Name is "M2MaterialContentPresenter" or "M3MaterialContentPresenter" && x.Visibility == Visibility.Visible), - _ => (ContentPresenter)GetTemplateChild($"{_design}ContentPresenter"), + _ => (ContentPresenter)GetTemplateChild($"{mode}ContentPresenter"), }; } diff --git a/src/Uno.Toolkit.UI/Extensions/EnumerableExtensions.cs b/src/Uno.Toolkit.UI/Extensions/EnumerableExtensions.cs index 82b84bbbc..b81dd662f 100644 --- a/src/Uno.Toolkit.UI/Extensions/EnumerableExtensions.cs +++ b/src/Uno.Toolkit.UI/Extensions/EnumerableExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace Uno.Toolkit.UI; @@ -21,4 +22,53 @@ internal static class EnumerableExtensions previous = etor.Current; } } + + /// + /// Functionally the same as , + /// but allow no match result to be null instead of default(struct). + /// + public static T? FirstOrNull(this IEnumerable source) where T : struct + { + foreach (var element in source) + { + return element; + } + + return null; + } + public static T? FirstOrNull(this IEnumerable source, Func predicate) where T : struct + { + foreach (var element in source) + { + if (predicate(element)) return element; + } + + return null; + } + + /// + /// Functionally the same as , + /// but allow no match result to be null instead of default(struct). + /// + public static T? LastOrNull(this IEnumerable source) where T : struct + { + var result = default(T?); + foreach (var element in source) + { + result = element; + } + + return result; + } + public static T? LastOrNull(this IEnumerable source, Func predicate) where T : struct + { + var result = default(T?); + foreach (var element in source) + { + if (predicate(element)) result = element; + } + + return result; + } + } From 7baa49b3d2af6c58a0e8fe130051b31224fb137b Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Mon, 4 Dec 2023 14:37:12 -0500 Subject: [PATCH 2/8] feat(responsive): layout override --- .../Controls/ResponsiveViewSamplePage.xaml | 138 ++++++++++++---- .../ResponsiveExtensionsSamplePage.xaml | 68 ++++---- .../ResponsiveView.Properties.cs | 110 +++++++++++++ .../Controls/ResponsiveView/ResponsiveView.cs | 151 ++++-------------- src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs | 25 +++ .../Helpers/ResponsiveHelper.cs | 10 +- .../Markup/ResponsiveExtension.cs | 101 ++++++++---- 7 files changed, 388 insertions(+), 215 deletions(-) create mode 100644 src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.Properties.cs create mode 100644 src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs diff --git a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Controls/ResponsiveViewSamplePage.xaml b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Controls/ResponsiveViewSamplePage.xaml index 653022291..095576feb 100644 --- a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Controls/ResponsiveViewSamplePage.xaml +++ b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Controls/ResponsiveViewSamplePage.xaml @@ -9,27 +9,19 @@ mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> + + #67E5AD + #7A67F8 + #F85977 + #159BFF + + - - #67E5AD - #7A67F8 - #F85977 - #159BFF - - - - 200 - 350 - 800 - 1200 - 1500 - - @@ -43,14 +35,14 @@ Width="100" Fill="{StaticResource UnoGreen}" /> + Width="100" + Fill="{StaticResource UnoPurple}" /> + Width="100" + Fill="{StaticResource UnoBlue}" /> @@ -69,14 +61,14 @@ Width="150" Fill="{StaticResource UnoGreen}" /> + Width="150" + Fill="{StaticResource UnoPurple}" /> + Width="150" + Fill="{StaticResource UnoBlue}" /> @@ -94,14 +86,14 @@ Width="200" Fill="{StaticResource UnoGreen}" /> + Width="200" + Fill="{StaticResource UnoPurple}" /> + Width="200" + Fill="{StaticResource UnoBlue}" /> @@ -119,14 +111,14 @@ Width="250" Fill="{StaticResource UnoGreen}" /> + Width="250" + Fill="{StaticResource UnoPurple}" /> + Width="250" + Fill="{StaticResource UnoBlue}" /> @@ -144,19 +136,95 @@ Width="300" Fill="{StaticResource UnoGreen}" /> + Width="300" + Fill="{StaticResource UnoPurple}" /> + Width="300" + Fill="{StaticResource UnoBlue}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/ResponsiveExtensionsSamplePage.xaml b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/ResponsiveExtensionsSamplePage.xaml index 3bb63f677..e838b33c9 100644 --- a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/ResponsiveExtensionsSamplePage.xaml +++ b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/ResponsiveExtensionsSamplePage.xaml @@ -5,8 +5,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sample="using:Uno.Toolkit.Samples" xmlns:utu="using:Uno.Toolkit.UI" - mc:Ignorable="d" + xmlns:void="used for commenting out node or attribute" + mc:Ignorable="d void" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> + Vertical Horizontal @@ -14,12 +16,6 @@ 25 Crimson Blue - @@ -27,44 +23,56 @@ - + + - + - - + + - + - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.Properties.cs b/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.Properties.cs new file mode 100644 index 000000000..1f08fb3a3 --- /dev/null +++ b/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.Properties.cs @@ -0,0 +1,110 @@ +#if IS_WINUI +using Microsoft.UI.Xaml; +#else +using Windows.UI.Xaml; +#endif + +namespace Uno.Toolkit.UI; + +public partial class ResponsiveView +{ + #region DependencyProperty: NarrowestTemplate + + public static DependencyProperty NarrowestTemplateProperty { get; } = DependencyProperty.Register( + nameof(NarrowestTemplate), + typeof(DataTemplate), + typeof(ResponsiveView), + new PropertyMetadata(default(DataTemplate?), OnNarrowestTemplateChanged)); + + public DataTemplate? NarrowestTemplate + { + get => (DataTemplate?)GetValue(NarrowestTemplateProperty); + set => SetValue(NarrowestTemplateProperty, value); + } + + #endregion + #region DependencyProperty: NarrowTemplate + + public static DependencyProperty NarrowTemplateProperty { get; } = DependencyProperty.Register( + nameof(NarrowTemplate), + typeof(DataTemplate), + typeof(ResponsiveView), + new PropertyMetadata(default(DataTemplate?), OnNarrowTemplateChanged)); + + public DataTemplate? NarrowTemplate + { + get => (DataTemplate?)GetValue(NarrowTemplateProperty); + set => SetValue(NarrowTemplateProperty, value); + } + + #endregion + #region DependencyProperty: NormalTemplate + + public static DependencyProperty NormalTemplateProperty { get; } = DependencyProperty.Register( + nameof(NormalTemplate), + typeof(DataTemplate), + typeof(ResponsiveView), + new PropertyMetadata(default(DataTemplate?), OnNormalTemplateChanged)); + + public DataTemplate? NormalTemplate + { + get => (DataTemplate?)GetValue(NormalTemplateProperty); + set => SetValue(NormalTemplateProperty, value); + } + + #endregion + #region DependencyProperty: WideTemplate + + public static DependencyProperty WideTemplateProperty { get; } = DependencyProperty.Register( + nameof(WideTemplate), + typeof(DataTemplate), + typeof(ResponsiveView), + new PropertyMetadata(default(DataTemplate?), OnWideTemplateChanged)); + + public DataTemplate? WideTemplate + { + get => (DataTemplate?)GetValue(WideTemplateProperty); + set => SetValue(WideTemplateProperty, value); + } + + #endregion + #region DependencyProperty: WidestTemplate + + public static DependencyProperty WidestTemplateProperty { get; } = DependencyProperty.Register( + nameof(WidestTemplate), + typeof(DataTemplate), + typeof(ResponsiveView), + new PropertyMetadata(default(DataTemplate?), OnWidestTemplateChanged)); + + public DataTemplate? WidestTemplate + { + get => (DataTemplate?)GetValue(WidestTemplateProperty); + set => SetValue(WidestTemplateProperty, value); + } + + #endregion + + #region DependencyProperty: ResponsiveLayout + + public static DependencyProperty ResponsiveLayoutProperty { get; } = DependencyProperty.Register( + nameof(ResponsiveLayout), + typeof(ResponsiveLayout), + typeof(ResponsiveView), + new PropertyMetadata(default(ResponsiveLayout), OnResponsiveLayoutChanged)); + + public ResponsiveLayout ResponsiveLayout + { + get => (ResponsiveLayout)GetValue(ResponsiveLayoutProperty); + set => SetValue(ResponsiveLayoutProperty, value); + } + + #endregion + + private static void OnNarrowestTemplateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) => (sender as ResponsiveView)?.ResolveTemplate(); + private static void OnNarrowTemplateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) => (sender as ResponsiveView)?.ResolveTemplate(); + private static void OnNormalTemplateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) => (sender as ResponsiveView)?.ResolveTemplate(); + private static void OnWideTemplateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) => (sender as ResponsiveView)?.ResolveTemplate(); + private static void OnWidestTemplateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) => (sender as ResponsiveView)?.ResolveTemplate(); + + private static void OnResponsiveLayoutChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) => (sender as ResponsiveView)?.ResolveTemplate(); +} diff --git a/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.cs b/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.cs index b4fd23fa5..3a0c595f5 100644 --- a/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.cs +++ b/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.cs @@ -1,5 +1,7 @@ using System.Linq; using Windows.Foundation; +using System; + #if IS_WINUI using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -12,104 +14,7 @@ namespace Uno.Toolkit.UI; public partial class ResponsiveView : ContentControl, IResponsiveCallback { - #region DependencyProperties - - #region Narrowest DP - public DataTemplate NarrowestTemplate - { - get { return (DataTemplate)GetValue(NarrowestTemplateProperty); } - set { SetValue(NarrowestTemplateProperty, value); } - } - - public static readonly DependencyProperty NarrowestTemplateProperty = - DependencyProperty.Register("NarrowestTemplate", typeof(DataTemplate), typeof(ResponsiveView), new PropertyMetadata(null, OnNarrowestTemplateChanged)); - - private static void OnNarrowestTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - => OnResponsiveTemplateChanged(d, e); - - #endregion - - #region Narrow DP - public DataTemplate NarrowTemplate - { - get { return (DataTemplate)GetValue(NarrowTemplateProperty); } - set { SetValue(NarrowTemplateProperty, value); } - } - - public static readonly DependencyProperty NarrowTemplateProperty = - DependencyProperty.Register("NarrowTemplate", typeof(DataTemplate), typeof(ResponsiveView), new PropertyMetadata(null, OnNarrowTemplateChanged)); - - private static void OnNarrowTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - => OnResponsiveTemplateChanged(d, e); - #endregion - - #region Normal DP - public DataTemplate NormalTemplate - { - get { return (DataTemplate)GetValue(NormalTemplateProperty); } - set { SetValue(NormalTemplateProperty, value); } - } - - public static readonly DependencyProperty NormalTemplateProperty = - DependencyProperty.Register("NormalTemplate", typeof(DataTemplate), typeof(ResponsiveView), new PropertyMetadata(null, OnNormalTemplateChanged)); - - private static void OnNormalTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - => OnResponsiveTemplateChanged(d, e); - #endregion - - #region Wide DP - public DataTemplate WideTemplate - { - get { return (DataTemplate)GetValue(WideTemplateProperty); } - set { SetValue(WideTemplateProperty, value); } - } - - public static readonly DependencyProperty WideTemplateProperty = - DependencyProperty.Register("WideTemplate", typeof(DataTemplate), typeof(ResponsiveView), new PropertyMetadata(null, OnWideTemplateChanged)); - - private static void OnWideTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - => OnResponsiveTemplateChanged(d, e); - #endregion - - #region Widest DP - public DataTemplate WidestTemplate - { - get { return (DataTemplate)GetValue(WidestTemplateProperty); } - set { SetValue(WidestTemplateProperty, value); } - } - - public static readonly DependencyProperty WidestTemplateProperty = - DependencyProperty.Register("WidestTemplate", typeof(DataTemplate), typeof(ResponsiveView), new PropertyMetadata(null, OnWidestTemplateChanged)); - - private static void OnWidestTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - => OnResponsiveTemplateChanged(d, e); - #endregion - - #region ResponsiveLayout DP - public static DependencyProperty ResponsiveLayoutProperty { get; } = DependencyProperty.Register( - nameof(ResponsiveLayout), - typeof(ResponsiveLayout), - typeof(ResponsiveView), - new PropertyMetadata(default)); - - public ResponsiveLayout ResponsiveLayout - { - get => (ResponsiveLayout)GetValue(ResponsiveLayoutProperty); - set => SetValue(ResponsiveLayoutProperty, value); - } - #endregion - - private static void OnResponsiveTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is ResponsiveView { IsLoaded: true } view) - { - var dataTemplate = view.GetInitialValue(); - view.Content = dataTemplate?.LoadContent() as UIElement; - } - } - #endregion - - private DataTemplate? _currentContent; + internal ResolvedLayout? ResolvedLayout { get; private set; } public ResponsiveView() { @@ -122,42 +27,46 @@ public ResponsiveView() private void ResponsiveView_Loaded(object sender, RoutedEventArgs e) { - _currentContent = GetInitialValue(); - - Content = _currentContent?.LoadContent() as UIElement; + ResolveTemplate(); } - private DataTemplate? GetInitialValue() + public void OnSizeChanged(Size size, ResponsiveLayout layout) { - var helper = ResponsiveHelper.GetForCurrentView(); - - return GetValueForSize(helper.WindowSize, ResponsiveLayout ?? helper.Layout); + ResolveTemplate(size, GetAppliedLayout() ?? layout); } - private DataTemplate? GetValueForSize(Size size, ResponsiveLayout layout) + private void ResolveTemplate() { - var defs = new (double MinWidth, DataTemplate? Value)?[] - { - (layout.Narrowest, NarrowestTemplate), - (layout.Narrow, NarrowTemplate), - (layout.Normal, NormalTemplate), - (layout.Wide, WideTemplate), - (layout.Widest, WidestTemplate), - }.Where(x => x?.Value != null).ToArray(); + if (!IsLoaded) return; - var match = defs.FirstOrDefault(y => y?.MinWidth >= size.Width) ?? defs.LastOrDefault(); + var helper = ResponsiveHelper.GetForCurrentView(); - return match?.Value; + ResolveTemplate(helper.WindowSize, GetAppliedLayout() ?? helper.Layout); } - public void OnSizeChanged(Size size, ResponsiveLayout layout) + private void ResolveTemplate(Size size, ResponsiveLayout layout) { - var dataTemplate = GetValueForSize(size, ResponsiveLayout ?? layout); + if (!IsLoaded) return; - if (dataTemplate != _currentContent) + var defs = new (double MinWidth, ResolvedLayout Value)[] { - _currentContent = dataTemplate; - Content = dataTemplate?.LoadContent() as UIElement; + (layout.Narrowest, new(nameof(layout.Narrowest), NarrowestTemplate)), + (layout.Narrow, new(nameof(layout.Narrow), NarrowTemplate)), + (layout.Normal, new(nameof(layout.Normal), NormalTemplate)), + (layout.Wide, new(nameof(layout.Wide), WideTemplate)), + (layout.Widest, new(nameof(layout.Widest), WidestTemplate)), + }.Where(x => x.Value.Value != null).ToArray(); + var match = defs.FirstOrNull(y => y.MinWidth >= size.Width) ?? defs.LastOrNull(); + var resolved = match?.Value; + + if (resolved != ResolvedLayout) + { + Content = resolved?.Value?.LoadContent() as UIElement; + ResolvedLayout = resolved; } } + + internal ResponsiveLayout? GetAppliedLayout() => + ResponsiveLayout ?? + this.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey); } diff --git a/src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs b/src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs new file mode 100644 index 000000000..dc55b33aa --- /dev/null +++ b/src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs @@ -0,0 +1,25 @@ +#if IS_WINUI +using Microsoft.UI.Xaml; +#else +using Windows.UI.Xaml; +#endif + +namespace Uno.Toolkit.UI; + +internal static class ResourceHelper +{ + public static T? ResolveLocalResource(this FrameworkElement? resourceProvider, object key) where T : class + => resourceProvider.ResolveLocalResource(key) as T; + + public static object? ResolveLocalResource(this FrameworkElement? resourceProvider, object key) + { + while (resourceProvider != null) + { + if (resourceProvider.Resources.TryGetValue(key, out var resource)) return resource; + + resourceProvider = resourceProvider.Parent as FrameworkElement; + } + + return null; + } +} diff --git a/src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs b/src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs index 3784e2cdf..7ef47bb0e 100644 --- a/src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs +++ b/src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs @@ -22,6 +22,8 @@ internal interface IResponsiveCallback public partial class ResponsiveLayout : DependencyObject { + internal const string DefaultResourceKey = "DefaultResponsiveLayout"; + #region DependencyProperty: Narrowest public static DependencyProperty NarrowestProperty { get; } = DependencyProperty.Register( @@ -106,8 +108,12 @@ public double Widest Wide = wide, Widest = widest, }; + + public override string ToString() => "[" + string.Join(",", Narrowest, Narrow, Normal, Wide, Widest) + "]"; } +internal record ResolvedLayout(string Layout, T Value); + public class ResponsiveHelper { private static readonly Lazy _instance = new Lazy(() => new ResponsiveHelper()); @@ -144,7 +150,7 @@ private void OnWindowSizeChanged(Size size) foreach (var reference in _callbacks.ToArray()) { - if (reference.IsAlive && reference.Target is IResponsiveCallback callback) + if (reference.Target is IResponsiveCallback callback) { #if UNO14502_WORKAROUND // Note: In ResponsiveExtensionsSamplePage, if we are using SamplePageLayout with the template, @@ -154,7 +160,7 @@ private void OnWindowSizeChanged(Size size) // We are using a hard reference to keep the markup extension alive. // We need to check if its reference target is still alive. If it is not, then it should be removed. - if (callback is ResponsiveExtension { _weakTarget: { IsAlive: false } }) + if (callback is ResponsiveExtension { TargetWeakRef: { IsAlive: false } }) { _hardCallbackReferences.Remove(callback); _callbacks.Remove(reference); diff --git a/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs b/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs index ad62fb97a..6fc7f0079 100644 --- a/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs +++ b/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs @@ -1,4 +1,9 @@ +#if !WINDOWS_UWP +#define SUPPORTS_XAML_SERVICE_PROVIDER +#endif + using System; +using System.Collections.Generic; using System.Linq; using Uno.Extensions; using Uno.Logging; @@ -14,15 +19,19 @@ namespace Uno.Toolkit.UI; +#if DEBUG +public partial class ResponsiveExtension // for debugging +{ + // Used by TreeGraph to obtain the ResponsiveExtension(s) declared on the Owner. + internal static List<(WeakReference Owner, string Property, WeakReference Extension)> TrackedInstances { get; } = new(); +} +#endif + /// /// A markup extension that updates a property based on the current window width. /// public partial class ResponsiveExtension : MarkupExtension, IResponsiveCallback { - internal WeakReference? _weakTarget; -#if !WINDOWS_UWP - private DependencyProperty? _targetProperty; -#endif public object? Narrowest { get; set; } public object? Narrow { get; set; } public object? Normal { get; set; } @@ -31,79 +40,117 @@ public partial class ResponsiveExtension : MarkupExtension, IResponsiveCallback public ResponsiveLayout? Layout { get; set; } +#if SUPPORTS_XAML_SERVICE_PROVIDER + internal WeakReference? TargetWeakRef { get; private set; } + private DependencyProperty? _targetProperty; +#endif + internal ResolvedLayout? ResolvedValue { get; private set; } +#if DEBUG + internal ResponsiveLayout? LastUsedLayout { get; private set; } +#endif + public ResponsiveExtension() { } -#if WINDOWS_UWP +#if !SUPPORTS_XAML_SERVICE_PROVIDER /// protected override object? ProvideValue() { this.Log().WarnIfEnabled(() => "The property value, once initially set, cannot be updated due to UWP limitation. Consider upgrading to WinUI, on which the service provider context is exposed through a ProvideValue overload."); - return GetInitialValue(); + return ResolveValue(); } #else /// protected override object? ProvideValue(IXamlServiceProvider serviceProvider) { - BindToSizeChanged(serviceProvider); + BindToEvents(serviceProvider); - return GetInitialValue(); + return ResolveValue(); } #endif - private object? GetInitialValue() + private object? ResolveValue() { var helper = ResponsiveHelper.GetForCurrentView(); - return GetValueForSize(helper.WindowSize, Layout ?? helper.Layout); + return ResolveValue(helper.WindowSize, GetAppliedLayout() ?? helper.Layout); } - private object? GetValueForSize(Size size, ResponsiveLayout layout) + private object? ResolveValue(Size size, ResponsiveLayout layout) { - var defs = new (double MinWidth, object? Value)?[] + var defs = new (double MinWidth, ResolvedLayout Value)[] { - (layout.Narrowest, Narrowest), - (layout.Narrow, Narrow), - (layout.Normal, Normal), - (layout.Wide, Wide), - (layout.Widest, Widest), - }.Where(x => x?.Value != null).ToArray(); + (layout.Narrowest, new(nameof(layout.Narrowest), Narrowest)), + (layout.Narrow, new(nameof(layout.Narrow), Narrow)), + (layout.Normal, new(nameof(layout.Normal), Normal)), + (layout.Wide, new(nameof(layout.Wide), Wide)), + (layout.Widest, new(nameof(layout.Widest), Widest)), + }.Where(x => x.Value.Value != null).ToArray(); + var match = defs.FirstOrNull(y => y.MinWidth >= size.Width) ?? defs.LastOrNull(); + var resolved = match?.Value; - var match = defs.FirstOrDefault(y => y?.MinWidth >= size.Width) ?? defs.LastOrDefault(); + LastUsedLayout = layout; + ResolvedValue = resolved; - return match?.Value; + return resolved?.Value; } -#if !WINDOWS_UWP - private void BindToSizeChanged(IXamlServiceProvider serviceProvider) +#if SUPPORTS_XAML_SERVICE_PROVIDER + private void BindToEvents(IXamlServiceProvider serviceProvider) { if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget pvt && pvt.TargetObject is FrameworkElement target && pvt.TargetProperty is ProvideValueTargetProperty pvtp && target.FindDependencyPropertyUsingReflection($"{pvtp?.Name}Property") is DependencyProperty dp) { - _weakTarget = new WeakReference(target); + TargetWeakRef = new WeakReference(target); _targetProperty = dp; + // here, we need to bind to two events: + // 1. Window.SizeChanged for obvious reason + // 2. Control.Loaded because the initial value(result of ProvideValue) is resolved without the inherited .resources + // which may define a different DefaultResponsiveLayout resource somewhere along the visual tree, so we need to rectify that. + ResponsiveHelper.GetForCurrentView().Register(this); + target.Loaded += OnTargetLoaded; + +#if DEBUG + TrackedInstances.Add((TargetWeakRef, pvtp!.Name, new WeakReference(this))); +#endif } else { this.Log().Error($"Failed to register {nameof(ResponsiveExtension)}"); } } + + private void OnTargetLoaded(object sender, RoutedEventArgs e) + { + if (TargetWeakRef is { IsAlive: true, Target: FrameworkElement target }) + { + target.Loaded -= OnTargetLoaded; + + // Along the visual tree, we may have a DefaultResponsiveLayout defined in the resources which could cause a different value to be resolved. + // But because in ProvideValue, the target has not been added to the visual tree yet, we cannot access the "full" .resources yet. + // So we need to rectify that here. + target.SetValue(_targetProperty, ResolveValue()); + } + } #endif public void OnSizeChanged(Size size, ResponsiveLayout layout) { -#if !WINDOWS_UWP - if (_weakTarget?.IsAlive == true && - _weakTarget.Target is FrameworkElement target && +#if SUPPORTS_XAML_SERVICE_PROVIDER + if (TargetWeakRef?.Target is FrameworkElement target && _targetProperty is not null) { - target.SetValue(_targetProperty, GetValueForSize(size, Layout ?? layout)); + target.SetValue(_targetProperty, ResolveValue(size, GetAppliedLayout() ?? layout)); } #endif } + + internal ResponsiveLayout? GetAppliedLayout() => + Layout ?? + (TargetWeakRef?.Target as FrameworkElement)?.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey); } From dbcce477d1635d281195fee8db52d4e29bce413f Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Mon, 4 Dec 2023 14:38:30 -0500 Subject: [PATCH 3/8] chore(sampleapp): add more context to debug treegraph --- .../Helpers/VisualTreeHelperEx.cs | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/Uno.Toolkit.UI/Helpers/VisualTreeHelperEx.cs b/src/Uno.Toolkit.UI/Helpers/VisualTreeHelperEx.cs index 0a428e3d6..246829de8 100644 --- a/src/Uno.Toolkit.UI/Helpers/VisualTreeHelperEx.cs +++ b/src/Uno.Toolkit.UI/Helpers/VisualTreeHelperEx.cs @@ -1,4 +1,6 @@ -using System; +//#define TREEGRAPH_VERBOSE_LAYOUT + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -198,6 +200,8 @@ private static string DebugVTNode(object x) static IEnumerable GetDetails(object x) { + #region Common Details: Layout (high priority) +#if TREEGRAPH_VERBOSE_LAYOUT #if __IOS__ if (x is _View view && view.Superview is { }) { @@ -209,11 +213,14 @@ static IEnumerable GetDetails(object x) { yield return $"Rect={FormatViewRect(view)}"; } +#endif #endif if (x is FrameworkElement fe) { yield return $"Actual={fe.ActualWidth}x{fe.ActualHeight}"; +#if TREEGRAPH_VERBOSE_LAYOUT yield return $"Constraints=[{fe.MinWidth},{fe.Width},{fe.MaxWidth}]x[{fe.MinHeight},{fe.Height},{fe.MaxHeight}]"; +#endif yield return $"HV={fe.HorizontalAlignment}/{fe.VerticalAlignment}"; } if (x is UIElement uie) @@ -221,36 +228,62 @@ static IEnumerable GetDetails(object x) //yield return $"Desired={FormatSize(uie.DesiredSize)}"; //yield return $"LAS={FormatSize(uie.LastAvailableSize)}"; } +#if TREEGRAPH_VERBOSE_LAYOUT + if (TryGetDpValue(x, "HorizontalContentAlignment", out var hca) | + TryGetDpValue(x, "VerticalContentAlignment", out var vca)) + { + yield return $"HVC={hca}/{vca}"; + } +#endif + #endregion + #region WinUI Control Details if (x is ScrollViewer sv) { yield return $"Offset={sv.HorizontalOffset:0.#},{sv.VerticalOffset:0.#}"; yield return $"Viewport={sv.ViewportWidth:0.#}x{sv.ViewportHeight:0.#}"; yield return $"Extent={sv.ExtentWidth:0.#}x{sv.ExtentHeight:0.#}"; } - if (TryGetDpValue(x, "HorizontalContentAlignment", out var hca) | - TryGetDpValue(x, "VerticalContentAlignment", out var vca)) - { - yield return $"HVC={hca}/{vca}"; - } if (x is ListViewItem lvi) { yield return $"Index={ItemsControl.ItemsControlFromItemContainer(lvi)?.IndexFromContainer(lvi) ?? -1}"; } if (x is TextBlock txt && !string.IsNullOrEmpty(txt.Text)) { - yield return $"Text=\"{Regex.Escape(txt.Text)}\""; + yield return $"Text=\"{EscapeMultiline(txt.Text)}\""; } if (x is Shape shape) { if (shape.Fill is not null) yield return $"Fill={FormatBrush(shape.Fill)}"; if (shape.Stroke is not null) yield return $"Stroke={FormatBrush(shape.Stroke)}*{shape.StrokeThickness}px"; } + #endregion + #region Toolkit Control Details + if (x is ResponsiveView rv) + { + if (rv.ResolvedLayout is { } resolved) yield return $"Resolved={resolved.Layout}"; + yield return $"Layout={rv.GetAppliedLayout()}"; + } +#if DEBUG + if (ResponsiveExtension.TrackedInstances.Where(y => y.Owner.Target == x).ToArray() is { Length: > 0 } instances) + { + foreach (var item in instances) + { + if (item.Extension.Target is ResponsiveExtension re) + { + yield return $"{item.Property}@Responsive: {re.LastUsedLayout}->{re.ResolvedValue?.Layout}\\{re.ResolvedValue?.Value}"; + } + } + } +#endif + #endregion + #region Common Details: Layout,Misc (low priority) if (TryGetDpValue(x, "CornerRadius", out var cr)) yield return $"CornerRadius={FormatCornerRadius(cr)}"; if (TryGetDpValue(x, "Margin", out var margin)) yield return $"Margin={FormatThickness(margin)}"; if (TryGetDpValue(x, "Padding", out var padding)) yield return $"Padding={FormatThickness(padding)}"; if (TryGetDpValue(x, "Opacity", out var opacity)) yield return $"Opacity={opacity}"; if (TryGetDpValue(x, "Visibility", out var visibility)) yield return $"Visibility={visibility}"; if (GetActiveVisualStates(x as Control) is { } states) yield return $"VisualStates={states}"; + #endregion } } private static string DebugVTNode(object x, Func> describeProperties) From 11e890988cae758b272da95d3239189a636ffa5f Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Tue, 5 Dec 2023 20:31:56 -0500 Subject: [PATCH 4/8] feat: automatic type conversion for res-markup --- .../ResponsiveExtensionsSamplePage.xaml | 19 ++------ .../Markup/ResponsiveExtension.cs | 48 +++++++++++++++++-- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/ResponsiveExtensionsSamplePage.xaml b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/ResponsiveExtensionsSamplePage.xaml index e838b33c9..7e8775f70 100644 --- a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/ResponsiveExtensionsSamplePage.xaml +++ b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/ResponsiveExtensionsSamplePage.xaml @@ -5,19 +5,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sample="using:Uno.Toolkit.Samples" xmlns:utu="using:Uno.Toolkit.UI" - xmlns:void="used for commenting out node or attribute" + xmlns:void="used for quickly commenting out node or attribute" mc:Ignorable="d void" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> - - Vertical - Horizontal - 15 - 25 - Crimson - Blue - - @@ -25,7 +16,7 @@ - + @@ -35,14 +26,14 @@ - + + Background="{utu:Responsive Normal=Red, + Wide=Blue}" /> diff --git a/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs b/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs index 6fc7f0079..03552a7bf 100644 --- a/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs +++ b/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs @@ -5,9 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Logging; +using Windows.Foundation; using Uno.Extensions; using Uno.Logging; -using Windows.Foundation; #if IS_WINUI using Microsoft.UI.Xaml; @@ -17,6 +18,8 @@ using Windows.UI.Xaml.Markup; #endif +using static System.Reflection.BindingFlags; + namespace Uno.Toolkit.UI; #if DEBUG @@ -32,6 +35,8 @@ namespace Uno.Toolkit.UI; /// public partial class ResponsiveExtension : MarkupExtension, IResponsiveCallback { + private static readonly ILogger _logger = typeof(ResponsiveExtension).Log(); + public object? Narrowest { get; set; } public object? Narrow { get; set; } public object? Normal { get; set; } @@ -42,6 +47,7 @@ public partial class ResponsiveExtension : MarkupExtension, IResponsiveCallback #if SUPPORTS_XAML_SERVICE_PROVIDER internal WeakReference? TargetWeakRef { get; private set; } + private Type? _propertyType; private DependencyProperty? _targetProperty; #endif internal ResolvedLayout? ResolvedValue { get; private set; } @@ -90,10 +96,20 @@ public ResponsiveExtension() var match = defs.FirstOrNull(y => y.MinWidth >= size.Width) ?? defs.LastOrNull(); var resolved = match?.Value; +#if DEBUG LastUsedLayout = layout; +#endif ResolvedValue = resolved; - return resolved?.Value; + var result = resolved?.Value; +#if SUPPORTS_XAML_SERVICE_PROVIDER + if (result != null && _propertyType != null && result.GetType() != _propertyType) + { + result = XamlCastSafe(result, _propertyType); + } +#endif + + return result; } #if SUPPORTS_XAML_SERVICE_PROVIDER @@ -102,11 +118,16 @@ private void BindToEvents(IXamlServiceProvider serviceProvider) if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget pvt && pvt.TargetObject is FrameworkElement target && pvt.TargetProperty is ProvideValueTargetProperty pvtp && - target.FindDependencyPropertyUsingReflection($"{pvtp?.Name}Property") is DependencyProperty dp) + target.FindDependencyPropertyUsingReflection($"{pvtp.Name}Property") is DependencyProperty dp) { TargetWeakRef = new WeakReference(target); _targetProperty = dp; - + _propertyType = +#if HAS_UNO // workaround for uno#14719: uno doesn't inject the proper pvtp.Type + target.GetType().GetProperty(pvtp.Name, Public | Instance | FlattenHierarchy)?.PropertyType; +#else + pvtp.Type; +#endif // here, we need to bind to two events: // 1. Window.SizeChanged for obvious reason // 2. Control.Loaded because the initial value(result of ProvideValue) is resolved without the inherited .resources @@ -116,7 +137,7 @@ pvt.TargetProperty is ProvideValueTargetProperty pvtp && target.Loaded += OnTargetLoaded; #if DEBUG - TrackedInstances.Add((TargetWeakRef, pvtp!.Name, new WeakReference(this))); + TrackedInstances.Add((TargetWeakRef, pvtp.Name, new WeakReference(this))); #endif } else @@ -150,6 +171,23 @@ public void OnSizeChanged(Size size, ResponsiveLayout layout) #endif } + private static object? XamlCastSafe(object value, Type type) + { + try + { + return XamlBindingHelper.ConvertValue(type, value); + } + catch (Exception) + { + if (_logger.IsEnabled(LogLevel.Error)) + { + _logger.LogError($"Failed to convert value from '{value.GetType().Name}' to '{type.Name}'"); + } + + return value; + } + } + internal ResponsiveLayout? GetAppliedLayout() => Layout ?? (TargetWeakRef?.Target as FrameworkElement)?.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey); From 200c61e983cc9062e4a32cf3fa05ceed411b66ce Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Wed, 6 Dec 2023 11:53:16 -0500 Subject: [PATCH 5/8] chore: fix res-layout not resolving from app.resource --- .../Controls/ResponsiveView/ResponsiveView.cs | 3 ++- src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs | 10 ++++++++++ src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.cs b/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.cs index 3a0c595f5..66397ea15 100644 --- a/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.cs +++ b/src/Uno.Toolkit.UI/Controls/ResponsiveView/ResponsiveView.cs @@ -68,5 +68,6 @@ private void ResolveTemplate(Size size, ResponsiveLayout layout) internal ResponsiveLayout? GetAppliedLayout() => ResponsiveLayout ?? - this.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey); + this.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey) ?? + Application.Current.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey); } diff --git a/src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs b/src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs index dc55b33aa..7d4bd20b7 100644 --- a/src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs +++ b/src/Uno.Toolkit.UI/Helpers/ResourceHelper.cs @@ -22,4 +22,14 @@ internal static class ResourceHelper return null; } + + public static T? ResolveLocalResource(this Application resourceProvider, object key) where T : class + => resourceProvider.ResolveLocalResource(key) as T; + + public static object? ResolveLocalResource(this Application resourceProvider, object key) + { + if (resourceProvider.Resources.TryGetValue(key, out var resource)) return resource; + + return null; + } } diff --git a/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs b/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs index 03552a7bf..93a757a3c 100644 --- a/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs +++ b/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs @@ -190,5 +190,6 @@ public void OnSizeChanged(Size size, ResponsiveLayout layout) internal ResponsiveLayout? GetAppliedLayout() => Layout ?? - (TargetWeakRef?.Target as FrameworkElement)?.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey); + (TargetWeakRef?.Target as FrameworkElement)?.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey) ?? + Application.Current.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey); } From e0c6d0220304043a427f4ab634554d90a0bfcd0c Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Wed, 6 Dec 2023 11:53:39 -0500 Subject: [PATCH 6/8] chore: update responsive docs --- doc/controls/ResponsiveView.md | 64 ++++++++--------------- doc/helpers/responsive-extension.md | 79 +++++++++++++---------------- 2 files changed, 54 insertions(+), 89 deletions(-) diff --git a/doc/controls/ResponsiveView.md b/doc/controls/ResponsiveView.md index 2190efd82..cff9d42bf 100644 --- a/doc/controls/ResponsiveView.md +++ b/doc/controls/ResponsiveView.md @@ -3,19 +3,14 @@ uid: Toolkit.Controls.ResponsiveView --- # ResponsiveView -## Summary - The `ResponsiveView` provides the ability to display different content based on screen size, making it easy to adapt to various devices. ## Remarks -The ResponsiveView Control adapts to different screen sizes by dynamically choosing the right template. It looks at the current screen width and the defined templates. Since not all templates need a value, the control ensures a smooth user experience by picking the smallest defined template that satisfies the width requirements. If no match is found, it defaults to the largest defined template. +The `ResponsiveView` control adapts to different screen sizes by dynamically choosing the right template. It looks at the current screen width and the defined templates. Since not all templates need a value, the control ensures a smooth user experience by picking the smallest defined template that satisfies the width requirements. If no match is found, it defaults to the largest defined template. -**Initialization**: The `ResponsiveHelper` needs to be hooked up to the window's `SizeChanged` event in order for it to receive updates when the window size changes. -This is typically done in the `OnLaunched` method in the `App` class, where you can get the current window and call the `HookupEvent` method on the `ResponsiveHelper`. - -Here is an example of how this might be achieved: - +**Initialization**: The `ResponsiveHelper` needs to be hooked up to the window's `SizeChanged` event in order for this control to receive updates when the window size changes. +This is typically done in the `OnLaunched` method in the `App` class, where you can get the current `Window` instance for `ResponsiveHelper.HookupEvent`: ```cs protected override void OnLaunched(LaunchActivatedEventArgs args) { @@ -24,13 +19,14 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) #else MainWindow = Microsoft.UI.Xaml.Window.Current; #endif + // ... var helper = Uno.Toolkit.UI.ResponsiveHelper.GetForCurrentView(); helper.HookupEvent(MainWindow); } ``` -## Inheritance +## Inheritance Object → DependencyObject → UIElement → FrameworkElement → Control → ContentControl ## Properties @@ -44,7 +40,7 @@ Object → DependencyObject → UIElement → FrameworkElement ͛ | ResponsiveLayout | ResponsiveLayout | Overrides the screen size threshold/breakpoints. | ### ResponsiveLayout -Provides the ability to override the MaxWidth for each screen size: `Narrowest`, `Narrow`, `Normal`, `Wide`, and `Widest`. +Provides the ability to override the breakpoint for each screen size: `Narrowest`, `Narrow`, `Normal`, `Wide`, and `Widest`. ### Properties | Property | Type | Description | @@ -57,35 +53,25 @@ Provides the ability to override the MaxWidth for each screen size: `Narrowest`, ## Usage +> [!TIP] +> It is not necessary to define every template or layout breakpoints: Narrowest,Narrow,Normal,Wide,Widest. You can just define the bare minimum needed. + ```xml xmlns:utu="using:Uno.Toolkit.UI" ... + - - - - - + - - - - - - - - - - ``` @@ -97,37 +83,27 @@ xmlns:utu="using:Uno.Toolkit.UI" - 200 350 - 800 1200 - 1500 - - - - - + - - - - - - - - - - -``` \ No newline at end of file +``` + +> [!NOTE] +> This `ResponsiveLayout` can also be provided from different locations. In the order of precedences, they are: +> - from the `.ResponsiveLayout` property +> - in `ResponsiveView`'s parent `.Resources` with `x:Key="DefaultResponsiveLayout"`, or its ancestor's... +> - in `Application.Resources` with `x:Key="DefaultResponsiveLayout"` +> - from the hardcoded `ResponsiveHelper.Layout` diff --git a/doc/helpers/responsive-extension.md b/doc/helpers/responsive-extension.md index 97a03475a..88de16fd3 100644 --- a/doc/helpers/responsive-extension.md +++ b/doc/helpers/responsive-extension.md @@ -3,18 +3,12 @@ uid: Toolkit.Helpers.ResponsiveExtension --- # ResponsiveExtension -## Summary The `ResponsiveExtension` class is a markup extension that enables the customization of `UIElement` properties based on screen size. This functionality provides a dynamic and responsive user interface experience. ### Inheritance Object → MarkupExtension → ResponsiveExtension -### Constructors -| Constructor | Description | -|-----------------------|----------------------------------------------------------------| -| ResponsiveExtension() | Initializes a new instance of the `ResponsiveExtension` class. | - ## Properties | Property | Type | Description | | ---------- | ---------------- | ---------------------------------------------------------- | @@ -39,16 +33,8 @@ This is done using an instance of the `ResponsiveLayout` class. | Widest | double | Default value is 1080. | ## Remarks -**Platform limitation**: The ability to update property values when the window size changes is only available on targets other than Windows UWP. -Due to a limitation of the UWP API (Windows target only), the `MarkupExtension.ProvideValue(IXamlServiceProvider)` overload is unavailable, which is required to continuously update the value. -Because of this, the markup extension will only provide the initial value, and will not respond to window size changes. - -**Initialization**: The `ResponsiveHelper` needs to be hooked up to the window's `SizeChanged` event in order for it to receive updates when the window size changes. -This is typically done in the `OnLaunched` method in the `App` class, where you can get the current window and call the `HookupEvent` method on the `ResponsiveHelper`. -It is important to do this when the app launches, otherwise the `ResponsiveExtension` won't be able to update the property values when the window size changes. - -Here is an example of how this might be achieved: - +**Initialization**: The `ResponsiveHelper` needs to be hooked up to the window's `SizeChanged` event in order for this markup to receive updates when the window size changes. +This is typically done in the `OnLaunched` method in the `App` class, where you can get the current `Window` instance for `ResponsiveHelper.HookupEvent`: ```cs protected override void OnLaunched(LaunchActivatedEventArgs args) { @@ -57,54 +43,57 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) #else MainWindow = Microsoft.UI.Xaml.Window.Current; #endif + // ... var helper = Uno.Toolkit.UI.ResponsiveHelper.GetForCurrentView(); helper.HookupEvent(MainWindow); } ``` +## Platform limitation (UWP-desktop) +`ResponsiveExtension` relies on `MarkupExtension.ProvideValue(IXamlServiceProvider)` to find the target control and property for continuous update of value, and to obtain the property type to apply automatic type conversion as its value properties are parsed as string by the xaml engine. Because this overload is a new addition to WinUI only, UWP project targeting windows will not have access to these features. Uno UWP project against non-windows targets do not have this limitation. However, the windows app may crash or misbehave if you attempt to use this markup on non-string property. +```xml + +``` +You can workaround this, by declaring the values are resources and use `{StaticResource}` to refer to them: +```xml + + Red + Blue +... -**Property type limitation**: Content property values other than strings must be appropriately typed for the markup extension to interpret them correctly. -In the basic usage example below, the values `NarrowRed` and `WideBlue` are properly typed as they refer to the `StaticResource` keys defined in `Page.Resources`. -For instance, using `Background={utu:Responsive Narrow=SkyBlue, Wide=Pink}` would be incorrect, while the string literal usage under `` is accepted. - + +``` ## Usage -### Basic example +> [!TIP] +> It is not necessary to define every template or layout breakpoints: Narrowest,Narrow,Normal,Wide,Widest. You can just define the bare minimum needed. + ```xml xmlns:utu="using:Uno.Toolkit.UI" ... - - Crimson - Blue - -... - + + ``` ### Custom thresholds ```xml xmlns:utu="using:Uno.Toolkit.UI" -xmlns:hlp="using:Uno.Toolkit.UI.Helpers" ... + - + ... - - - - - + + ``` +> [!NOTE] +> This `ResponsiveLayout` can also be provided from different locations. In the order of precedences, they are: +> - from the `Layout` property +> - in the property owner's parent `.Resources` with `x:Key="DefaultResponsiveLayout"`, or the property owner's parent's parent's... +> - in `Application.Resources` with `x:Key="DefaultResponsiveLayout"` +> - from the hardcoded `ResponsiveHelper.Layout` + From ddfa70dd56055699f871a78befe85da2fbf94282 Mon Sep 17 00:00:00 2001 From: Xiaotian Gu Date: Wed, 6 Dec 2023 13:52:21 -0500 Subject: [PATCH 7/8] chore: Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Silviu Obreja Co-authored-by: Érik Lima <114886335+eriklimakc@users.noreply.github.com> --- doc/controls/ResponsiveView.md | 4 ++-- doc/helpers/responsive-extension.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/controls/ResponsiveView.md b/doc/controls/ResponsiveView.md index cff9d42bf..ef7079bdd 100644 --- a/doc/controls/ResponsiveView.md +++ b/doc/controls/ResponsiveView.md @@ -54,7 +54,7 @@ Provides the ability to override the breakpoint for each screen size: `Narrowest ## Usage > [!TIP] -> It is not necessary to define every template or layout breakpoints: Narrowest,Narrow,Normal,Wide,Widest. You can just define the bare minimum needed. +> It is not necessary to define every template or layout breakpoint: Narrowest, Narrow, Normal, Wide, Widest. You can just define the bare minimum needed. ```xml xmlns:utu="using:Uno.Toolkit.UI" @@ -102,7 +102,7 @@ xmlns:utu="using:Uno.Toolkit.UI" ``` > [!NOTE] -> This `ResponsiveLayout` can also be provided from different locations. In the order of precedences, they are: +> This `ResponsiveLayout` can also be provided from different locations. In order of precedences, they are: > - from the `.ResponsiveLayout` property > - in `ResponsiveView`'s parent `.Resources` with `x:Key="DefaultResponsiveLayout"`, or its ancestor's... > - in `Application.Resources` with `x:Key="DefaultResponsiveLayout"` diff --git a/doc/helpers/responsive-extension.md b/doc/helpers/responsive-extension.md index 88de16fd3..f6c6d7c33 100644 --- a/doc/helpers/responsive-extension.md +++ b/doc/helpers/responsive-extension.md @@ -50,12 +50,12 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) } ``` ## Platform limitation (UWP-desktop) -`ResponsiveExtension` relies on `MarkupExtension.ProvideValue(IXamlServiceProvider)` to find the target control and property for continuous update of value, and to obtain the property type to apply automatic type conversion as its value properties are parsed as string by the xaml engine. Because this overload is a new addition to WinUI only, UWP project targeting windows will not have access to these features. Uno UWP project against non-windows targets do not have this limitation. However, the windows app may crash or misbehave if you attempt to use this markup on non-string property. +`ResponsiveExtension` relies on `MarkupExtension.ProvideValue(IXamlServiceProvider)` to find the target control and property for continuous value updates, and to obtain the property type to apply automatic type conversion, as its value properties are parsed as string by the XAML engine. Since this overload is a recent addition exclusive to WinUI, UWP projects targeting Windows won't have access to these features. Uno UWP projects targeting non-Windows platforms do not face this limitation. However, the Windows app may crash or present unexpected behavior if you attempt to use this markup on a non-string property. ```xml ``` -You can workaround this, by declaring the values are resources and use `{StaticResource}` to refer to them: +You can workaround this by declaring the values as resources and using {StaticResource} to refer to them: ```xml Red @@ -68,7 +68,7 @@ You can workaround this, by declaring the values are resources and use `{StaticR ## Usage > [!TIP] -> It is not necessary to define every template or layout breakpoints: Narrowest,Narrow,Normal,Wide,Widest. You can just define the bare minimum needed. +> It is not necessary to define every template or layout breakpoint: Narrowest, Narrow, Normal, Wide, Widest. You can just define the bare minimum needed. ```xml xmlns:utu="using:Uno.Toolkit.UI" From 5687074b20872472b9c0245f37e929a29e6535f4 Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Wed, 6 Dec 2023 14:21:12 -0500 Subject: [PATCH 8/8] chore: fix build --- src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs | 1 + src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs b/src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs index 7ef47bb0e..12fde226e 100644 --- a/src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs +++ b/src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Windows.Foundation; using Uno.Disposables; + #if IS_WINUI using Microsoft.UI.Xaml; #else diff --git a/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs b/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs index 93a757a3c..789fa0297 100644 --- a/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs +++ b/src/Uno.Toolkit.UI/Markup/ResponsiveExtension.cs @@ -3,7 +3,6 @@ #endif using System; -using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using Windows.Foundation; @@ -190,6 +189,8 @@ public void OnSizeChanged(Size size, ResponsiveLayout layout) internal ResponsiveLayout? GetAppliedLayout() => Layout ?? +#if SUPPORTS_XAML_SERVICE_PROVIDER (TargetWeakRef?.Target as FrameworkElement)?.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey) ?? +#endif Application.Current.ResolveLocalResource(ResponsiveLayout.DefaultResourceKey); }