@@ -453,27 +453,194 @@ public static class DependencyObjectExtensions
453453 /// <para>
454454 /// This method is meant to provide extra flexibility in specific scenarios and it should not
455455 /// be used when only the first item is being looked for. In those cases, use one of the
456- /// available <see cref="FindDescendant{T}(DependencyObject)"/> overloads instead, which will
457- /// offer a more compact syntax as well as better performance in those cases.
456+ /// available <see cref="FindDescendant{T}(DependencyObject)"/> overloads instead,
457+ /// which will offer a more compact syntax as well as better performance in those cases.
458458 /// </para>
459459 /// </summary>
460460 /// <param name="element">The root element.</param>
461461 /// <returns>All the descendant <see cref="DependencyObject"/> instance from <paramref name="element"/>.</returns>
462462 public static IEnumerable < DependencyObject > FindDescendants ( this DependencyObject element )
463463 {
464- int childrenCount = VisualTreeHelper . GetChildrenCount ( element ) ;
464+ return FindDescendants ( element , SearchType . DepthFirst ) ;
465+ }
465466
466- for ( var i = 0 ; i < childrenCount ; i ++ )
467+ /// <summary>
468+ /// Find all descendant elements of the specified element. This method can be chained with
469+ /// LINQ calls to add additional filters or projections on top of the returned results.
470+ /// <para>
471+ /// This method is meant to provide extra flexibility in specific scenarios and it should not
472+ /// be used when only the first item is being looked for. In those cases, use one of the
473+ /// available <see cref="FindDescendant{T}(DependencyObject)"/> overloads instead,
474+ /// which will offer a more compact syntax as well as better performance in those cases.
475+ /// </para>
476+ /// </summary>
477+ /// <param name="element">The root element.</param>
478+ /// <param name="searchType">The search type to use to explore the visual tree.</param>
479+ /// <returns>All the descendant <see cref="DependencyObject"/> instance from <paramref name="element"/>.</returns>
480+ public static IEnumerable < DependencyObject > FindDescendants ( this DependencyObject element , SearchType searchType )
481+ {
482+ // Depth-first traversal, with recursion
483+ static IEnumerable < DependencyObject > FindDescendantsWithDepthFirstSearch ( DependencyObject element )
467484 {
468- DependencyObject child = VisualTreeHelper . GetChild ( element , i ) ;
485+ int childrenCount = VisualTreeHelper . GetChildrenCount ( element ) ;
486+
487+ for ( var i = 0 ; i < childrenCount ; i ++ )
488+ {
489+ DependencyObject child = VisualTreeHelper . GetChild ( element , i ) ;
490+
491+ yield return child ;
492+
493+ foreach ( DependencyObject childOfChild in FindDescendants ( child ) )
494+ {
495+ yield return childOfChild ;
496+ }
497+ }
498+ }
499+
500+ // Breadth-first traversal, with pooled local stack
501+ static IEnumerable < DependencyObject > FindDescendantsWithBreadthFirstSearch ( DependencyObject element )
502+ {
503+ using ArrayPoolBufferWriter < object > bufferWriter = ArrayPoolBufferWriter < object > . Create ( ) ;
504+
505+ int childrenCount = VisualTreeHelper . GetChildrenCount ( element ) ;
506+
507+ for ( int i = 0 ; i < childrenCount ; i ++ )
508+ {
509+ DependencyObject child = VisualTreeHelper . GetChild ( element , i ) ;
469510
470- yield return child ;
511+ yield return child ;
471512
472- foreach ( DependencyObject childOfChild in FindDescendants ( child ) )
513+ bufferWriter . Add ( child ) ;
514+ }
515+
516+ for ( int i = 0 ; i < bufferWriter . Count ; i ++ )
473517 {
474- yield return childOfChild ;
518+ DependencyObject parent = ( DependencyObject ) bufferWriter [ i ] ;
519+
520+ childrenCount = VisualTreeHelper . GetChildrenCount ( parent ) ;
521+
522+ for ( int j = 0 ; j < childrenCount ; j ++ )
523+ {
524+ DependencyObject child = VisualTreeHelper . GetChild ( parent , j ) ;
525+
526+ yield return child ;
527+
528+ bufferWriter . Add ( child ) ;
529+ }
475530 }
476531 }
532+
533+ static IEnumerable < DependencyObject > ThrowArgumentOutOfRangeExceptionForInvalidSearchType ( )
534+ {
535+ throw new ArgumentOutOfRangeException ( nameof ( searchType ) , "The input search type is not valid" ) ;
536+ }
537+
538+ return searchType switch
539+ {
540+ SearchType . DepthFirst => FindDescendantsWithDepthFirstSearch ( element ) ,
541+ SearchType . BreadthFirst => FindDescendantsWithBreadthFirstSearch ( element ) ,
542+ _ => ThrowArgumentOutOfRangeExceptionForInvalidSearchType ( )
543+ } ;
544+ }
545+
546+ /// <summary>
547+ /// Find all descendant elements of the specified element (or self). This method can be chained
548+ /// with LINQ calls to add additional filters or projections on top of the returned results.
549+ /// <para>
550+ /// This method is meant to provide extra flexibility in specific scenarios and it should not
551+ /// be used when only the first item is being looked for. In those cases, use one of the
552+ /// available <see cref="FindDescendantOrSelf{T}(DependencyObject)"/> overloads instead,
553+ /// which will offer a more compact syntax as well as better performance in those cases.
554+ /// </para>
555+ /// </summary>
556+ /// <param name="element">The root element.</param>
557+ /// <returns>All the descendant <see cref="DependencyObject"/> instance from <paramref name="element"/>.</returns>
558+ public static IEnumerable < DependencyObject > FindDescendantsOrSelf ( this DependencyObject element )
559+ {
560+ return FindDescendantsOrSelf ( element , SearchType . DepthFirst ) ;
561+ }
562+
563+ /// <summary>
564+ /// Find all descendant elements of the specified element (or self). This method can be chained
565+ /// with LINQ calls to add additional filters or projections on top of the returned results.
566+ /// <para>
567+ /// This method is meant to provide extra flexibility in specific scenarios and it should not
568+ /// be used when only the first item is being looked for. In those cases, use one of the
569+ /// available <see cref="FindDescendantOrSelf{T}(DependencyObject)"/> overloads instead,
570+ /// which will offer a more compact syntax as well as better performance in those cases.
571+ /// </para>
572+ /// </summary>
573+ /// <param name="element">The root element.</param>
574+ /// <param name="searchType">The search type to use to explore the visual tree.</param>
575+ /// <returns>All the descendant <see cref="DependencyObject"/> instance from <paramref name="element"/>.</returns>
576+ public static IEnumerable < DependencyObject > FindDescendantsOrSelf ( this DependencyObject element , SearchType searchType )
577+ {
578+ // Depth-first traversal, with recursion
579+ static IEnumerable < DependencyObject > FindDescendantsWithDepthFirstSearch ( DependencyObject element )
580+ {
581+ yield return element ;
582+
583+ int childrenCount = VisualTreeHelper . GetChildrenCount ( element ) ;
584+
585+ for ( var i = 0 ; i < childrenCount ; i ++ )
586+ {
587+ DependencyObject child = VisualTreeHelper . GetChild ( element , i ) ;
588+
589+ yield return child ;
590+
591+ foreach ( DependencyObject childOfChild in FindDescendants ( child ) )
592+ {
593+ yield return childOfChild ;
594+ }
595+ }
596+ }
597+
598+ // Breadth-first traversal, with pooled local stack
599+ static IEnumerable < DependencyObject > FindDescendantsWithBreadthFirstSearch ( DependencyObject element )
600+ {
601+ yield return element ;
602+
603+ using ArrayPoolBufferWriter < object > bufferWriter = ArrayPoolBufferWriter < object > . Create ( ) ;
604+
605+ int childrenCount = VisualTreeHelper . GetChildrenCount ( element ) ;
606+
607+ for ( int i = 0 ; i < childrenCount ; i ++ )
608+ {
609+ DependencyObject child = VisualTreeHelper . GetChild ( element , i ) ;
610+
611+ yield return child ;
612+
613+ bufferWriter . Add ( child ) ;
614+ }
615+
616+ for ( int i = 0 ; i < bufferWriter . Count ; i ++ )
617+ {
618+ DependencyObject parent = ( DependencyObject ) bufferWriter [ i ] ;
619+
620+ childrenCount = VisualTreeHelper . GetChildrenCount ( parent ) ;
621+
622+ for ( int j = 0 ; j < childrenCount ; j ++ )
623+ {
624+ DependencyObject child = VisualTreeHelper . GetChild ( parent , j ) ;
625+
626+ yield return child ;
627+
628+ bufferWriter . Add ( child ) ;
629+ }
630+ }
631+ }
632+
633+ static IEnumerable < DependencyObject > ThrowArgumentOutOfRangeExceptionForInvalidSearchType ( )
634+ {
635+ throw new ArgumentOutOfRangeException ( nameof ( searchType ) , "The input search type is not valid" ) ;
636+ }
637+
638+ return searchType switch
639+ {
640+ SearchType . DepthFirst => FindDescendantsWithDepthFirstSearch ( element ) ,
641+ SearchType . BreadthFirst => FindDescendantsWithBreadthFirstSearch ( element ) ,
642+ _ => ThrowArgumentOutOfRangeExceptionForInvalidSearchType ( )
643+ } ;
477644 }
478645
479646 /// <summary>
0 commit comments