Skip to content

Commit

Permalink
chore: add support for legacy DO
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaoy312 committed Oct 8, 2024
1 parent abdbee3 commit 2c0b7f0
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;");
Expand Down Expand Up @@ -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;
}
"""
);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Page x:Class="Uno.UI.RuntimeTests.Tests.TemplatedParent.Setup.BehaviorSetup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.UI.RuntimeTests.Tests.TemplatedParent.Setup">

<Button Tag="Asd">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid x:Name="RootGrid">
<local:Interaction.Behaviors>
<local:LegacyDOBehavior TestValue="{TemplateBinding Tag}" />
<local:NonLegacyDOBehavior TestValue="{TemplateBinding Tag}" />
</local:Interaction.Behaviors>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>

</Page>
Original file line number Diff line number Diff line change
@@ -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)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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()
Expand Down Expand Up @@ -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");

Check warning on line 299 in src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs#L299

'System.Exception' should not be thrown by user code.
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<ITemplatedParentProvider>(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)
{
Expand Down Expand Up @@ -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<string> DebugVT_TP(object x)
{
Expand Down
19 changes: 17 additions & 2 deletions src/Uno.UI/DataBinding/BindingExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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()
{
Expand Down
9 changes: 9 additions & 0 deletions src/Uno.UI/DataBinding/ITemplatedParentProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ public interface ITemplatedParentProvider

void SetTemplatedParent(DependencyObject parent);
}

[EditorBrowsable(EditorBrowsableState.Never)]
/// <summary>
/// Marker interface used to block DependencyObjectGenerator
/// from injecting <see cref="ITemplatedParentProvider"/> and its implementations.
/// </summary>
public interface INotTemplatedParentProvider
{
}
15 changes: 15 additions & 0 deletions src/Uno.UI/UI/Xaml/DependencyObjectStore.Binder.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -43,13 +48,23 @@ 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)
{
// do nothing, this only exist to keep public api the same.
}
#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)
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UI/UI/Xaml/DependencyObjectStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 8 additions & 4 deletions src/Uno.UI/UI/Xaml/TemplatedParentScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ internal static class TemplatedParentScope
/// <summary>Set the templated-parent for the dependency-object based on the currently materializing template.</summary>
/// <param name="do"></param>
/// <param name="reapplyTemplateBindings">Should be true, if not called from ctor.</param>
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)
{
Expand All @@ -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()
Expand Down

0 comments on commit 2c0b7f0

Please sign in to comment.