Skip to content

Add sample/tests for FrameworkElementExtensions.Ancestor/Type #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions components/Extensions/samples/FrameworkElementAncestorSample.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Page x:Class="ExtensionsExperiment.Samples.FrameworkElementAncestorSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:ExtensionsExperiment.Samples"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">

<StackPanel Padding="16"
Spacing="{x:Bind ValueSlider.Value, Mode=OneWay}">
<Slider x:Name="ValueSlider"
Maximum="16"
Minimum="4"
StepFrequency="4"
Value="8" />
<TextBlock Text="This is some text in a StackPanel with Spacing:" />
<TextBlock ui:FrameworkElementExtensions.AncestorType="StackPanel"
Text="{Binding (ui:FrameworkElementExtensions.Ancestor).Spacing, RelativeSource={RelativeSource Self}}" />
</StackPanel>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace ExtensionsExperiment.Samples;

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
[ToolkitSample(id: nameof(FrameworkElementAncestorSample), nameof(FrameworkElementAncestorSample), description: $"A sample for showing how to use the FrameworkElementExtensions.Ancestor attached property.")]
public sealed partial class FrameworkElementAncestorSample : Page
{
public FrameworkElementAncestorSample()
{
this.InitializeComponent();
}
}
8 changes: 3 additions & 5 deletions components/Extensions/samples/FrameworkElementExtensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,9 @@ The `AncestorType` attached property will walk the visual tree from the attached

Here is an example of how this can be used:

```xaml
<Button
ui:FrameworkElementExtensions.AncestorType="Grid"
Visibility="{Binding (ui:FrameworkElementExtensions.Ancestor).Visibility,RelativeSource={RelativeSource Self}}"/>
```
> [!SAMPLE FrameworkElementAncestorSample]

While this example is trivial, it shows you how to properly setup and bind to the parent element's property, in this case `Spacing`.

## Cursor

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace CommunityToolkit.WinUI;

// TODO: Note: Windows App SDK doesn't support this (need to use Protected Cursor), but we still use this extension for Sizer controls.
// For now rather than not porting, we'll just exclude on Windows App SDK platforms. Fenced other blocks below and support both equivelent types, but don't have a general way to set cursor on window yet in PointerEntered/Exited. If in end, FrameworkElement gets non-protected Cursor property like WPF, then this extension also isn't needed.
// For now rather than not porting, we'll just exclude on Windows App SDK platforms. Fenced other blocks below and support both equivalent types, but don't have a general way to set cursor on window yet in PointerEntered/Exited. If in end, FrameworkElement gets non-protected Cursor property like WPF, then this extension also isn't needed.
// See https://github.com/microsoft/microsoft-ui-xaml/issues/4834
#if !WINAPPSDK

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;

namespace CommunityToolkit.WinUI;

/// <inheritdoc cref="FrameworkElementExtensions"/>
Expand Down Expand Up @@ -39,15 +37,15 @@ public static void SetAncestor(DependencyObject obj, object value)
/// Gets the Type of Ancestor to look for from this element.
/// </summary>
/// <returns>Type of Ancestor to look for from this element</returns>
public static Type GetAncestorType(DependencyObject obj)
public static Type GetAncestorType(FrameworkElement obj)
{
return (Type)obj.GetValue(AncestorTypeProperty);
}

/// <summary>
/// Sets the <see cref="Type"/> to look for from this element and place in the <see cref="AncestorProperty"/>.
/// </summary>
public static void SetAncestorType(DependencyObject obj, Type value)
public static void SetAncestorType(FrameworkElement obj, Type value)
{
obj.SetValue(AncestorTypeProperty, value);
}
Expand Down Expand Up @@ -80,6 +78,19 @@ private static void FrameworkElement_Loaded(object sender, RoutedEventArgs e)
if (sender is FrameworkElement fe)
{
SetAncestor(fe, fe.FindAscendant(GetAncestorType(fe))!);

fe.Unloaded -= FrameworkElement_Unloaded;
fe.Unloaded += FrameworkElement_Unloaded;
}
}

private static void FrameworkElement_Unloaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement fe)
{
fe.Unloaded -= FrameworkElement_Unloaded;

SetAncestor(fe, null!);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Page x:Class="ExtensionsExperiment.Tests.BitmapIconExtensionTestPage"
<Page x:Class="ExtensionsComponent.Tests.BitmapIconExtensionTestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace ExtensionsExperiment.Tests;
namespace ExtensionsComponent.Tests;

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
Expand Down
2 changes: 0 additions & 2 deletions components/Extensions/tests/BitmapIconExtensionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

using CommunityToolkit.Tests;
using CommunityToolkit.Tooling.TestGen;
using CommunityToolkit.WinUI;
using ExtensionsExperiment.Tests;

namespace ExtensionsComponent.Tests;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.Tests;
using CommunityToolkit.Tooling.TestGen;

namespace ExtensionsComponent.Tests;

[TestClass]
public partial class FrameworkElementExtensionsTests : VisualUITestBase
{
[TestCategory("FrameworkElementExtension")]
[UIThreadTestMethod]
public void FrameworkElementExtension_RelativeAncestor_InDataTemplate(FrameworkElementRelativeAncestorDataTemplateTestPage page)
{
var list = page.FindDescendant<ListView>();

Assert.IsNotNull(list, "Couldn't find listview");

int count = 0;
foreach (var item in list.FindDescendants().OfType<TextBlock>())
{
count++;
Assert.AreEqual("Hello", item.Text, "Text didn't match binding of ancestor tag property");
}

Assert.AreEqual(3, count, "Didn't find three textblocks");
}

[TestCategory("FrameworkElementExtension")]
[UIThreadTestMethod]
public async Task FrameworkElementExtension_RelativeAncestor_FreeParentBaseline(FrameworkElementRelativeAncestorDataTemplateTestPage page)
{
var text = page.FindDescendant<TextBox>();

Assert.IsNotNull(text, "Couldn't find TextBox");

// Grab a hold of a weak reference for TextBox so we can detect when it unloads.
WeakReference textRef = new(text);
text = null;

var parent = page.FindDescendant<Grid>();

Assert.IsNotNull(parent, "Couldn't find parent Grid");

// Remove all the children from the grid to simulate it unloading.
VisualTreeHelper.DisconnectChildrenRecursive(parent);
parent.Children.Clear();
parent = null;

// Wait for the Visual Tree to perform removals and clean-up
await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });

// Wait for the .NET Garbage Collector to clean up references
GC.Collect();
GC.WaitForPendingFinalizers();

Assert.IsFalse(textRef.IsAlive, "TextBox is still alive...");
}

[TestCategory("FrameworkElementExtension")]
[UIThreadTestMethod]
public async Task FrameworkElementExtension_RelativeAncestor_FreeParent(FrameworkElementRelativeAncestorDataTemplateTestPage page)
{
var list = page.FindDescendant<ListView>();

Assert.IsNotNull(list, "Couldn't find listview");

// Grab a hold of a weak reference for ListView so we can detect when it unloads.
WeakReference listRef = new(list);
list = null;

// Remove all the children from the grid to simulate it unloading.
VisualTreeHelper.DisconnectChildrenRecursive(page);
page.Content = null;

// Wait for the Visual Tree to perform removals and clean-up
await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });

// Wait for the .NET Garbage Collector to clean up references
GC.Collect();
GC.WaitForPendingFinalizers();

Assert.IsFalse(listRef.IsAlive, "ListView is still alive...");
}

[TestCategory("FrameworkElementExtension")]
[UIThreadTestMethod]
public async Task FrameworkElementExtension_RelativeAncestor_FreePageNavigation()
{
TaskCompletionSource<bool?> taskCompletionSource = new();
var frame = new Frame();
frame.Navigated += OnNavigated;

await LoadTestContentAsync(frame);

// Navigate to the new page.
frame.Navigate(typeof(FrameworkElementRelativeAncestorDataTemplateTestPage), null, new SuppressNavigationTransitionInfo());

async void OnNavigated(object sender, NavigationEventArgs e)
{
frame.Navigated -= OnNavigated;

// Wait for first Render pass
await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });

taskCompletionSource.SetResult(true);
}

// Wait for frame to navigate/load
var result = await taskCompletionSource.Task;
Assert.IsTrue(result, "Navigation didn't complete");

// Find the ListView we want to track

var list = frame.FindDescendant<ListView>();

Assert.IsNotNull(list, "Couldn't find listview");

// Grab a hold of a weak reference for ListView so we can detect when it unloads.
WeakReference listRef = new(list);
list = null;

TaskCompletionSource<bool?> taskCompletionSource2 = new();
frame.Navigated += OnNavigated2;

async void OnNavigated2(object sender, NavigationEventArgs e)
{
frame.Navigated -= OnNavigated2;

// Wait for first Render pass
await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });

taskCompletionSource2.SetResult(true);
}

// Navigate to any other page to unload our other one
frame.Navigate(typeof(BitmapIconExtensionTestPage), null, new SuppressNavigationTransitionInfo());

// Wait for navigation to complete
result = await taskCompletionSource2.Task;
Assert.IsTrue(result, "Navigation didn't complete 2");

// Wait for the Visual Tree to perform removals and clean-up
await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });

// Wait for the .NET Garbage Collector to clean up references
GC.Collect();
GC.WaitForPendingFinalizers();

Assert.IsFalse(listRef.IsAlive, "ListView is still alive...");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Page x:Class="ExtensionsComponent.Tests.FrameworkElementRelativeAncestorDataTemplateTestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">

<Grid>
<ListView Tag="Hello">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock ui:FrameworkElementExtensions.AncestorType="ListView"
Text="{Binding (ui:FrameworkElementExtensions.Ancestor).Tag, RelativeSource={RelativeSource Self}}" />
</DataTemplate>
</ListView.ItemTemplate>
<x:String>1</x:String>
<x:String>2</x:String>
<x:String>3</x:String>
</ListView>
<!-- Another child to test clearing GC -->
<TextBox />
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace ExtensionsComponent.Tests;

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class FrameworkElementRelativeAncestorDataTemplateTestPage : Page
{
public FrameworkElementRelativeAncestorDataTemplateTestPage()
{
this.InitializeComponent();
}
}
8 changes: 8 additions & 0 deletions components/Extensions/tests/Extensions.Tests.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,20 @@
<Compile Include="$(MSBuildThisFileDirectory)BitmapIconExtensionTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DispatcherQueueExtensionTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DispatcherQueueTimerExtensionTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Element\FrameworkElementExtensionsTests.RelativeAncestor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Element\FrameworkElementRelativeAncestorDataTemplateTestPage.xaml.cs">
<DependentUpon>FrameworkElementRelativeAncestorDataTemplateTestPage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ObjectWithNullableBoolProperty.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="$(MSBuildThisFileDirectory)BitmapIconExtensionTestPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Element\FrameworkElementRelativeAncestorDataTemplateTestPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project>