Thursday 28 March 2013

WPF - Find Child by Type

WPF provides the VisualTreeHelper class which exposes a few static methods that help us to navigate the visual tree more easily. However, the existing methods don't provide any specific methods for searching the visual tree for a particular element type.

In this post I will show how we can use the existing methods of VisualTreeHelper to complete this task and provide us with a handy piece of reusable code for future projects.

The following methods can give us access to child elements in the visual tree;

int GetChildrenCount(DependencyObject reference);

We can pass an element from the visual tree to GetChildrenCount and it will return the number of children that the specified visual object contains.

DependencyObject GetChild(DependencyObject reference, int childIndex);

We pass an element from the visual tree along with an index and the child element found at the specified index of child collection is returned.

This is all very useful but the visual tree can become a complex thing, particularly when we start styling our own visual templates so getting hold of the right element isn't always straight forward.

Wouldn't it be good if we had some way of saying "Here's a ListView, please give me it's ScrollViewer"?

We can use the code below to do just that.

  1. public static T GetVisualChild<T>(this DependencyObject referenceVisual) where T : Visual
  2. {
  3.     Visual child = null;
  4.     int childCount = VisualTreeHelper.GetChildrenCount(referenceVisual);
  5.  
  6.     for (int i = 0; i < childCount; i++)
  7.     {
  8.         child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual;
  9.  
  10.         if (child != null && child is T)
  11.         {
  12.             break;
  13.         }
  14.         else if (child != null)
  15.         {
  16.             child = GetVisualChild<T>(child);
  17.             if (child != null && child is T) break;
  18.         }
  19.     }
  20.  
  21.     return child as T;
  22. }

The above code will add an extension method, GetVisualChild, to any element that inherits from Visual. It makes use of the two described methods of the VisualTreeHelper class to first get number of children of referenceVisual and then loop through each of these children using VisualTreeHelper.GetChild.

If the returned child is of the desired type we return this element, if it's not we then call our extension method again this time passing the child element so that we examine all child elements within the visual tree until we find an element of the desired type.

Looking at this code you may have realised that we examine each full branch of the visual tree in turn until we find a child visual of the specified type and we return that child. What if our parent visual contains multiple children of the specified type and we want to access a specific one, or more than one? A little modification to our extension method and we can return all children of the required type.

  1. public static List<T> GetVisualChildren<T>(this Visual referenceVisual) where T : Visual
  2. {
  3.     List<T> children = new List<T>();
  4.     int childCount = VisualTreeHelper.GetChildrenCount(referenceVisual);
  5.  
  6.     for (int i = 0; i < childCount; i++)
  7.     {
  8.         Visual child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual;
  9.  
  10.         if (child != null)
  11.         {
  12.             if (child is T) children.Add((T)child);
  13.             List<T> temp = GetVisualChildren<T>(child);
  14.             children.AddRange(temp);
  15.         }
  16.     }
  17.  
  18.     return children;
  19. }

The above method examines every child branch of the visual tree and builds up a collection of all children that are of the specified type.

We can now call these two methods like so;

  1. private void GetVisualChildTest(ListView lvw, Grid layoutRoot)
  2. {
  3.     ScrollViewer sv = lvw.GetVisualChild<ScrollViewer>();
  4.     List<ScrollViewer> svs = layoutRoot.GetVisualChildren<ScrollViewer>();
  5. }

No comments:

Post a Comment