From 527bba3a889b0c9efa00a729e26d993aa052ebd7 Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Tue, 8 Oct 2024 18:07:46 -0400 Subject: [PATCH] chore: add support for legacy DO --- .../Given_DependencyObjectGenerator.cs | 7 ++ .../DependencyObjectGenerator.cs | 61 ++++++----- .../TemplatedParent/Setup/BehaviorSetup.xaml | 19 ++++ .../Setup/BehaviorSetup.xaml.cs | 101 ++++++++++++++++++ .../TemplatedParent/TemplatedParentTests.cs | 26 ++++- src/Uno.UI/DataBinding/BindingExpression.cs | 19 +++- .../DataBinding/ITemplatedParentProvider.cs | 9 ++ .../UI/Xaml/DependencyObjectStore.Binder.cs | 15 +++ src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 2 +- src/Uno.UI/UI/Xaml/TemplatedParentScope.cs | 12 ++- 10 files changed, 233 insertions(+), 38 deletions(-) create mode 100644 src/Uno.UI.RuntimeTests/Tests/TemplatedParent/Setup/BehaviorSetup.xaml create mode 100644 src/Uno.UI.RuntimeTests/Tests/TemplatedParent/Setup/BehaviorSetup.xaml.cs diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/DependencyObjectGeneratorTests/Given_DependencyObjectGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/DependencyObjectGeneratorTests/Given_DependencyObjectGenerator.cs index 731110a9ed7e..f5944c79ee4e 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/DependencyObjectGeneratorTests/Given_DependencyObjectGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/DependencyObjectGeneratorTests/Given_DependencyObjectGenerator.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Uno.UI.SourceGenerators.DependencyObject; using Uno.UI.SourceGenerators.Tests.Verifiers; @@ -369,4 +370,10 @@ public void SuspendBindings() => test.TestState.AdditionalReferences.AddRange(BuildUnoReferences(isAndroid: false)); await test.RunAsync(); } + + [TestMethod] + public void TestAsdAsd() + { + + } } diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/DependencyObject/DependencyObjectGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/DependencyObject/DependencyObjectGenerator.cs index b4198ea129a6..059614e4d766 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/DependencyObject/DependencyObjectGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/DependencyObject/DependencyObjectGenerator.cs @@ -175,16 +175,18 @@ private void ProcessType(INamedTypeSymbol typeSymbol) } }; + var canBeTpProvider = !typeSymbol.Interfaces.Any(x => x.Name == "INotTemplatedParentProvider"); + var implementations = new string?[] { "IDependencyObjectStoreProvider", _isUnoSolution && !typeSymbol.IsSealed ? "IDependencyObjectInternal" : null, - "ITemplatedParentProvider", + canBeTpProvider ? "ITemplatedParentProvider" : null, "IWeakReferenceProvider", }.Where(x => x is not null); using (typeSymbol.AddToIndentedStringBuilder(builder, beforeClassHeaderAction, afterClassHeader: " : " + string.Join(", ", implementations))) { - GenerateDependencyObjectImplementation(typeSymbol, builder, hasDispatcherQueue: _dependencyObjectSymbol!.GetMembers("DispatcherQueue").Any()); + GenerateDependencyObjectImplementation(typeSymbol, builder, hasDispatcherQueue: _dependencyObjectSymbol!.GetMembers("DispatcherQueue").Any(), canBeTpProvider); GenerateIBinderImplementation(typeSymbol, builder); } @@ -766,7 +768,7 @@ public override bool Equals(object other) } } - private void GenerateDependencyObjectImplementation(INamedTypeSymbol typeSymbol, IndentedStringBuilder builder, bool hasDispatcherQueue) + private void GenerateDependencyObjectImplementation(INamedTypeSymbol typeSymbol, IndentedStringBuilder builder, bool hasDispatcherQueue, bool implTpProvider) { builder.AppendLineIndented(@"private DependencyObjectStore __storeBackingField;"); builder.AppendLineIndented(@"public global::Windows.UI.Core.CoreDispatcher Dispatcher => global::Windows.ApplicationModel.Core.CoreApplication.MainView.Dispatcher;"); @@ -817,33 +819,36 @@ private void GenerateDependencyObjectImplementation(INamedTypeSymbol typeSymbol, } } - var unoBrowsableOnly = _isUnoSolution ? null : "[EditorBrowsable(EditorBrowsableState.Never)]"; + if (implTpProvider) + { + var unoBrowsableOnly = _isUnoSolution ? null : "[EditorBrowsable(EditorBrowsableState.Never)]"; - builder.AppendLine(); - builder.AppendMultiLineIndented($$""" - {{unoBrowsableOnly}}private ManagedWeakReference _templatedParentWeakRef; - {{unoBrowsableOnly}}public ManagedWeakReference GetTemplatedParentWeakRef() => _templatedParentWeakRef; + builder.AppendLine(); + builder.AppendMultiLineIndented($$""" + {{unoBrowsableOnly}}private ManagedWeakReference _templatedParentWeakRef; + {{unoBrowsableOnly}}public ManagedWeakReference GetTemplatedParentWeakRef() => _templatedParentWeakRef; - {{unoBrowsableOnly}}public DependencyObject GetTemplatedParent() => _templatedParentWeakRef?.Target as DependencyObject; - {{unoBrowsableOnly}}public void SetTemplatedParent(DependencyObject parent) - { - //if (parent != null) - //{ - // global::System.Diagnostics.Debug.Assert(parent - // is global::Windows.UI.Xaml.Controls.Control - // or global::Windows.UI.Xaml.Controls.ContentPresenter - // or global::Windows.UI.Xaml.Controls.ItemsPresenter); - // global::System.Diagnostics.Debug.Assert(GetTemplatedParent() == null); - //} - - SetTemplatedParentImpl(parent); - } - {{unoBrowsableOnly}}{{(typeSymbol.IsSealed ? "private" : "private protected virtual")}} void SetTemplatedParentImpl(DependencyObject parent) - { - _templatedParentWeakRef = (parent as IWeakReferenceProvider)?.WeakReference; - } - """ - ); + {{unoBrowsableOnly}}public DependencyObject GetTemplatedParent() => _templatedParentWeakRef?.Target as DependencyObject; + {{unoBrowsableOnly}}public void SetTemplatedParent(DependencyObject parent) + { + //if (parent != null) + //{ + // global::System.Diagnostics.Debug.Assert(parent + // is global::Windows.UI.Xaml.Controls.Control + // or global::Windows.UI.Xaml.Controls.ContentPresenter + // or global::Windows.UI.Xaml.Controls.ItemsPresenter); + // global::System.Diagnostics.Debug.Assert(GetTemplatedParent() == null); + //} + + SetTemplatedParentImpl(parent); + } + {{unoBrowsableOnly}}{{(typeSymbol.IsSealed ? "private" : "private protected virtual")}} void SetTemplatedParentImpl(DependencyObject parent) + { + _templatedParentWeakRef = (parent as IWeakReferenceProvider)?.WeakReference; + } + """ + ); + } } } } diff --git a/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/Setup/BehaviorSetup.xaml b/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/Setup/BehaviorSetup.xaml new file mode 100644 index 000000000000..bfe83efffdbf --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/Setup/BehaviorSetup.xaml @@ -0,0 +1,19 @@ + + + + + diff --git a/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/Setup/BehaviorSetup.xaml.cs b/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/Setup/BehaviorSetup.xaml.cs new file mode 100644 index 000000000000..cb86bf362bb2 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/Setup/BehaviorSetup.xaml.cs @@ -0,0 +1,101 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Uno.UI.DataBinding; + +namespace Uno.UI.RuntimeTests.Tests.TemplatedParent.Setup; + +public sealed partial class BehaviorSetup : Page +{ + public BehaviorSetup() + { + this.InitializeComponent(); + } +} + +public sealed class Interaction +{ + #region DependencyProperty: Behaviors + + public static DependencyProperty BehaviorsProperty { get; } = DependencyProperty.RegisterAttached( + "Behaviors", + typeof(BehaviorCollection), + typeof(Interaction), + new PropertyMetadata(null, OnBehaviorsChanged)); + + public static BehaviorCollection GetBehaviors(DependencyObject obj) => GetBehaviorsOverride(obj); + public static void SetBehaviors(DependencyObject obj, BehaviorCollection value) => obj.SetValue(BehaviorsProperty, value); + + #endregion + + private static BehaviorCollection GetBehaviorsOverride(DependencyObject obj) + { + var value = (BehaviorCollection)obj.GetValue(BehaviorsProperty); + if (value is null) + { + obj.SetValue(BehaviorsProperty, value = new BehaviorCollection()); + } + + return value; + } + + private static void OnBehaviorsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue is BehaviorCollection collection) + { + collection.AssociatedObject = sender; + } + } +} +public sealed class BehaviorCollection : DependencyObjectCollection +{ + public DependencyObject AssociatedObject { get; set; } +} + +public interface IBehavior { } + +public partial class LegacyDOBehavior : DependencyObject, IBehavior, INotTemplatedParentProvider +{ + #region DependencyProperty: TestValue + + public static DependencyProperty TestValueProperty { get; } = DependencyProperty.Register( + nameof(TestValue), + typeof(object), + typeof(LegacyDOBehavior), + new PropertyMetadata(default(object), OnTestValueChanged)); + + public object TestValue + { + get => (object)GetValue(TestValueProperty); + set => SetValue(TestValueProperty, value); + } + + #endregion + + private static void OnTestValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + } +} +public partial class NonLegacyDOBehavior : DependencyObject, IBehavior +{ + #region DependencyProperty: TestValue + + public static DependencyProperty TestValueProperty { get; } = DependencyProperty.Register( + nameof(TestValue), + typeof(object), + typeof(NonLegacyDOBehavior), + new PropertyMetadata(default(object), OnTestValueChanged)); + + public object TestValue + { + get => (object)GetValue(TestValueProperty); + set => SetValue(TestValueProperty, value); + } + + #endregion + + private static void OnTestValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + } +} diff --git a/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs b/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs index 6ba2946461c9..e99e086b8cb4 100644 --- a/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs +++ b/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs @@ -18,6 +18,7 @@ using Microsoft.UI.Xaml.Media.Animation; using Microsoft.VisualStudio.TestTools.UnitTesting; using Uno.Extensions; +using Uno.UI.DataBinding; using Uno.UI.Extensions; using Uno.UI.Helpers; using Uno.UI.RuntimeTests.Helpers; @@ -31,7 +32,7 @@ namespace Uno.UI.RuntimeTests.Tests.TemplatedParent; [TestClass] [RunsOnUIThread] -public partial class TemplatedParentTests +public partial class TemplatedParentTests // tests { [TestMethod] public async Task Uno8049_Test() @@ -288,8 +289,27 @@ public async Task VisualStateGroup_TP_Inheritance() """; VerifyTree(expectations, setup, checkVSG: true); } + + [TestMethod] + public async Task LegacyDO_StillSupports_TP_Injection() + { + var setup = new BehaviorSetup(); + await UITestHelper.Load(setup, x => x.IsLoaded); + + var button = setup.Content as Button ?? throw new Exception("button not found"); + var grid = button.GetTemplateRoot() ?? throw new Exception("template root not found"); + var collection = Interaction.GetBehaviors(grid) ?? throw new Exception("behavior collection not found"); + + var sut0 = collection.ElementAtOrDefault(0) as LegacyDOBehavior; + + // Verify that "legacy DepObj"(DO from library built before templated-parent rework) + // 1. is simulated correctly via the "INotTemplatedParentProvider" blocker + Assert.IsNotInstanceOfType(sut0, "sut0 shouldnt impl ITemplatedParentProvider"); + // 2. still supports tp-injection. + Assert.AreEqual(button.Tag, sut0.TestValue, "sut0.TestValue template-binding failed"); + } } -public partial class TemplatedParentTests +public partial class TemplatedParentTests // helper methods { private static string SkipLines(string tree, params int[] lines) { @@ -356,7 +376,7 @@ private static object GetTemplatedParentCompat(FrameworkElement fe) #endif } } -public partial class TemplatedParentTests +public partial class TemplatedParentTests // TreeGraph helper methods { private static IEnumerable DebugVT_TP(object x) { diff --git a/src/Uno.UI/DataBinding/BindingExpression.cs b/src/Uno.UI/DataBinding/BindingExpression.cs index 5550a6731ad3..ec18e118ad06 100644 --- a/src/Uno.UI/DataBinding/BindingExpression.cs +++ b/src/Uno.UI/DataBinding/BindingExpression.cs @@ -54,7 +54,13 @@ public object DataContext { if (ParentBinding.IsTemplateBinding) { - return (_view?.Target as ITemplatedParentProvider)?.GetTemplatedParent(); + return _view?.Target switch + { + ITemplatedParentProvider tpProvider => tpProvider.GetTemplatedParent(), + IDependencyObjectStoreProvider dosProvider => dosProvider.Store.GetTemplatedParent2(), + + _ => null, + }; } if (_isElementNameSource || ExplicitSource != null) { @@ -149,7 +155,16 @@ Binding binding ApplyElementName(); } - private ManagedWeakReference GetWeakTemplatedParent() => (_view?.Target as ITemplatedParentProvider)?.GetTemplatedParentWeakRef(); + private ManagedWeakReference GetWeakTemplatedParent() + { + return _view?.Target switch + { + ITemplatedParentProvider tpProvider => tpProvider.GetTemplatedParentWeakRef(), + IDependencyObjectStoreProvider dosProvider => dosProvider.Store.GetTemplatedParentWeakRef(), + + _ => null, + }; + } private ManagedWeakReference GetWeakDataContext() { diff --git a/src/Uno.UI/DataBinding/ITemplatedParentProvider.cs b/src/Uno.UI/DataBinding/ITemplatedParentProvider.cs index f88a3d1f346c..c307f4f02ff3 100644 --- a/src/Uno.UI/DataBinding/ITemplatedParentProvider.cs +++ b/src/Uno.UI/DataBinding/ITemplatedParentProvider.cs @@ -17,3 +17,12 @@ public interface ITemplatedParentProvider void SetTemplatedParent(DependencyObject parent); } + +[EditorBrowsable(EditorBrowsableState.Never)] +/// +/// Marker interface used to block DependencyObjectGenerator +/// from injecting and its implementations. +/// +public interface INotTemplatedParentProvider +{ +} diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.Binder.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.Binder.cs index 3b313aaf4902..b37c5ecb6d5d 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.Binder.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.Binder.cs @@ -1,5 +1,10 @@ #nullable enable +#if ENABLE_LEGACY_TEMPLATED_PARENT_SUPPORT +// fallback option for legacy DepObj from external library generated before templated-parent rework. +#define ENABLE_LEGACY_DO_TP_SUPPORT +#endif + using System; using System.Collections; using System.Collections.Generic; @@ -43,6 +48,11 @@ public partial class DependencyObjectStore private bool _bindingsSuspended; private readonly DependencyProperty _dataContextProperty; +#if ENABLE_LEGACY_DO_TP_SUPPORT + private ManagedWeakReference? _templatedParentWeakRef; + internal ManagedWeakReference? GetTemplatedParentWeakRef() => _templatedParentWeakRef; +#endif + #if ENABLE_LEGACY_TEMPLATED_PARENT_SUPPORT public void SetTemplatedParent(FrameworkElement? templatedParent) { @@ -50,6 +60,11 @@ public void SetTemplatedParent(FrameworkElement? templatedParent) } #endif +#if ENABLE_LEGACY_DO_TP_SUPPORT + internal DependencyObject? GetTemplatedParent2() => _templatedParentWeakRef?.Target as DependencyObject; + internal void SetTemplatedParent2(DependencyObject parent) => _templatedParentWeakRef = (parent as IWeakReferenceProvider)?.WeakReference; +#endif + private bool IsCandidateChild([NotNullWhen(true)] object? child) { if (child is IDependencyObjectStoreProvider) diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 1ec2e41b4f7b..438e9b26b128 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -185,7 +185,7 @@ public DependencyObjectStore(object originalObject, DependencyProperty dataConte _dataContextProperty = dataContextProperty; #if ENABLE_LEGACY_TEMPLATED_PARENT_SUPPORT - TemplatedParentScope.UpdateTemplatedParentIfNeeded(originalObject as DependencyObject); + TemplatedParentScope.UpdateTemplatedParentIfNeeded(originalObject as DependencyObject, store: this); #endif if (_trace.IsEnabled) diff --git a/src/Uno.UI/UI/Xaml/TemplatedParentScope.cs b/src/Uno.UI/UI/Xaml/TemplatedParentScope.cs index 8de571969a52..2d85062f360c 100644 --- a/src/Uno.UI/UI/Xaml/TemplatedParentScope.cs +++ b/src/Uno.UI/UI/Xaml/TemplatedParentScope.cs @@ -15,16 +15,16 @@ internal static class TemplatedParentScope /// Set the templated-parent for the dependency-object based on the currently materializing template. /// /// Should be true, if not called from ctor. - internal static void UpdateTemplatedParentIfNeeded(DependencyObject? @do, bool reapplyTemplateBindings = false) + internal static void UpdateTemplatedParentIfNeeded(DependencyObject? @do, bool reapplyTemplateBindings = false, DependencyObjectStore? store = null) { if (@do is null) return; if (GetCurrentTemplate() is { IsLegacyTemplate: true, TemplatedParent: { } tp }) { - UpdateTemplatedParent(@do, tp, reapplyTemplateBindings); + UpdateTemplatedParent(@do, tp, reapplyTemplateBindings, store); } } - internal static void UpdateTemplatedParent(DependencyObject? @do, DependencyObject tp, bool reapplyTemplateBindings = true) + internal static void UpdateTemplatedParent(DependencyObject? @do, DependencyObject tp, bool reapplyTemplateBindings = true, DependencyObjectStore? store = null) { if (@do is ITemplatedParentProvider tpProvider) { @@ -35,9 +35,13 @@ internal static void UpdateTemplatedParent(DependencyObject? @do, DependencyObje // before any binding is applied, so there is no need to force update. if (reapplyTemplateBindings && @do is IDependencyObjectStoreProvider dosProvider) { - dosProvider.Store.ApplyTemplateBindings(); + (store ?? dosProvider.Store).ApplyTemplateBindings(); } } + else if (@do is IDependencyObjectStoreProvider dosProvider) + { + (store ?? dosProvider.Store).SetTemplatedParent2(tp); + } } internal static MaterializingTemplateInfo? GetCurrentTemplate()