Finding Elements in the WPF Tree – Both Ways
A while ago I posted a helper method to traverse a (visual or logical) tree in order to find an element’s parent of a given type. The corresponding blog entry is here.
This time, I needed to search the other way: I wanted to find all descendants (direct or indirect childs) of a given element that match a given type. As a result, I came up with a complementary extension method that does the job for me:
//get all TextBox controls within the grid:
Grid container;
IEnumerable<TextBox> editors = container.FindChildren<TextBox>();
Usage Sample
As a sample, let’s use a window dialog which contains several images:
- Images that were pasted into a RichTextBox control.
- Images that are part of the UI, nested within the control hierarchy of a ribbon.
In order to find all these Image elements you can see on the screenshot, this will do:
//start a the root (the window itself) Window window = this; foreach (Image img in window.FindChildren<Image>()) { Console.WriteLine("Image source: " + img.Source); }
…the above snippet produces the following output:
Image source: pack://payload:,,wpf1,/Xaml/Image1.png
Image source: pack://payload:,,wpf1,/Xaml/Image2.png
Image source: pack://application:,,,/Shared/Images/Ribbon/Paste_32x32.png
Image source: System.Windows.Media.Imaging.FormatConvertedBitmap
Image source: System.Windows.Media.Imaging.FormatConvertedBitmap
Image source: pack://application:,,,/Shared/Images/Ribbon/EditUndo.png
Image source: pack://application:,,,/Shared/Images/Ribbon/EditRedo.png
Image source: pack://application:,,,/Shared/Images/Ribbon/Bold.png
Image source: System.Windows.Media.Imaging.BitmapFrameEncode
[…]
Accordingly, in order to analyze only the contents of the rich text editor, just start on a lower level of the tree:
//only examine the contents of the editor RichTextBox editor = this.txtContent; foreach (Image img in editor.FindChildren<Image>()) { Console.WriteLine("Image source: " + img.Source); }
Implementation
(Download link at the end of the posting)
/// <summary> /// Analyzes both visual and logical tree in order to find all elements /// of a given type that are descendants of the <paramref name="source"/> /// item. /// </summary> /// <typeparam name="T">The type of the queried items.</typeparam> /// <param name="source">The root element that marks the source of the /// search. If the source is already of the requested type, it will not /// be included in the result.</param> /// <returns>All descendants of <paramref name="source"/> that match the /// requested type.</returns> public static IEnumerable<T> FindChildren<T>(this DependencyObject source) where T : DependencyObject { if (source != null) { var childs = GetChildObjects(source); foreach (DependencyObject child in childs) { //analyze if children match the requested type if (child != null && child is T) { yield return (T) child; } //recurse tree foreach (T descendant in FindChildren<T>(child)) { yield return descendant; } } } } /// <summary> /// This method is an alternative to WPF's /// <see cref="VisualTreeHelper.GetChild"/> method, which also /// supports content elements. Do note, that for content elements, /// this method falls back to the logical tree of the element. /// </summary> /// <param name="parent">The item to be processed.</param> /// <returns>The submitted item's child elements, if available.</returns> public static IEnumerable<DependencyObject> GetChildObjects( this DependencyObject parent) { if (parent == null) yield break;
if (parent is ContentElement || parent is FrameworkElement) { //use the logical tree for content / framework elements foreach (object obj in LogicalTreeHelper.GetChildren(parent)) { var depObj = obj as DependencyObject; if (depObj != null) yield return (DependencyObject) obj; } } else { //use the visual tree per default int count = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < count; i++) { yield return VisualTreeHelper.GetChild(parent, i); } } }
For your convenience, I’ve put together a simple helper class that contains the helper methods to search for both children and parent elements in the tree:
http://www.hardcodet.net/uploads/2009/06/UIHelper.cs
Happy coding 🙂
Excellent! Thanks
Great article. I have one problem and I was wandering if you can help me. I have TabControl with few tabs on it. When I call FindChildren method with TabControl reference as argument in order to find TextBlock controls on all tab pages result is empty. Of course I have at least one TextBlock control on each tab page.
@Bobo
I just want to correct my previous post. FindChildren method returns only controls from the first tab page. controls on other tab pages are not returned.
@Bobo
I ran into the same problem. This worked for me, although as far as I can tell it essentially does the exact same thing.
private IEnumerable FindLogicalChildren(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
foreach (object childObj in LogicalTreeHelper.GetChildren(depObj))
{
DependencyObject child = childObj as DependencyObject;
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindLogicalChildren(child))
{
yield return childOfChild;
}
}
}
}
The method failed for FrameworkElements, which require to traverse the logical rather than the visual tree. This is fixed now – thanks for reporting 🙂
I am using your FindChildren in my Project, but now i run into a problem:
The method doesn’t find child elements that are collapsed or hidden. Is there a way to find them anyway?
FANTASTIC..!!!!
Hi!
How about traversing the visual tree of a DataGrid? It doesn’t work with VisualTreeHelper.I actually need to find all column headers, whether string, controls or datatemplates.
Excellent thanks saved me time
Very good!
I updated this method to search by name.
public static IEnumerable
FindChildrenByName(this DependencyObject source, string name)
where T : DependencyObject
{
if (source != null)
{
var childs = GetChildObjects(source);
foreach (DependencyObject child in childs)
{
//analyze if children match the requested type
if (child != null && child is T)
{
FrameworkElement fwk = null;
try
{
fwk = (FrameworkElement)child;
}
catch{
}
if (fwk != null && fwk.Name.Equals(name))
{
yield return (T)child;
}
}
//recurse tree
foreach (T descendant in FindChildrenByName(child,name))
{
yield return descendant;
}
}
}
}
Marcos