Skip to content

Commit d0c4992

Browse files
committed
Added FindDescendant overloads with custom search type
1 parent ba4311b commit d0c4992

File tree

1 file changed

+160
-18
lines changed

1 file changed

+160
-18
lines changed

Microsoft.Toolkit.Uwp.UI/Extensions/DependencyObjectExtensions.cs

Lines changed: 160 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System;
66
using System.Collections.Generic;
7+
using Microsoft.Toolkit.Uwp.UI.Helpers.Internals;
78
using Microsoft.Toolkit.Uwp.UI.Predicates;
89
using Windows.UI.Xaml;
910
using Windows.UI.Xaml.Media;
@@ -28,7 +29,22 @@ public static class DependencyObjectExtensions
2829
{
2930
PredicateByName predicateByName = new(name, comparisonType);
3031

31-
return FindDescendant<FrameworkElement, PredicateByName>(element, ref predicateByName);
32+
return FindDescendant<FrameworkElement, PredicateByName>(element, ref predicateByName, SearchType.DepthFirst);
33+
}
34+
35+
/// <summary>
36+
/// Find the first descendant of type <see cref="FrameworkElement"/> with a given name.
37+
/// </summary>
38+
/// <param name="element">The root element.</param>
39+
/// <param name="name">The name of the element to look for.</param>
40+
/// <param name="comparisonType">The comparison type to use to match <paramref name="name"/>.</param>
41+
/// <param name="searchType">The search type to use to explore the visual tree.</param>
42+
/// <returns>The descendant that was found, or <see langword="null"/>.</returns>
43+
public static FrameworkElement? FindDescendant(this DependencyObject element, string name, StringComparison comparisonType, SearchType searchType)
44+
{
45+
PredicateByName predicateByName = new(name, comparisonType);
46+
47+
return FindDescendant<FrameworkElement, PredicateByName>(element, ref predicateByName, searchType);
3248
}
3349

3450
/// <summary>
@@ -42,7 +58,22 @@ public static class DependencyObjectExtensions
4258
{
4359
PredicateByAny<T> predicateByAny = default;
4460

45-
return FindDescendant<T, PredicateByAny<T>>(element, ref predicateByAny);
61+
return FindDescendant<T, PredicateByAny<T>>(element, ref predicateByAny, SearchType.DepthFirst);
62+
}
63+
64+
/// <summary>
65+
/// Find the first descendant element of a given type.
66+
/// </summary>
67+
/// <typeparam name="T">The type of elements to match.</typeparam>
68+
/// <param name="element">The root element.</param>
69+
/// <param name="searchType">The search type to use to explore the visual tree.</param>
70+
/// <returns>The descendant that was found, or <see langword="null"/>.</returns>
71+
public static T? FindDescendant<T>(this DependencyObject element, SearchType searchType)
72+
where T : notnull, DependencyObject
73+
{
74+
PredicateByAny<T> predicateByAny = default;
75+
76+
return FindDescendant<T, PredicateByAny<T>>(element, ref predicateByAny, searchType);
4677
}
4778

4879
/// <summary>
@@ -55,7 +86,21 @@ public static class DependencyObjectExtensions
5586
{
5687
PredicateByType predicateByType = new(type);
5788

58-
return FindDescendant<DependencyObject, PredicateByType>(element, ref predicateByType);
89+
return FindDescendant<DependencyObject, PredicateByType>(element, ref predicateByType, SearchType.DepthFirst);
90+
}
91+
92+
/// <summary>
93+
/// Find the first descendant element of a given type.
94+
/// </summary>
95+
/// <param name="element">The root element.</param>
96+
/// <param name="type">The type of element to match.</param>
97+
/// <param name="searchType">The search type to use to explore the visual tree.</param>
98+
/// <returns>The descendant that was found, or <see langword="null"/>.</returns>
99+
public static DependencyObject? FindDescendant(this DependencyObject element, Type type, SearchType searchType)
100+
{
101+
PredicateByType predicateByType = new(type);
102+
103+
return FindDescendant<DependencyObject, PredicateByType>(element, ref predicateByType, searchType);
59104
}
60105

61106
/// <summary>
@@ -70,7 +115,23 @@ public static class DependencyObjectExtensions
70115
{
71116
PredicateByFunc<T> predicateByFunc = new(predicate);
72117

73-
return FindDescendant<T, PredicateByFunc<T>>(element, ref predicateByFunc);
118+
return FindDescendant<T, PredicateByFunc<T>>(element, ref predicateByFunc, SearchType.DepthFirst);
119+
}
120+
121+
/// <summary>
122+
/// Find the first descendant element matching a given predicate.
123+
/// </summary>
124+
/// <typeparam name="T">The type of elements to match.</typeparam>
125+
/// <param name="element">The root element.</param>
126+
/// <param name="predicate">The predicatee to use to match the descendant nodes.</param>
127+
/// <param name="searchType">The search type to use to explore the visual tree.</param>
128+
/// <returns>The descendant that was found, or <see langword="null"/>.</returns>
129+
public static T? FindDescendant<T>(this DependencyObject element, Func<T, bool> predicate, SearchType searchType)
130+
where T : notnull, DependencyObject
131+
{
132+
PredicateByFunc<T> predicateByFunc = new(predicate);
133+
134+
return FindDescendant<T, PredicateByFunc<T>>(element, ref predicateByFunc, searchType);
74135
}
75136

76137
/// <summary>
@@ -87,41 +148,122 @@ public static class DependencyObjectExtensions
87148
{
88149
PredicateByFunc<T, TState> predicateByFunc = new(state, predicate);
89150

90-
return FindDescendant<T, PredicateByFunc<T, TState>>(element, ref predicateByFunc);
151+
return FindDescendant<T, PredicateByFunc<T, TState>>(element, ref predicateByFunc, SearchType.DepthFirst);
91152
}
92153

93154
/// <summary>
94-
/// Find the first descendant element matching a given predicate, using a depth-first search.
155+
/// Find the first descendant element matching a given predicate.
95156
/// </summary>
96157
/// <typeparam name="T">The type of elements to match.</typeparam>
97-
/// <typeparam name="TPredicate">The type of predicate in use.</typeparam>
158+
/// <typeparam name="TState">The type of state to use when matching nodes.</typeparam>
98159
/// <param name="element">The root element.</param>
160+
/// <param name="state">The state to give as input to <paramref name="predicate"/>.</param>
99161
/// <param name="predicate">The predicatee to use to match the descendant nodes.</param>
162+
/// <param name="searchType">The search type to use to explore the visual tree.</param>
100163
/// <returns>The descendant that was found, or <see langword="null"/>.</returns>
101-
private static T? FindDescendant<T, TPredicate>(this DependencyObject element, ref TPredicate predicate)
164+
public static T? FindDescendant<T, TState>(this DependencyObject element, TState state, Func<T, TState, bool> predicate, SearchType searchType)
102165
where T : notnull, DependencyObject
103-
where TPredicate : struct, IPredicate<T>
104166
{
105-
int childrenCount = VisualTreeHelper.GetChildrenCount(element);
167+
PredicateByFunc<T, TState> predicateByFunc = new(state, predicate);
106168

107-
for (var i = 0; i < childrenCount; i++)
169+
return FindDescendant<T, PredicateByFunc<T, TState>>(element, ref predicateByFunc, searchType);
170+
}
171+
172+
/// <summary>
173+
/// Find the first descendant element matching a given predicate.
174+
/// </summary>
175+
/// <typeparam name="T">The type of elements to match.</typeparam>
176+
/// <typeparam name="TPredicate">The type of predicate in use.</typeparam>
177+
/// <param name="element">The root element.</param>
178+
/// <param name="predicate">The predicate to use to match the descendant nodes.</param>
179+
/// <param name="searchType">The search type to use to explore the visual tree.</param>
180+
/// <returns>The descendant that was found, or <see langword="null"/>.</returns>
181+
private static T? FindDescendant<T, TPredicate>(this DependencyObject element, ref TPredicate predicate, SearchType searchType)
182+
where T : notnull, DependencyObject
183+
where TPredicate : struct, IPredicate<T>
184+
{
185+
// Depth-first search, with recursive implementation
186+
static T? FindDescendantWithDepthFirstSearch(DependencyObject element, ref TPredicate predicate)
108187
{
109-
DependencyObject child = VisualTreeHelper.GetChild(element, i);
188+
int childrenCount = VisualTreeHelper.GetChildrenCount(element);
110189

111-
if (child is T result && predicate.Match(result))
190+
for (int i = 0; i < childrenCount; i++)
112191
{
113-
return result;
192+
DependencyObject child = VisualTreeHelper.GetChild(element, i);
193+
194+
if (child is T result && predicate.Match(result))
195+
{
196+
return result;
197+
}
198+
199+
T? descendant = FindDescendantWithDepthFirstSearch(child, ref predicate);
200+
201+
if (descendant is not null)
202+
{
203+
return descendant;
204+
}
114205
}
115206

116-
T? descendant = FindDescendant<T, TPredicate>(child, ref predicate);
207+
return null;
208+
}
209+
210+
// Breadth-first search, with iterative implementation and pooled local stack
211+
static T? FindDescendantWithBreadthFirstSearch(DependencyObject element, ref TPredicate predicate)
212+
{
213+
// We're using a pooled buffer writer to amortize allocations for the temporary collection of children
214+
// to visit for each level. The underlying array is deliberately just of type object and not DependencyObject
215+
// to reduce the number of generic instantiations and allow the rented arrays to be reused more.
216+
using ArrayPoolBufferWriter<object> bufferWriter = ArrayPoolBufferWriter<object>.Create();
117217

118-
if (descendant is not null)
218+
int childrenCount = VisualTreeHelper.GetChildrenCount(element);
219+
220+
// Add the top level children
221+
for (int i = 0; i < childrenCount; i++)
119222
{
120-
return descendant;
223+
DependencyObject child = VisualTreeHelper.GetChild(element, i);
224+
225+
if (child is T result && predicate.Match(result))
226+
{
227+
return result;
228+
}
229+
230+
bufferWriter.Add(child);
121231
}
232+
233+
// Explore each depth level
234+
for (int i = 0; i < bufferWriter.Count; i++)
235+
{
236+
DependencyObject parent = (DependencyObject)bufferWriter[i];
237+
238+
childrenCount = VisualTreeHelper.GetChildrenCount(parent);
239+
240+
for (int j = 0; j < childrenCount; j++)
241+
{
242+
DependencyObject child = VisualTreeHelper.GetChild(parent, j);
243+
244+
if (child is T result && predicate.Match(result))
245+
{
246+
return result;
247+
}
248+
249+
bufferWriter.Add(child);
250+
}
251+
}
252+
253+
return null;
122254
}
123255

124-
return null;
256+
static T? ThrowArgumentOutOfRangeExceptionForInvalidSearchType()
257+
{
258+
throw new ArgumentOutOfRangeException(nameof(searchType), "The input search type is not valid");
259+
}
260+
261+
return searchType switch
262+
{
263+
SearchType.DepthFirst => FindDescendantWithDepthFirstSearch(element, ref predicate),
264+
SearchType.BreadthFirst => FindDescendantWithBreadthFirstSearch(element, ref predicate),
265+
_ => ThrowArgumentOutOfRangeExceptionForInvalidSearchType()
266+
};
125267
}
126268

127269
/// <summary>

0 commit comments

Comments
 (0)