Description
openedon Oct 6, 2021
Describe the problem this feature would solve
The DependencyObjectExtensions
exposes APIs that offer a very convenient (and efficient) way to interact, query and traverse items in the visual tree linked to a given object. There are currently some limitations though that force developers (eg. us in the Store team) to have to implement some additional features manually. I think it would make sense to address them with a single implementation in the Toolkit that we and others could then use as well. This would reduce complexity from other codebases and offer a streamlined and consistent way to perform all these more advanced operations when working with visual trees.
The current limitations are as follows, in no particular order:
- There isn't an API to easily just get the collection of children for a given item.
VisualTreeHelper
exposes APIs to get the children count and then get children at a specified offset. We're missing a helper to just put these two together. - Especially when working with large visual trees, the default DFS search can be quite inefficient, and developers might want to optionally choose to use a different exploration mode, specifically BFS. This isn't currently available in the Toolkit, and it also has the issue of being relatively tricky to implement efficiently (as one would want to pool the temporary children buffers to avoid creating and throwing away arrays every single time a traversal is executed and the target list grows).
- The
FindAscendants
andFindDescendants
methods lack a correspondingOrSelf
version like other APIs.
Describe the solution
I propose to add the following set of new APIs to solve the issues mentioned above:
namespace Microsoft.Toolkit.Uwp.UI
{
public enum SearchType
{
DepthFirst,
BreadthFirst
}
public static class DependencyObjectExtensions
{
// Get the children
public static DependencyObject[] GetChildren(this DependencyObject element);
public static T[] GetChildren<T>(this DependencyObject element) where T : notnull, DependencyObject;
// Overloads with search type
public static FrameworkElement? FindDescendant(this DependencyObject element, string name, StringComparison comparisonType, SearchType searchType);
public static T? FindDescendant<T>(this DependencyObject element, SearchType searchType) where T : notnull, DependencyObject;
public static DependencyObject? FindDescendant(this DependencyObject element, Type type, SearchType searchType);
public static T? FindDescendant<T>(this DependencyObject element, Func<T, bool> predicate, SearchType searchType) where T : notnull, DependencyObject;
public static T? FindDescendant<T, TState>(this DependencyObject element, TState state, Func<T, TState, bool> predicate, SearchType searchType) where T : notnull, DependencyObject;
public static FrameworkElement? FindDescendantOrSelf(this DependencyObject element, string name, StringComparison comparisonType, SearchType searchType);
public static T? FindDescendantOrSelf<T>(this DependencyObject element, SearchType searchType) where T : notnull, DependencyObject;
public static DependencyObject? FindDescendantOrSelf(this DependencyObject element, Type type, SearchType searchType);
public static T? FindDescendantOrSelf<T>(this DependencyObject element, Func<T, bool> predicate, SearchType searchType) where T : notnull, DependencyObject;
public static T? FindDescendantOrSelf<T, TState>(this DependencyObject element, TState state, Func<T, TState, bool> predicate, SearchType searchType) where T : notnull, DependencyObject;
public static IEnumerable<DependencyObject> FindDescendants(this DependencyObject element, SearchType searchType);
// Find Ascendants/Descendants with self (and search type)
public static IEnumerable<DependencyObject> FindDescendantsOrSelf(this DependencyObject element);
public static IEnumerable<DependencyObject> FindDescendantsOrSelf(this DependencyObject element, SearchType searchType);
public static IEnumerable<DependencyObject> FindAscendantsOrSelf(this DependencyObject element);
}
}
Describe alternatives you've considered
Do nothing and let each developer reimplement the extensions needed manually. As mentioned above, this is not ideal as it adds complexity, increases the API surface to test and maintain for everyone, and likely will result in developers not implementing these APIs in the most efficient way possible (because everyone has limited time, or might not be experienced enough, or care).
Open questions
- Should
GetChildren
return anIEnumerable<T>
instead of an array? - Should we also add an extension to check whether an item is an ascendant/descendant of another?
- Should we also add these corresponding extensions to the
FrameworkElementExtensions
type?
Metadata
Assignees
Type
Projects
Status
In Progress