diff --git a/Microsoft.Toolkit.Uwp.UI/Enums/SearchType.cs b/Microsoft.Toolkit.Uwp.UI/Enums/SearchType.cs
new file mode 100644
index 00000000000..f17d680e858
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI/Enums/SearchType.cs
@@ -0,0 +1,24 @@
+// 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 Microsoft.Toolkit.Uwp.UI
+{
+ ///
+ /// Indicates a type of search for elements in a visual or logical tree.
+ ///
+ public enum SearchType
+ {
+ ///
+ /// Depth-first search, where each branch is recursively explored until the end before moving to the next one.
+ ///
+ DepthFirst,
+
+ ///
+ /// Breadth-first search, where each depthwise level is completely explored before moving to the next one.
+ /// This is particularly useful if the target element to find is known to not be too distant from the starting
+ /// point and the whole visual/logical tree from the root is large enough, as it can reduce the traversal time.
+ ///
+ BreadthFirst
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/DependencyObjectExtensions.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/DependencyObjectExtensions.cs
index 3caf8c97527..0fccee4bdfa 100644
--- a/Microsoft.Toolkit.Uwp.UI/Extensions/DependencyObjectExtensions.cs
+++ b/Microsoft.Toolkit.Uwp.UI/Extensions/DependencyObjectExtensions.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using Microsoft.Toolkit.Uwp.UI.Helpers.Internals;
using Microsoft.Toolkit.Uwp.UI.Predicates;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
@@ -28,7 +29,22 @@ public static class DependencyObjectExtensions
{
PredicateByName predicateByName = new(name, comparisonType);
- return FindDescendant(element, ref predicateByName);
+ return FindDescendant(element, ref predicateByName, SearchType.DepthFirst);
+ }
+
+ ///
+ /// Find the first descendant of type with a given name.
+ ///
+ /// The root element.
+ /// The name of the element to look for.
+ /// The comparison type to use to match .
+ /// The search type to use to explore the visual tree.
+ /// The descendant that was found, or .
+ public static FrameworkElement? FindDescendant(this DependencyObject element, string name, StringComparison comparisonType, SearchType searchType)
+ {
+ PredicateByName predicateByName = new(name, comparisonType);
+
+ return FindDescendant(element, ref predicateByName, searchType);
}
///
@@ -42,7 +58,22 @@ public static class DependencyObjectExtensions
{
PredicateByAny predicateByAny = default;
- return FindDescendant>(element, ref predicateByAny);
+ return FindDescendant>(element, ref predicateByAny, SearchType.DepthFirst);
+ }
+
+ ///
+ /// Find the first descendant element of a given type.
+ ///
+ /// The type of elements to match.
+ /// The root element.
+ /// The search type to use to explore the visual tree.
+ /// The descendant that was found, or .
+ public static T? FindDescendant(this DependencyObject element, SearchType searchType)
+ where T : notnull, DependencyObject
+ {
+ PredicateByAny predicateByAny = default;
+
+ return FindDescendant>(element, ref predicateByAny, searchType);
}
///
@@ -55,7 +86,21 @@ public static class DependencyObjectExtensions
{
PredicateByType predicateByType = new(type);
- return FindDescendant(element, ref predicateByType);
+ return FindDescendant(element, ref predicateByType, SearchType.DepthFirst);
+ }
+
+ ///
+ /// Find the first descendant element of a given type.
+ ///
+ /// The root element.
+ /// The type of element to match.
+ /// The search type to use to explore the visual tree.
+ /// The descendant that was found, or .
+ public static DependencyObject? FindDescendant(this DependencyObject element, Type type, SearchType searchType)
+ {
+ PredicateByType predicateByType = new(type);
+
+ return FindDescendant(element, ref predicateByType, searchType);
}
///
@@ -63,14 +108,30 @@ public static class DependencyObjectExtensions
///
/// The type of elements to match.
/// The root element.
- /// The predicatee to use to match the descendant nodes.
+ /// The predicate to use to match the descendant nodes.
/// The descendant that was found, or .
public static T? FindDescendant(this DependencyObject element, Func predicate)
where T : notnull, DependencyObject
{
PredicateByFunc predicateByFunc = new(predicate);
- return FindDescendant>(element, ref predicateByFunc);
+ return FindDescendant>(element, ref predicateByFunc, SearchType.DepthFirst);
+ }
+
+ ///
+ /// Find the first descendant element matching a given predicate.
+ ///
+ /// The type of elements to match.
+ /// The root element.
+ /// The predicate to use to match the descendant nodes.
+ /// The search type to use to explore the visual tree.
+ /// The descendant that was found, or .
+ public static T? FindDescendant(this DependencyObject element, Func predicate, SearchType searchType)
+ where T : notnull, DependencyObject
+ {
+ PredicateByFunc predicateByFunc = new(predicate);
+
+ return FindDescendant>(element, ref predicateByFunc, searchType);
}
///
@@ -80,48 +141,129 @@ public static class DependencyObjectExtensions
/// The type of state to use when matching nodes.
/// The root element.
/// The state to give as input to .
- /// The predicatee to use to match the descendant nodes.
+ /// The predicate to use to match the descendant nodes.
/// The descendant that was found, or .
public static T? FindDescendant(this DependencyObject element, TState state, Func predicate)
where T : notnull, DependencyObject
{
PredicateByFunc predicateByFunc = new(state, predicate);
- return FindDescendant>(element, ref predicateByFunc);
+ return FindDescendant>(element, ref predicateByFunc, SearchType.DepthFirst);
}
///
- /// Find the first descendant element matching a given predicate, using a depth-first search.
+ /// Find the first descendant element matching a given predicate.
+ ///
+ /// The type of elements to match.
+ /// The type of state to use when matching nodes.
+ /// The root element.
+ /// The state to give as input to .
+ /// The predicate to use to match the descendant nodes.
+ /// The search type to use to explore the visual tree.
+ /// The descendant that was found, or .
+ public static T? FindDescendant(this DependencyObject element, TState state, Func predicate, SearchType searchType)
+ where T : notnull, DependencyObject
+ {
+ PredicateByFunc predicateByFunc = new(state, predicate);
+
+ return FindDescendant>(element, ref predicateByFunc, searchType);
+ }
+
+ ///
+ /// Find the first descendant element matching a given predicate.
///
/// The type of elements to match.
/// The type of predicate in use.
/// The root element.
- /// The predicatee to use to match the descendant nodes.
+ /// The predicate to use to match the descendant nodes.
+ /// The search type to use to explore the visual tree.
/// The descendant that was found, or .
- private static T? FindDescendant(this DependencyObject element, ref TPredicate predicate)
+ private static T? FindDescendant(this DependencyObject element, ref TPredicate predicate, SearchType searchType)
where T : notnull, DependencyObject
where TPredicate : struct, IPredicate
{
- int childrenCount = VisualTreeHelper.GetChildrenCount(element);
-
- for (var i = 0; i < childrenCount; i++)
+ // Depth-first search, with recursive implementation
+ static T? FindDescendantWithDepthFirstSearch(DependencyObject element, ref TPredicate predicate)
{
- DependencyObject child = VisualTreeHelper.GetChild(element, i);
+ int childrenCount = VisualTreeHelper.GetChildrenCount(element);
- if (child is T result && predicate.Match(result))
+ for (int i = 0; i < childrenCount; i++)
{
- return result;
+ DependencyObject child = VisualTreeHelper.GetChild(element, i);
+
+ if (child is T result && predicate.Match(result))
+ {
+ return result;
+ }
+
+ T? descendant = FindDescendantWithDepthFirstSearch(child, ref predicate);
+
+ if (descendant is not null)
+ {
+ return descendant;
+ }
}
- T? descendant = FindDescendant(child, ref predicate);
+ return null;
+ }
+
+ // Breadth-first search, with iterative implementation and pooled local stack
+ static T? FindDescendantWithBreadthFirstSearch(DependencyObject element, ref TPredicate predicate)
+ {
+ // We're using a pooled buffer writer to amortize allocations for the temporary collection of children
+ // to visit for each level. The underlying array is deliberately just of type object and not DependencyObject
+ // to reduce the number of generic instantiations and allow the rented arrays to be reused more.
+ using ArrayPoolBufferWriter
/// The root element.
@@ -460,5 +900,73 @@ public static IEnumerable FindAscendants(this DependencyObject
element = parent;
}
}
+
+ ///
+ /// Find all ascendant elements of the specified element (or self). This method can be chained
+ /// with LINQ calls to add additional filters or projections on top of the returned results.
+ ///
+ /// This method is meant to provide extra flexibility in specific scenarios and it should not
+ /// be used when only the first item is being looked for. In those cases, use one of the
+ /// available overloads instead,
+ /// which will offer a more compact syntax as well as better performance in those cases.
+ ///
+ ///
+ /// The root element.
+ /// All the descendant instance from .
+ public static IEnumerable FindAscendantsOrSelf(this DependencyObject element)
+ {
+ yield return element;
+
+ while (true)
+ {
+ DependencyObject? parent = VisualTreeHelper.GetParent(element);
+
+ if (parent is null)
+ {
+ yield break;
+ }
+
+ yield return parent;
+
+ element = parent;
+ }
+ }
+
+ ///
+ /// Checks whether or not a given instance is a descendant of another one.
+ ///
+ /// The input child element.
+ /// The element to look for in the ascendants hierarchy for .
+ /// Whether or not is a descendant of .
+ public static bool IsDescendantOf(this DependencyObject child, DependencyObject element)
+ {
+ while (true)
+ {
+ DependencyObject? parent = VisualTreeHelper.GetParent(child);
+
+ if (parent is null)
+ {
+ return false;
+ }
+
+ if (parent == element)
+ {
+ return true;
+ }
+
+ child = parent;
+ }
+ }
+
+ ///
+ /// Checks whether or not a given instance is an ascendant of another one.
+ ///
+ /// The input parent element.
+ /// The element to look for in the descendants hierarchy for .
+ /// Whether or not is an ascendant of .
+ public static bool IsAscendantOf(this DependencyObject parent, DependencyObject element)
+ {
+ return IsDescendantOf(element, parent);
+ }
}
}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.LogicalTree.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.LogicalTree.cs
index 71a766e1f77..998219d449c 100644
--- a/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.LogicalTree.cs
+++ b/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.LogicalTree.cs
@@ -63,7 +63,7 @@ public static partial class FrameworkElementExtensions
///
/// The type of elements to match.
/// The root element.
- /// The predicatee to use to match the child nodes.
+ /// The predicate to use to match the child nodes.
/// The child that was found, or .
public static T? FindChild(this FrameworkElement element, Func predicate)
where T : notnull, FrameworkElement
@@ -80,7 +80,7 @@ public static partial class FrameworkElementExtensions
/// The type of state to use when matching nodes.
/// The root element.
/// The state to give as input to .
- /// The predicatee to use to match the child nodes.
+ /// The predicate to use to match the child nodes.
/// The child that was found, or .
public static T? FindChild(this FrameworkElement element, TState state, Func predicate)
where T : notnull, FrameworkElement
@@ -96,7 +96,7 @@ public static partial class FrameworkElementExtensions
/// The type of elements to match.
/// The type of predicate in use.
/// The root element.
- /// The predicatee to use to match the child nodes.
+ /// The predicate to use to match the child nodes.
/// The child that was found, or .
private static T? FindChild(this FrameworkElement element, ref TPredicate predicate)
where T : notnull, FrameworkElement
@@ -296,7 +296,7 @@ public static partial class FrameworkElementExtensions
///
/// The type of elements to match.
/// The root element.
- /// The predicatee to use to match the child nodes.
+ /// The predicate to use to match the child nodes.
/// The child (or self) that was found, or .
public static T? FindChildOrSelf(this FrameworkElement element, Func predicate)
where T : notnull, FrameworkElement
@@ -316,7 +316,7 @@ public static partial class FrameworkElementExtensions
/// The type of state to use when matching nodes.
/// The root element.
/// The state to give as input to .
- /// The predicatee to use to match the child nodes.
+ /// The predicate to use to match the child nodes.
/// The child (or self) that was found, or .
public static T? FindChildOrSelf(this FrameworkElement element, TState state, Func predicate)
where T : notnull, FrameworkElement
@@ -490,7 +490,7 @@ public static IEnumerable FindChildren(this FrameworkElement e
///
/// The type of elements to match.
/// The starting element.
- /// The predicatee to use to match the parent nodes.
+ /// The predicate to use to match the parent nodes.
/// The parent that was found, or .
public static T? FindParent(this FrameworkElement element, Func predicate)
where T : notnull, FrameworkElement
@@ -507,7 +507,7 @@ public static IEnumerable FindChildren(this FrameworkElement e
/// The type of state to use when matching nodes.
/// The starting element.
/// The state to give as input to .
- /// The predicatee to use to match the parent nodes.
+ /// The predicate to use to match the parent nodes.
/// The parent that was found, or .
public static T? FindParent(this FrameworkElement element, TState state, Func predicate)
where T : notnull, FrameworkElement
@@ -523,7 +523,7 @@ public static IEnumerable FindChildren(this FrameworkElement e
/// The type of elements to match.
/// The type of predicate in use.
/// The starting element.
- /// The predicatee to use to match the parent nodes.
+ /// The predicate to use to match the parent nodes.
/// The parent that was found, or .
private static T? FindParent(this FrameworkElement element, ref TPredicate predicate)
where T : notnull, FrameworkElement
@@ -600,7 +600,7 @@ public static IEnumerable FindChildren(this FrameworkElement e
///
/// The type of elements to match.
/// The starting element.
- /// The predicatee to use to match the parent nodes.
+ /// The predicate to use to match the parent nodes.
/// The parent (or self) that was found, or .
public static T? FindParentOrSelf(this FrameworkElement element, Func predicate)
where T : notnull, FrameworkElement
@@ -620,7 +620,7 @@ public static IEnumerable FindChildren(this FrameworkElement e
/// The type of state to use when matching nodes.
/// The starting element.
/// The state to give as input to .
- /// The predicatee to use to match the parent nodes.
+ /// The predicate to use to match the parent nodes.
/// The parent (or self) that was found, or .
public static T? FindParentOrSelf(this FrameworkElement element, TState state, Func predicate)
where T : notnull, FrameworkElement
diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/Predicates/PredicateByFunc{T,TState}.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/Predicates/PredicateByFunc{T,TState}.cs
index d327441568b..0e04f1531d0 100644
--- a/Microsoft.Toolkit.Uwp.UI/Extensions/Predicates/PredicateByFunc{T,TState}.cs
+++ b/Microsoft.Toolkit.Uwp.UI/Extensions/Predicates/PredicateByFunc{T,TState}.cs
@@ -21,7 +21,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Predicates
private readonly TState state;
///
- /// The predicatee to use to match items.
+ /// The predicate to use to match items.
///
private readonly Func predicate;
@@ -29,7 +29,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Predicates
/// Initializes a new instance of the struct.
///
/// The state to give as input to .
- /// The predicatee to use to match items.
+ /// The predicate to use to match items.
public PredicateByFunc(TState state, Func predicate)
{
this.state = state;
diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/Predicates/PredicateByFunc{T}.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/Predicates/PredicateByFunc{T}.cs
index c46b4f77203..8be22ede920 100644
--- a/Microsoft.Toolkit.Uwp.UI/Extensions/Predicates/PredicateByFunc{T}.cs
+++ b/Microsoft.Toolkit.Uwp.UI/Extensions/Predicates/PredicateByFunc{T}.cs
@@ -15,14 +15,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Predicates
where T : class
{
///
- /// The predicatee to use to match items.
+ /// The predicate to use to match items.
///
private readonly Func predicate;
///
/// Initializes a new instance of the struct.
///
- /// The predicatee to use to match items.
+ /// The predicate to use to match items.
public PredicateByFunc(Func predicate)
{
this.predicate = predicate;
diff --git a/Microsoft.Toolkit.Uwp.UI/Helpers/Internals/ArrayPoolBufferWriter{T}.cs b/Microsoft.Toolkit.Uwp.UI/Helpers/Internals/ArrayPoolBufferWriter{T}.cs
new file mode 100644
index 00000000000..568e7df0dab
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI/Helpers/Internals/ArrayPoolBufferWriter{T}.cs
@@ -0,0 +1,129 @@
+// 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 System;
+using System.Buffers;
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Toolkit.Uwp.UI.Helpers.Internals
+{
+ ///
+ /// A simple buffer writer implementation using pooled arrays.
+ ///
+ /// The type of items to store in the list.
+ ///
+ /// This type is a to avoid the object allocation and to
+ /// enable the pattern-based support. We aren't worried with consumers not
+ /// using this type correctly since it's private and only accessible within the parent type.
+ ///
+ internal struct ArrayPoolBufferWriter : IDisposable
+ {
+ ///
+ /// The default buffer size to use to expand empty arrays.
+ ///
+ private const int DefaultInitialBufferSize = 128;
+
+ ///
+ /// The underlying array.
+ ///
+ private T[] array;
+
+ ///
+ /// The starting offset within .
+ ///
+ private int index;
+
+ ///
+ /// Creates a new instance of the struct.
+ ///
+ /// A new instance.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ArrayPoolBufferWriter Create()
+ {
+ return new() { array = ArrayPool.Shared.Rent(DefaultInitialBufferSize) };
+ }
+
+ ///
+ /// Gets the total number of items stored in the current instance.
+ ///
+ public int Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.array.Length;
+ }
+
+ ///
+ /// Gets the item at the specified offset into the current buffer in use.
+ ///
+ /// The index of the element to retrieve.
+ /// The item at the specified offset into the current buffer in use.
+ /// Thrown when is out of range.
+ public T this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ if ((uint)index >= this.index)
+ {
+ static void Throw() => throw new ArgumentOutOfRangeException(nameof(index));
+
+ Throw();
+ }
+
+ return this.array[index];
+ }
+ }
+
+ ///
+ /// Adds a new item to the current collection.
+ ///
+ /// The item to add.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(T item)
+ {
+ if (this.index == this.array.Length)
+ {
+ ResizeBuffer();
+ }
+
+ this.array[this.index++] = item;
+ }
+
+ ///
+ /// Resets the underlying array and the stored items.
+ ///
+ public void Reset()
+ {
+ Array.Clear(this.array, 0, this.index);
+
+ this.index = 0;
+ }
+
+ ///
+ /// Resizes when there is no space left for new items.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void ResizeBuffer()
+ {
+ T[] rent = ArrayPool.Shared.Rent(this.index << 2);
+
+ Array.Copy(this.array, 0, rent, 0, this.index);
+ Array.Clear(this.array, 0, this.index);
+
+ ArrayPool.Shared.Return(this.array);
+
+ this.array = rent;
+ }
+
+ ///
+ public void Dispose()
+ {
+ Array.Clear(this.array, 0, this.index);
+
+ ArrayPool.Shared.Return(this.array);
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/UnitTests.UWP/Extensions/Test_VisualTreeExtensions.cs b/UnitTests/UnitTests.UWP/Extensions/Test_VisualTreeExtensions.cs
index 3d59f99b0eb..725bfd9bb24 100644
--- a/UnitTests/UnitTests.UWP/Extensions/Test_VisualTreeExtensions.cs
+++ b/UnitTests/UnitTests.UWP/Extensions/Test_VisualTreeExtensions.cs
@@ -2,6 +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.
+using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp;
@@ -52,6 +53,43 @@ await App.DispatcherQueue.EnqueueAsync(async () =>
});
}
+ [TestCategory("VisualTree")]
+ [TestMethod]
+ [DataRow(SearchType.DepthFirst)]
+ [DataRow(SearchType.BreadthFirst)]
+ public async Task Test_VisualTree_FindDescendantByName_Exists(SearchType searchType)
+ {
+ await App.DispatcherQueue.EnqueueAsync(async () =>
+ {
+ var treeRoot = XamlReader.Load(@"
+
+
+
+
+
+
+
+
+
+") as Page;
+
+ // Test Setup
+ Assert.IsNotNull(treeRoot, "XAML Failed to Load");
+
+ // Initialize Visual Tree
+ await SetTestContentAsync(treeRoot);
+
+ // Main Test
+ var textBlock = treeRoot.FindDescendant("TargetElement", StringComparison.Ordinal, searchType);
+
+ Assert.IsNotNull(textBlock, "Expected to find something.");
+ Assert.IsInstanceOfType(textBlock, typeof(TextBlock), "Didn't find expected typed element.");
+ Assert.AreEqual("TargetElement", textBlock.Name, "Didn't find named element.");
+ });
+ }
+
[TestCategory("VisualTree")]
[TestMethod]
public async Task Test_VisualTree_FindDescendantByName_NotFound()
@@ -119,6 +157,42 @@ await App.DispatcherQueue.EnqueueAsync(async () =>
});
}
+ [TestCategory("VisualTree")]
+ [TestMethod]
+ [DataRow(SearchType.DepthFirst)]
+ [DataRow(SearchType.BreadthFirst)]
+ public async Task Test_VisualTree_FindDescendant_Exists(SearchType searchType)
+ {
+ await App.DispatcherQueue.EnqueueAsync(async () =>
+ {
+ var treeRoot = XamlReader.Load(@"
+
+
+
+
+
+
+
+
+
+") as Page;
+
+ // Test Setup
+ Assert.IsNotNull(treeRoot, "XAML Failed to Load");
+
+ // Initialize Visual Tree
+ await SetTestContentAsync(treeRoot);
+
+ // Main Test
+ var textBlock = treeRoot.FindDescendant(searchType);
+
+ Assert.IsNotNull(textBlock, "Expected to find something.");
+ Assert.IsInstanceOfType(textBlock, typeof(TextBlock), "Didn't find expected typed element.");
+ });
+ }
+
[TestCategory("VisualTree")]
[TestMethod]
public async Task Test_VisualTree_FindDescendant_ItemsControl_Exists()
@@ -157,6 +231,46 @@ await App.DispatcherQueue.EnqueueAsync(async () =>
});
}
+ [TestCategory("VisualTree")]
+ [TestMethod]
+ [DataRow(SearchType.DepthFirst)]
+ [DataRow(SearchType.BreadthFirst)]
+ public async Task Test_VisualTree_FindDescendant_ItemsControl_Exists(SearchType searchType)
+ {
+ await App.DispatcherQueue.EnqueueAsync(async () =>
+ {
+ var treeRoot = XamlReader.Load(@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+") as Page;
+
+ // Test Setup
+ Assert.IsNotNull(treeRoot, "XAML Failed to Load");
+
+ // Initialize Visual Tree
+ await SetTestContentAsync(treeRoot);
+
+ // Main Test
+ var textBlock = treeRoot.FindDescendant(searchType);
+
+ Assert.IsNotNull(textBlock, "Expected to find something.");
+ Assert.IsInstanceOfType(textBlock, typeof(TextBlock), "Didn't find expected typed element.");
+ });
+ }
+
[TestCategory("VisualTree")]
[TestMethod]
public async Task Test_VisualTree_FindDescendant_NotFound()
@@ -237,6 +351,55 @@ await App.DispatcherQueue.EnqueueAsync(async () =>
});
}
+ [TestCategory("VisualTree")]
+ [TestMethod]
+ [DataRow(SearchType.DepthFirst)]
+ [DataRow(SearchType.BreadthFirst)]
+ public async Task Test_VisualTree_FindDescendants_Exists(SearchType searchType)
+ {
+ await App.DispatcherQueue.EnqueueAsync(async () =>
+ {
+ var treeRoot = XamlReader.Load(@"
+
+
+
+
+
+
+
+
+
+
+
+") as Page;
+
+ // Test Setup
+ Assert.IsNotNull(treeRoot, "XAML Failed to Load");
+
+ // Initialize Visual Tree
+ await SetTestContentAsync(treeRoot);
+
+ // Main Test
+ var textBlocks = treeRoot.FindDescendants(searchType).OfType();
+
+ Assert.IsNotNull(textBlocks, "Expected to find something.");
+
+ var array = textBlocks.ToArray();
+
+ Assert.AreEqual(4, array.Length, "Expected to find 4 TextBlock elements.");
+
+ // I don't think we want to guarantee order here, so just care that we can find each one.
+ Assert.IsTrue(array.Any((tb) => tb.Name == "One"), "Couldn't find TextBlock 'One'");
+ Assert.IsTrue(array.Any((tb) => tb.Name == "Two"), "Couldn't find TextBlock 'Two'");
+ Assert.IsTrue(array.Any((tb) => tb.Name == "Three"), "Couldn't find TextBlock 'Three'");
+
+ // TextBox has one in its template!
+ Assert.IsTrue(array.Any((tb) => tb.Name == "PlaceholderTextContentPresenter"), "Couldn't find hidden TextBlock from TextBox.");
+ });
+ }
+
[TestCategory("VisualTree")]
[TestMethod]
public async Task Test_VisualTree_FindDescendants_NotFound()
@@ -276,6 +439,103 @@ await App.DispatcherQueue.EnqueueAsync(async () =>
});
}
+ [TestCategory("VisualTree")]
+ [TestMethod]
+ public async Task Test_VisualTree_FindFirstLevelDescendants_Exists()
+ {
+ await App.DispatcherQueue.EnqueueAsync(async () =>
+ {
+ var treeRoot = XamlReader.Load(@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+") as Page;
+
+ // Test Setup
+ Assert.IsNotNull(treeRoot, "XAML Failed to Load");
+
+ // Initialize Visual Tree
+ await SetTestContentAsync(treeRoot);
+
+ // Main Test
+ var rootGrid = treeRoot.FindDescendant();
+ var children = rootGrid.FindFirstLevelDescendants().ToArray();
+
+ Assert.AreEqual(5, children.Length, "Expected to find 5 children.");
+
+ Assert.IsTrue(children.Any(c => ((FrameworkElement)c).Name == "A"), "Couldn't find child 'A'");
+ Assert.IsTrue(children.Any(c => ((FrameworkElement)c).Name == "B"), "Couldn't find child 'B'");
+ Assert.IsTrue(children.Any(c => ((FrameworkElement)c).Name == "C"), "Couldn't find child 'C'");
+ Assert.IsTrue(children.Any(c => ((FrameworkElement)c).Name == "D"), "Couldn't find child 'D'");
+ Assert.IsTrue(children.Any(c => ((FrameworkElement)c).Name == "E"), "Couldn't find child 'E'");
+ });
+ }
+
+ [TestCategory("VisualTree")]
+ [TestMethod]
+ public async Task Test_VisualTree_FindFirstLevelDescendantsOrSelf_Exists()
+ {
+ await App.DispatcherQueue.EnqueueAsync(async () =>
+ {
+ var treeRoot = XamlReader.Load(@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+") as Page;
+
+ // Test Setup
+ Assert.IsNotNull(treeRoot, "XAML Failed to Load");
+
+ // Initialize Visual Tree
+ await SetTestContentAsync(treeRoot);
+
+ // Main Test
+ var rootGrid = treeRoot.FindDescendant();
+ var childrenOrSelf = rootGrid.FindFirstLevelDescendantsOrSelf().ToArray();
+
+ Assert.AreEqual(6, childrenOrSelf.Length, "Expected to find 6 children or self.");
+
+ Assert.IsTrue(childrenOrSelf.Any(c => ((FrameworkElement)c).Name == "RootGrid"), "Couldn't find self");
+ Assert.IsTrue(childrenOrSelf.Any(c => ((FrameworkElement)c).Name == "A"), "Couldn't find child 'A'");
+ Assert.IsTrue(childrenOrSelf.Any(c => ((FrameworkElement)c).Name == "B"), "Couldn't find child 'B'");
+ Assert.IsTrue(childrenOrSelf.Any(c => ((FrameworkElement)c).Name == "C"), "Couldn't find child 'C'");
+ Assert.IsTrue(childrenOrSelf.Any(c => ((FrameworkElement)c).Name == "D"), "Couldn't find child 'D'");
+ Assert.IsTrue(childrenOrSelf.Any(c => ((FrameworkElement)c).Name == "E"), "Couldn't find child 'E'");
+ });
+ }
+
[TestCategory("VisualTree")]
[TestMethod]
public async Task Test_VisualTree_FindAscendant_Exists()
@@ -520,6 +780,54 @@ await App.DispatcherQueue.EnqueueAsync(async () =>
});
}
+ [TestCategory("VisualTree")]
+ [TestMethod]
+ public async Task Test_VisualTree_IsAscendantOrDescendantOf()
+ {
+ await App.DispatcherQueue.EnqueueAsync(async () =>
+ {
+ var treeRoot = XamlReader.Load(@"
+
+
+
+
+
+
+
+
+
+") as Page;
+
+ // Test Setup
+ Assert.IsNotNull(treeRoot, "XAML Failed to Load");
+
+ // Initialize Visual Tree
+ await SetTestContentAsync(treeRoot);
+
+ // Get the root and target elements
+ var rootGrid = treeRoot.FindDescendant("RootGrid");
+ var nestedBorder = treeRoot.FindDescendant("NestedBorder");
+ var textBlock = treeRoot.FindDescendant("TargetElement");
+
+ Assert.IsTrue(rootGrid.IsAscendantOf(nestedBorder));
+ Assert.IsTrue(rootGrid.IsAscendantOf(textBlock));
+ Assert.IsFalse(rootGrid.IsDescendantOf(nestedBorder));
+ Assert.IsFalse(rootGrid.IsDescendantOf(textBlock));
+
+ Assert.IsTrue(nestedBorder.IsDescendantOf(rootGrid));
+ Assert.IsFalse(nestedBorder.IsDescendantOf(textBlock));
+ Assert.IsFalse(nestedBorder.IsAscendantOf(rootGrid));
+ Assert.IsFalse(nestedBorder.IsAscendantOf(textBlock));
+
+ Assert.IsTrue(textBlock.IsDescendantOf(rootGrid));
+ Assert.IsFalse(textBlock.IsDescendantOf(nestedBorder));
+ Assert.IsFalse(textBlock.IsAscendantOf(rootGrid));
+ Assert.IsFalse(textBlock.IsAscendantOf(nestedBorder));
+ });
+ }
+
// TODO: Add another Ascendants test where we have something like a ListView which creates a container.
}
}
\ No newline at end of file