Browse Source

FindAncestorOfType and FindDescendantOfType with predicates (#20661)

* Find ancestors/descendants with a predicate

* Use Predicate instead of Func

* Find ancestors/descendants with a predicate

* Use Predicate instead of Func

* Predicate<Visual> to Predicate<T>

---------

Co-authored-by: Jan Kučera <miloush@users.noreply.github.com>
pull/20669/head
Jan Kučera 1 month ago
committed by GitHub
parent
commit
a48c415026
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 44
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  2. 43
      tests/Avalonia.Base.UnitTests/VisualExtensionsTests.cs

44
src/Avalonia.Base/VisualTree/VisualExtensions.cs

@ -147,6 +147,19 @@ namespace Avalonia.VisualTree
/// <param name="includeSelf">If given visual should be included in search.</param> /// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First ancestor of given type.</returns> /// <returns>First ancestor of given type.</returns>
public static T? FindAncestorOfType<T>(this Visual? visual, bool includeSelf = false) where T : class public static T? FindAncestorOfType<T>(this Visual? visual, bool includeSelf = false) where T : class
{
return FindAncestorOfType<T>(visual, includeSelf, predicate: null);
}
/// <summary>
/// Finds first ancestor of given type that matches a predicate.
/// </summary>
/// <typeparam name="T">Ancestor type.</typeparam>
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <param name="predicate">The predicate that the ancestor must match.</param>
/// <returns>First ancestor of given type.</returns>
public static T? FindAncestorOfType<T>(this Visual? visual, bool includeSelf, Predicate<T>? predicate) where T : class
{ {
if (visual is null) if (visual is null)
{ {
@ -159,7 +172,10 @@ namespace Avalonia.VisualTree
{ {
if (parent is T result) if (parent is T result)
{ {
return result; if (predicate == null || predicate(result))
{
return result;
}
} }
parent = parent.VisualParent; parent = parent.VisualParent;
@ -176,18 +192,31 @@ namespace Avalonia.VisualTree
/// <param name="includeSelf">If given visual should be included in search.</param> /// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First descendant of given type.</returns> /// <returns>First descendant of given type.</returns>
public static T? FindDescendantOfType<T>(this Visual? visual, bool includeSelf = false) where T : class public static T? FindDescendantOfType<T>(this Visual? visual, bool includeSelf = false) where T : class
{
return FindDescendantOfType<T>(visual, includeSelf, predicate: null);
}
/// <summary>
/// Finds first descendant of given type that matches given predicate.
/// </summary>
/// <typeparam name="T">Descendant type.</typeparam>
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <param name="predicate">The predicate that the descendant must match.</param>
/// <returns>First descendant of given type that matches given predicate.</returns>
public static T? FindDescendantOfType<T>(this Visual? visual, bool includeSelf, Predicate<T>? predicate) where T : class
{ {
if (visual is null) if (visual is null)
{ {
return null; return null;
} }
if (includeSelf && visual is T result) if (includeSelf && visual is T result && (predicate == null || predicate(result)))
{ {
return result; return result;
} }
return FindDescendantOfTypeCore<T>(visual); return FindDescendantOfTypeCore<T>(visual, predicate);
} }
/// <summary> /// <summary>
@ -485,7 +514,7 @@ namespace Avalonia.VisualTree
.Select(x => x.Element!); .Select(x => x.Element!);
} }
private static T? FindDescendantOfTypeCore<T>(Visual visual) where T : class private static T? FindDescendantOfTypeCore<T>(Visual visual, Predicate<T>? predicate) where T : class
{ {
var visualChildren = visual.VisualChildren; var visualChildren = visual.VisualChildren;
var visualChildrenCount = visualChildren.Count; var visualChildrenCount = visualChildren.Count;
@ -496,10 +525,13 @@ namespace Avalonia.VisualTree
if (child is T result) if (child is T result)
{ {
return result; if (predicate == null || predicate(result))
{
return result;
}
} }
var childResult = FindDescendantOfTypeCore<T>(child); var childResult = FindDescendantOfTypeCore<T>(child, predicate);
if (!(childResult is null)) if (!(childResult is null))
{ {

43
tests/Avalonia.Base.UnitTests/VisualExtensionsTests.cs

@ -22,6 +22,23 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(root, target.FindAncestorOfType<TestRoot>()); Assert.Equal(root, target.FindAncestorOfType<TestRoot>());
} }
[Fact]
public void FindAncestorOfType_Finds_Visible_Parent()
{
StackPanel target;
var root = new TestRoot
{
Child = new TestRoot
{
Child = target = new StackPanel(),
IsVisible = false
}
};
Assert.Equal(root, target.FindAncestorOfType<TestRoot>(false, v => v.IsVisible));
}
[Fact] [Fact]
public void FindAncestorOfType_Finds_Ancestor_Of_Nested_Child() public void FindAncestorOfType_Finds_Ancestor_Of_Nested_Child()
{ {
@ -85,6 +102,32 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(target, root.FindDescendantOfType<Button>()); Assert.Equal(target, root.FindDescendantOfType<Button>());
} }
[Fact]
public void FindDescendantOfType_Finds_Nested_Visible_Child()
{
Button target;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
new StackPanel
{
Children =
{
new Button { IsVisible = false },
(target = new Button())
}
}
}
}
};
Assert.Equal(target, root.FindDescendantOfType<Button>(false, v => v.IsVisible));
}
[Fact] [Fact]
public void FindCommonVisualAncestor_First_Is_Parent_Of_Second() public void FindCommonVisualAncestor_First_Is_Parent_Of_Second()
{ {

Loading…
Cancel
Save