From 6e30b4b9cf2d9aa9e11ebc053529157be70e457e Mon Sep 17 00:00:00 2001 From: Adam Stephenson Date: Mon, 10 Feb 2025 19:03:51 -0800 Subject: [PATCH] Add cases for handling expander's header when searching usercontrols. --- .../FrameworkElementExtensions.LogicalTree.cs | 1578 +++++++++-------- 1 file changed, 805 insertions(+), 773 deletions(-) diff --git a/components/Extensions/src/Tree/FrameworkElementExtensions.LogicalTree.cs b/components/Extensions/src/Tree/FrameworkElementExtensions.LogicalTree.cs index 2ffaba73..9e4d6fb6 100644 --- a/components/Extensions/src/Tree/FrameworkElementExtensions.LogicalTree.cs +++ b/components/Extensions/src/Tree/FrameworkElementExtensions.LogicalTree.cs @@ -1,773 +1,805 @@ -// 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.Collections.Generic; -using System.Reflection; -using CommunityToolkit.WinUI.Predicates; - -#nullable enable - -namespace CommunityToolkit.WinUI; - -/// -public static partial class FrameworkElementExtensions -{ - /// - /// Find the first child of type with a given name, using a depth-first search. - /// - /// The root element. - /// The name of the element to look for. - /// The comparison type to use to match . - /// The child that was found, or . - public static FrameworkElement? FindChild(this FrameworkElement element, string name, StringComparison comparisonType = StringComparison.Ordinal) - { - PredicateByName predicateByName = new (name, comparisonType); - - return FindChild(element, ref predicateByName); - } - - /// - /// Find the first child element of a given type, using a depth-first search. - /// - /// The type of elements to match. - /// The root element. - /// The child that was found, or . - public static T? FindChild(this FrameworkElement element) - where T : notnull, FrameworkElement - { - PredicateByAny predicateByAny = default; - - return FindChild>(element, ref predicateByAny); - } - - /// - /// Find the first child element of a given type, using a depth-first search. - /// - /// The root element. - /// The type of element to match. - /// The child that was found, or . - public static FrameworkElement? FindChild(this FrameworkElement element, Type type) - { - PredicateByType predicateByType = new (type); - - return FindChild(element, ref predicateByType); - } - - /// - /// Find the first child element matching a given predicate, using a depth-first search. - /// - /// The type of elements to match. - /// The root element. - /// The predicatee 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 - { - PredicateByFunc predicateByFunc = new (predicate); - - return FindChild>(element, ref predicateByFunc); - } - - /// - /// Find the first child element matching a given predicate, using a depth-first search. - /// - /// 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 predicatee 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 - { - PredicateByFunc predicateByFunc = new (state, predicate); - - return FindChild>(element, ref predicateByFunc); - } - - /// - /// Find the first child element matching a given predicate, using a depth-first search. - /// - /// 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 child that was found, or . - private static T? FindChild(this FrameworkElement element, ref TPredicate predicate) - where T : notnull, FrameworkElement - where TPredicate : struct, IPredicate - { - // Jump label to manually optimize the tail recursive paths for elements with a single - // child by just overwriting the current element and jumping back to the start of the - // method. This avoids a recursive call and one stack frame every time. - Start: - - if (element is Panel panel) - { - foreach (UIElement child in panel.Children) - { - if (child is not FrameworkElement current) - { - continue; - } - - if (child is T result && predicate.Match(result)) - { - return result; - } - - T? descendant = FindChild(current, ref predicate); - - if (descendant is not null) - { - return descendant; - } - } - } - else if (element is ItemsControl itemsControl) - { - foreach (object item in itemsControl.Items) - { - if (item is not FrameworkElement current) - { - continue; - } - - if (item is T result && predicate.Match(result)) - { - return result; - } - - T? descendant = FindChild(current, ref predicate); - - if (descendant is not null) - { - return descendant; - } - } - } - else if (element is ContentControl contentControl) - { - if (contentControl.Content is FrameworkElement content) - { - if (content is T result && predicate.Match(result)) - { - return result; - } - - element = content; - - goto Start; - } - } - else if (element is Border border) - { - if (border.Child is FrameworkElement child) - { - if (child is T result && predicate.Match(result)) - { - return result; - } - - element = child; - - goto Start; - } - } - else if (element is ContentPresenter contentPresenter) - { - // Sometimes ContentPresenter is used in control templates instead of ContentControl, - // therefore we should still check if its Content is a matching FrameworkElement instance. - // This also makes this work for SwitchPresenter. - if (contentPresenter.Content is FrameworkElement content) - { - if (content is T result && predicate.Match(result)) - { - return result; - } - - element = content; - - goto Start; - } - } - else if (element is Viewbox viewbox) - { - if (viewbox.Child is FrameworkElement child) - { - if (child is T result && predicate.Match(result)) - { - return result; - } - - element = child; - - goto Start; - } - } - else if (element is UserControl userControl) - { - // We put UserControl right before the slower reflection fallback path as - // this type is less likely to be used compared to the other ones above. - if (userControl.Content is FrameworkElement content) - { - if (content is T result && predicate.Match(result)) - { - return result; - } - - element = content; - - goto Start; - } - } - else if (element.GetContentControl() is FrameworkElement containedControl) - { - if (containedControl is T result && predicate.Match(result)) - { - return result; - } - - element = containedControl; - - goto Start; - } - - return null; - } - - /// - /// Find the first child (or self) of type with a given name, using a depth-first search. - /// - /// The root element. - /// The name of the element to look for. - /// The comparison type to use to match . - /// The child (or self) that was found, or . - public static FrameworkElement? FindChildOrSelf(this FrameworkElement element, string name, StringComparison comparisonType = StringComparison.Ordinal) - { - if (name.Equals(element.Name, comparisonType)) - { - return element; - } - - return FindChild(element, name, comparisonType); - } - - /// - /// Find the first child (or self) element of a given type, using a depth-first search. - /// - /// The type of elements to match. - /// The root element. - /// The child (or self) that was found, or . - public static T? FindChildOrSelf(this FrameworkElement element) - where T : notnull, FrameworkElement - { - if (element is T result) - { - return result; - } - - return FindChild(element); - } - - /// - /// Find the first child (or self) element of a given type, using a depth-first search. - /// - /// The root element. - /// The type of element to match. - /// The child (or self) that was found, or . - public static FrameworkElement? FindChildOrSelf(this FrameworkElement element, Type type) - { - if (element.GetType() == type) - { - return element; - } - - return FindChild(element, type); - } - - /// - /// Find the first child (or self) element matching a given predicate, using a depth-first search. - /// - /// The type of elements to match. - /// The root element. - /// The predicatee 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 - { - if (element is T result && predicate(result)) - { - return result; - } - - return FindChild(element, predicate); - } - - /// - /// Find the first child (or self) element matching a given predicate, using a depth-first search. - /// - /// 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 predicatee 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 - { - if (element is T result && predicate(result, state)) - { - return result; - } - - return FindChild(element, state, predicate); - } - - /// - /// Find all logical child elements of the specified element. 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 child instance from . - public static IEnumerable FindChildren(this FrameworkElement element) - { - Start: - - if (element is Panel panel) - { - foreach (UIElement child in panel.Children) - { - if (child is not FrameworkElement current) - { - continue; - } - - yield return current; - - foreach (FrameworkElement childOfChild in FindChildren(current)) - { - yield return childOfChild; - } - } - } - else if (element is ItemsControl itemsControl) - { - foreach (object item in itemsControl.Items) - { - if (item is not FrameworkElement current) - { - continue; - } - - yield return current; - - foreach (FrameworkElement childOfChild in FindChildren(current)) - { - yield return childOfChild; - } - } - } - else if (element is ContentControl contentControl) - { - if (contentControl.Content is FrameworkElement content) - { - yield return content; - - element = content; - - goto Start; - } - } - else if (element is Border border) - { - if (border.Child is FrameworkElement child) - { - yield return child; - - element = child; - - goto Start; - } - } - else if (element is ContentPresenter contentPresenter) - { - if (contentPresenter.Content is FrameworkElement content) - { - yield return content; - - element = content; - - goto Start; - } - } - else if (element is Viewbox viewbox) - { - if (viewbox.Child is FrameworkElement child) - { - yield return child; - - element = child; - - goto Start; - } - } - else if (element is UserControl userControl) - { - if (userControl.Content is FrameworkElement content) - { - yield return content; - - element = content; - - goto Start; - } - } - else if (element.GetContentControl() is FrameworkElement containedControl) - { - yield return containedControl; - - element = containedControl; - - goto Start; - } - } - - /// - /// Find the first parent of type with a given name. - /// - /// The starting element. - /// The name of the element to look for. - /// The comparison type to use to match . - /// The parent that was found, or . - public static FrameworkElement? FindParent(this FrameworkElement element, string name, StringComparison comparisonType = StringComparison.Ordinal) - { - PredicateByName predicateByName = new (name, comparisonType); - - return FindParent(element, ref predicateByName); - } - - /// - /// Find the first parent element of a given type. - /// - /// The type of elements to match. - /// The starting element. - /// The parent that was found, or . - public static T? FindParent(this FrameworkElement element) - where T : notnull, FrameworkElement - { - PredicateByAny predicateByAny = default; - - return FindParent>(element, ref predicateByAny); - } - - /// - /// Find the first parent element of a given type. - /// - /// The starting element. - /// The type of element to match. - /// The parent that was found, or . - public static FrameworkElement? FindParent(this FrameworkElement element, Type type) - { - PredicateByType predicateByType = new (type); - - return FindParent(element, ref predicateByType); - } - - /// - /// Find the first parent element matching a given predicate. - /// - /// The type of elements to match. - /// The starting element. - /// The predicatee 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 - { - PredicateByFunc predicateByFunc = new (predicate); - - return FindParent>(element, ref predicateByFunc); - } - - /// - /// Find the first parent element matching a given predicate. - /// - /// The type of elements to match. - /// 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 parent that was found, or . - public static T? FindParent(this FrameworkElement element, TState state, Func predicate) - where T : notnull, FrameworkElement - { - PredicateByFunc predicateByFunc = new (state, predicate); - - return FindParent>(element, ref predicateByFunc); - } - - /// - /// Find the first parent element matching a given predicate. - /// - /// 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 parent that was found, or . - private static T? FindParent(this FrameworkElement element, ref TPredicate predicate) - where T : notnull, FrameworkElement - where TPredicate : struct, IPredicate - { - while (true) - { - if (element.Parent is not FrameworkElement parent) - { - return null; - } - - if (parent is T result && predicate.Match(result)) - { - return result; - } - - element = parent; - } - } - - /// - /// Find the first parent (or self) of type with a given name. - /// - /// The starting element. - /// The name of the element to look for. - /// The comparison type to use to match . - /// The parent (or self) that was found, or . - public static FrameworkElement? FindParentOrSelf(this FrameworkElement element, string name, StringComparison comparisonType = StringComparison.Ordinal) - { - if (name.Equals(element.Name, comparisonType)) - { - return element; - } - - return FindParent(element, name, comparisonType); - } - - /// - /// Find the first parent (or self) element of a given type. - /// - /// The type of elements to match. - /// The starting element. - /// The parent (or self) that was found, or . - public static T? FindParentOrSelf(this FrameworkElement element) - where T : notnull, FrameworkElement - { - if (element is T result) - { - return result; - } - - return FindParent(element); - } - - /// - /// Find the first parent (or self) element of a given type. - /// - /// The starting element. - /// The type of element to match. - /// The parent (or self) that was found, or . - public static FrameworkElement? FindParentOrSelf(this FrameworkElement element, Type type) - { - if (element.GetType() == type) - { - return element; - } - - return FindParent(element, type); - } - - /// - /// Find the first parent (or self) element matching a given predicate. - /// - /// The type of elements to match. - /// The starting element. - /// The predicatee 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 - { - if (element is T result && predicate(result)) - { - return result; - } - - return FindParent(element, predicate); - } - - /// - /// Find the first parent (or self) element matching a given predicate. - /// - /// The type of elements to match. - /// 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 parent (or self) that was found, or . - public static T? FindParentOrSelf(this FrameworkElement element, TState state, Func predicate) - where T : notnull, FrameworkElement - { - if (element is T result && predicate(result, state)) - { - return result; - } - - return FindParent(element, state, predicate); - } - - /// - /// Find all parent elements of the specified element. 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 parent instance from . - public static IEnumerable FindParents(this FrameworkElement element) - { - while (true) - { - if (element.Parent is not FrameworkElement parent) - { - yield break; - } - - yield return parent; - - element = parent; - } - } - - /// - /// Gets the content property of this element as defined by , if available. - /// - /// The parent element. - /// The retrieved content control, or if not available. - #if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "This method is currently not safe for trimming, and annotations here wouldn't help.")] - #endif - public static UIElement? GetContentControl(this FrameworkElement element) - { - Type type = element.GetType(); - TypeInfo? typeInfo = type.GetTypeInfo(); - - while (typeInfo is not null) - { - // We need to manually explore the custom attributes this way as the target one - // is not returned by any of the other available GetCustomAttribute APIs. - foreach (CustomAttributeData attribute in typeInfo.CustomAttributes) - { - if (attribute.AttributeType == typeof(ContentPropertyAttribute)) - { - // If we're finding a ContentPropertyAttribute, this whole path should be set, - // don't think we need additional checks here. - string propertyName = (string)attribute.NamedArguments![0].TypedValue!.Value!; - PropertyInfo? propertyInfo = type.GetProperty(propertyName); - - return propertyInfo?.GetValue(element) as UIElement; - } - } - - typeInfo = typeInfo.BaseType?.GetTypeInfo(); - } - - return null; - } - - /// - /// Provides a WPF compatible version of FindResource to provide a static resource lookup. - /// If the key is not found in the current element's resources, the logical tree is then - /// searched element-by-element to look for the resource in each element's resources. - /// If none of the elements contain the resource, the Application's resources are then searched. - /// See: . - /// And also: . - /// - /// The to start searching for the target resource. - /// The resource key to search for. - /// The requested resource. - /// Thrown when no resource is found with the specified key. - public static object FindResource(this FrameworkElement element, object resourceKey) - { - if (TryFindResource(element, resourceKey, out object? value)) - { - return value!; - } - - static object Throw(object resourceKey) => throw new KeyNotFoundException($"No resource was found with the key \"{resourceKey}\""); - - return Throw(resourceKey); - } - - /// - /// Provides a WPF compatible version of TryFindResource to provide a static resource lookup. - /// If the key is not found in the current element's resources, the logical tree is then - /// searched element-by-element to look for the resource in each element's resources. - /// If none of the elements contain the resource, the Application's resources are then searched. - /// See: . - /// And also: . - /// - /// The to start searching for the target resource. - /// The resource key to search for. - /// The requested resource, or if it wasn't found. - public static object? TryFindResource(this FrameworkElement element, object resourceKey) - { - object? value = null; - - FrameworkElement? current = element; - - // Look in our dictionary and then walk-up parents. We use a do-while loop here - // so that an implicit NRE will be thrown at the first iteration in case the - // input element is null. This is consistent with the other extensions. - do - { - if (current.Resources?.TryGetValue(resourceKey, out value) == true) - { - return value; - } - - current = current.Parent as FrameworkElement; - } - while (current is not null); - - // Finally try application resources - _ = Application.Current?.Resources?.TryGetValue(resourceKey, out value); - - return value; - } - - /// - /// Provides a WPF compatible version of TryFindResource to provide a static resource lookup. - /// If the key is not found in the current element's resources, the logical tree is then - /// searched element-by-element to look for the resource in each element's resources. - /// If none of the elements contain the resource, the Application's resources are then searched. - /// See: . - /// And also: . - /// - /// The to start searching for the target resource. - /// The resource key to search for. - /// The resulting value, if present. - /// Whether or not a value with the specified key has been found. - public static bool TryFindResource(this FrameworkElement element, object resourceKey, out object? value) - { - return (value = TryFindResource(element, resourceKey)) is not null; - } -} +// 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.Collections.Generic; +using System.Reflection; +using CommunityToolkit.WinUI.Predicates; +using Microsoft.UI.Xaml.Controls; + +#nullable enable + +namespace CommunityToolkit.WinUI; + +/// +public static partial class FrameworkElementExtensions +{ + /// + /// Find the first child of type with a given name, using a depth-first search. + /// + /// The root element. + /// The name of the element to look for. + /// The comparison type to use to match . + /// The child that was found, or . + public static FrameworkElement? FindChild(this FrameworkElement element, string name, StringComparison comparisonType = StringComparison.Ordinal) + { + PredicateByName predicateByName = new (name, comparisonType); + + return FindChild(element, ref predicateByName); + } + + /// + /// Find the first child element of a given type, using a depth-first search. + /// + /// The type of elements to match. + /// The root element. + /// The child that was found, or . + public static T? FindChild(this FrameworkElement element) + where T : notnull, FrameworkElement + { + PredicateByAny predicateByAny = default; + + return FindChild>(element, ref predicateByAny); + } + + /// + /// Find the first child element of a given type, using a depth-first search. + /// + /// The root element. + /// The type of element to match. + /// The child that was found, or . + public static FrameworkElement? FindChild(this FrameworkElement element, Type type) + { + PredicateByType predicateByType = new (type); + + return FindChild(element, ref predicateByType); + } + + /// + /// Find the first child element matching a given predicate, using a depth-first search. + /// + /// The type of elements to match. + /// The root element. + /// The predicatee 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 + { + PredicateByFunc predicateByFunc = new (predicate); + + return FindChild>(element, ref predicateByFunc); + } + + /// + /// Find the first child element matching a given predicate, using a depth-first search. + /// + /// 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 predicatee 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 + { + PredicateByFunc predicateByFunc = new (state, predicate); + + return FindChild>(element, ref predicateByFunc); + } + + /// + /// Find the first child element matching a given predicate, using a depth-first search. + /// + /// 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 child that was found, or . + private static T? FindChild(this FrameworkElement element, ref TPredicate predicate) + where T : notnull, FrameworkElement + where TPredicate : struct, IPredicate + { + // Jump label to manually optimize the tail recursive paths for elements with a single + // child by just overwriting the current element and jumping back to the start of the + // method. This avoids a recursive call and one stack frame every time. + Start: + + if (element is Panel panel) + { + foreach (UIElement child in panel.Children) + { + if (child is not FrameworkElement current) + { + continue; + } + + if (child is T result && predicate.Match(result)) + { + return result; + } + + T? descendant = FindChild(current, ref predicate); + + if (descendant is not null) + { + return descendant; + } + } + } + else if (element is ItemsControl itemsControl) + { + foreach (object item in itemsControl.Items) + { + if (item is not FrameworkElement current) + { + continue; + } + + if (item is T result && predicate.Match(result)) + { + return result; + } + + T? descendant = FindChild(current, ref predicate); + + if (descendant is not null) + { + return descendant; + } + } + } + else if (element is ContentControl contentControl) + { + if (element is Expander expander) + { + if (expander.Header is FrameworkElement header) + { + if (header is T result && predicate.Match(result)) + { + return result; + } + + T? descendant = FindChild(header, ref predicate); + + if (descendant is not null) + { + return descendant; + } + } + } + + if (contentControl.Content is FrameworkElement content) + { + if (content is T result && predicate.Match(result)) + { + return result; + } + + element = content; + + goto Start; + } + } + else if (element is Border border) + { + if (border.Child is FrameworkElement child) + { + if (child is T result && predicate.Match(result)) + { + return result; + } + + element = child; + + goto Start; + } + } + else if (element is ContentPresenter contentPresenter) + { + // Sometimes ContentPresenter is used in control templates instead of ContentControl, + // therefore we should still check if its Content is a matching FrameworkElement instance. + // This also makes this work for SwitchPresenter. + if (contentPresenter.Content is FrameworkElement content) + { + if (content is T result && predicate.Match(result)) + { + return result; + } + + element = content; + + goto Start; + } + } + else if (element is Viewbox viewbox) + { + if (viewbox.Child is FrameworkElement child) + { + if (child is T result && predicate.Match(result)) + { + return result; + } + + element = child; + + goto Start; + } + } + else if (element is UserControl userControl) + { + // We put UserControl right before the slower reflection fallback path as + // this type is less likely to be used compared to the other ones above. + if (userControl.Content is FrameworkElement content) + { + if (content is T result && predicate.Match(result)) + { + return result; + } + + element = content; + + goto Start; + } + } + else if (element.GetContentControl() is FrameworkElement containedControl) + { + if (containedControl is T result && predicate.Match(result)) + { + return result; + } + + element = containedControl; + + goto Start; + } + + return null; + } + + /// + /// Find the first child (or self) of type with a given name, using a depth-first search. + /// + /// The root element. + /// The name of the element to look for. + /// The comparison type to use to match . + /// The child (or self) that was found, or . + public static FrameworkElement? FindChildOrSelf(this FrameworkElement element, string name, StringComparison comparisonType = StringComparison.Ordinal) + { + if (name.Equals(element.Name, comparisonType)) + { + return element; + } + + return FindChild(element, name, comparisonType); + } + + /// + /// Find the first child (or self) element of a given type, using a depth-first search. + /// + /// The type of elements to match. + /// The root element. + /// The child (or self) that was found, or . + public static T? FindChildOrSelf(this FrameworkElement element) + where T : notnull, FrameworkElement + { + if (element is T result) + { + return result; + } + + return FindChild(element); + } + + /// + /// Find the first child (or self) element of a given type, using a depth-first search. + /// + /// The root element. + /// The type of element to match. + /// The child (or self) that was found, or . + public static FrameworkElement? FindChildOrSelf(this FrameworkElement element, Type type) + { + if (element.GetType() == type) + { + return element; + } + + return FindChild(element, type); + } + + /// + /// Find the first child (or self) element matching a given predicate, using a depth-first search. + /// + /// The type of elements to match. + /// The root element. + /// The predicatee 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 + { + if (element is T result && predicate(result)) + { + return result; + } + + return FindChild(element, predicate); + } + + /// + /// Find the first child (or self) element matching a given predicate, using a depth-first search. + /// + /// 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 predicatee 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 + { + if (element is T result && predicate(result, state)) + { + return result; + } + + return FindChild(element, state, predicate); + } + + /// + /// Find all logical child elements of the specified element. 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 child instance from . + public static IEnumerable FindChildren(this FrameworkElement element) + { + Start: + + if (element is Panel panel) + { + foreach (UIElement child in panel.Children) + { + if (child is not FrameworkElement current) + { + continue; + } + + yield return current; + + foreach (FrameworkElement childOfChild in FindChildren(current)) + { + yield return childOfChild; + } + } + } + else if (element is ItemsControl itemsControl) + { + foreach (object item in itemsControl.Items) + { + if (item is not FrameworkElement current) + { + continue; + } + + yield return current; + + foreach (FrameworkElement childOfChild in FindChildren(current)) + { + yield return childOfChild; + } + } + } + else if (element is ContentControl contentControl) + { + if (element is Expander expander) + { + if (expander.Header is FrameworkElement header) + { + yield return header; + + foreach (FrameworkElement childOfChild in FindChildren(header)) + { + yield return childOfChild; + } + } + } + + if (contentControl.Content is FrameworkElement content) + { + yield return content; + + element = content; + + goto Start; + } + } + else if (element is Border border) + { + if (border.Child is FrameworkElement child) + { + yield return child; + + element = child; + + goto Start; + } + } + else if (element is ContentPresenter contentPresenter) + { + if (contentPresenter.Content is FrameworkElement content) + { + yield return content; + + element = content; + + goto Start; + } + } + else if (element is Viewbox viewbox) + { + if (viewbox.Child is FrameworkElement child) + { + yield return child; + + element = child; + + goto Start; + } + } + else if (element is UserControl userControl) + { + if (userControl.Content is FrameworkElement content) + { + yield return content; + + element = content; + + goto Start; + } + } + else if (element.GetContentControl() is FrameworkElement containedControl) + { + yield return containedControl; + + element = containedControl; + + goto Start; + } + } + + /// + /// Find the first parent of type with a given name. + /// + /// The starting element. + /// The name of the element to look for. + /// The comparison type to use to match . + /// The parent that was found, or . + public static FrameworkElement? FindParent(this FrameworkElement element, string name, StringComparison comparisonType = StringComparison.Ordinal) + { + PredicateByName predicateByName = new (name, comparisonType); + + return FindParent(element, ref predicateByName); + } + + /// + /// Find the first parent element of a given type. + /// + /// The type of elements to match. + /// The starting element. + /// The parent that was found, or . + public static T? FindParent(this FrameworkElement element) + where T : notnull, FrameworkElement + { + PredicateByAny predicateByAny = default; + + return FindParent>(element, ref predicateByAny); + } + + /// + /// Find the first parent element of a given type. + /// + /// The starting element. + /// The type of element to match. + /// The parent that was found, or . + public static FrameworkElement? FindParent(this FrameworkElement element, Type type) + { + PredicateByType predicateByType = new (type); + + return FindParent(element, ref predicateByType); + } + + /// + /// Find the first parent element matching a given predicate. + /// + /// The type of elements to match. + /// The starting element. + /// The predicatee 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 + { + PredicateByFunc predicateByFunc = new (predicate); + + return FindParent>(element, ref predicateByFunc); + } + + /// + /// Find the first parent element matching a given predicate. + /// + /// The type of elements to match. + /// 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 parent that was found, or . + public static T? FindParent(this FrameworkElement element, TState state, Func predicate) + where T : notnull, FrameworkElement + { + PredicateByFunc predicateByFunc = new (state, predicate); + + return FindParent>(element, ref predicateByFunc); + } + + /// + /// Find the first parent element matching a given predicate. + /// + /// 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 parent that was found, or . + private static T? FindParent(this FrameworkElement element, ref TPredicate predicate) + where T : notnull, FrameworkElement + where TPredicate : struct, IPredicate + { + while (true) + { + if (element.Parent is not FrameworkElement parent) + { + return null; + } + + if (parent is T result && predicate.Match(result)) + { + return result; + } + + element = parent; + } + } + + /// + /// Find the first parent (or self) of type with a given name. + /// + /// The starting element. + /// The name of the element to look for. + /// The comparison type to use to match . + /// The parent (or self) that was found, or . + public static FrameworkElement? FindParentOrSelf(this FrameworkElement element, string name, StringComparison comparisonType = StringComparison.Ordinal) + { + if (name.Equals(element.Name, comparisonType)) + { + return element; + } + + return FindParent(element, name, comparisonType); + } + + /// + /// Find the first parent (or self) element of a given type. + /// + /// The type of elements to match. + /// The starting element. + /// The parent (or self) that was found, or . + public static T? FindParentOrSelf(this FrameworkElement element) + where T : notnull, FrameworkElement + { + if (element is T result) + { + return result; + } + + return FindParent(element); + } + + /// + /// Find the first parent (or self) element of a given type. + /// + /// The starting element. + /// The type of element to match. + /// The parent (or self) that was found, or . + public static FrameworkElement? FindParentOrSelf(this FrameworkElement element, Type type) + { + if (element.GetType() == type) + { + return element; + } + + return FindParent(element, type); + } + + /// + /// Find the first parent (or self) element matching a given predicate. + /// + /// The type of elements to match. + /// The starting element. + /// The predicatee 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 + { + if (element is T result && predicate(result)) + { + return result; + } + + return FindParent(element, predicate); + } + + /// + /// Find the first parent (or self) element matching a given predicate. + /// + /// The type of elements to match. + /// 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 parent (or self) that was found, or . + public static T? FindParentOrSelf(this FrameworkElement element, TState state, Func predicate) + where T : notnull, FrameworkElement + { + if (element is T result && predicate(result, state)) + { + return result; + } + + return FindParent(element, state, predicate); + } + + /// + /// Find all parent elements of the specified element. 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 parent instance from . + public static IEnumerable FindParents(this FrameworkElement element) + { + while (true) + { + if (element.Parent is not FrameworkElement parent) + { + yield break; + } + + yield return parent; + + element = parent; + } + } + + /// + /// Gets the content property of this element as defined by , if available. + /// + /// The parent element. + /// The retrieved content control, or if not available. + #if NET8_0_OR_GREATER + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "This method is currently not safe for trimming, and annotations here wouldn't help.")] + #endif + public static UIElement? GetContentControl(this FrameworkElement element) + { + Type type = element.GetType(); + TypeInfo? typeInfo = type.GetTypeInfo(); + + while (typeInfo is not null) + { + // We need to manually explore the custom attributes this way as the target one + // is not returned by any of the other available GetCustomAttribute APIs. + foreach (CustomAttributeData attribute in typeInfo.CustomAttributes) + { + if (attribute.AttributeType == typeof(ContentPropertyAttribute)) + { + // If we're finding a ContentPropertyAttribute, this whole path should be set, + // don't think we need additional checks here. + string propertyName = (string)attribute.NamedArguments![0].TypedValue!.Value!; + PropertyInfo? propertyInfo = type.GetProperty(propertyName); + + return propertyInfo?.GetValue(element) as UIElement; + } + } + + typeInfo = typeInfo.BaseType?.GetTypeInfo(); + } + + return null; + } + + /// + /// Provides a WPF compatible version of FindResource to provide a static resource lookup. + /// If the key is not found in the current element's resources, the logical tree is then + /// searched element-by-element to look for the resource in each element's resources. + /// If none of the elements contain the resource, the Application's resources are then searched. + /// See: . + /// And also: . + /// + /// The to start searching for the target resource. + /// The resource key to search for. + /// The requested resource. + /// Thrown when no resource is found with the specified key. + public static object FindResource(this FrameworkElement element, object resourceKey) + { + if (TryFindResource(element, resourceKey, out object? value)) + { + return value!; + } + + static object Throw(object resourceKey) => throw new KeyNotFoundException($"No resource was found with the key \"{resourceKey}\""); + + return Throw(resourceKey); + } + + /// + /// Provides a WPF compatible version of TryFindResource to provide a static resource lookup. + /// If the key is not found in the current element's resources, the logical tree is then + /// searched element-by-element to look for the resource in each element's resources. + /// If none of the elements contain the resource, the Application's resources are then searched. + /// See: . + /// And also: . + /// + /// The to start searching for the target resource. + /// The resource key to search for. + /// The requested resource, or if it wasn't found. + public static object? TryFindResource(this FrameworkElement element, object resourceKey) + { + object? value = null; + + FrameworkElement? current = element; + + // Look in our dictionary and then walk-up parents. We use a do-while loop here + // so that an implicit NRE will be thrown at the first iteration in case the + // input element is null. This is consistent with the other extensions. + do + { + if (current.Resources?.TryGetValue(resourceKey, out value) == true) + { + return value; + } + + current = current.Parent as FrameworkElement; + } + while (current is not null); + + // Finally try application resources + _ = Application.Current?.Resources?.TryGetValue(resourceKey, out value); + + return value; + } + + /// + /// Provides a WPF compatible version of TryFindResource to provide a static resource lookup. + /// If the key is not found in the current element's resources, the logical tree is then + /// searched element-by-element to look for the resource in each element's resources. + /// If none of the elements contain the resource, the Application's resources are then searched. + /// See: . + /// And also: . + /// + /// The to start searching for the target resource. + /// The resource key to search for. + /// The resulting value, if present. + /// Whether or not a value with the specified key has been found. + public static bool TryFindResource(this FrameworkElement element, object resourceKey, out object? value) + { + return (value = TryFindResource(element, resourceKey)) is not null; + } +}