Skip to content

[Feature] Improve/extend the visual tree extensions #4300

Open

Description

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 and FindDescendants methods lack a corresponding OrSelf 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 an IEnumerable<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?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    • Status

      In Progress

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions