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 527bba3
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -369,4 +370,10 @@ public void SuspendBindings() =>
test.TestState.AdditionalReferences.AddRange(BuildUnoReferences(isAndroid: false));
await test.RunAsync();
}

[TestMethod]
public void TestAsdAsd()
{

}
}
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");
var grid = button.GetTemplateRoot() ?? throw new Exception("template root not found");

Check warning on line 300 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#L300

'System.Exception' should not be thrown by user code.
var collection = Interaction.GetBehaviors(grid) ?? throw new Exception("behavior collection not found");

Check warning on line 301 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#L301

'System.Exception' should not be thrown by user code.

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
Loading

0 comments on commit 527bba3

Please sign in to comment.