Click here to Skip to main content
Click here to Skip to main content

LINQ to Visual Tree

, 15 Jun 2010
Rate this:
Please Sign up or sign in to vote.
This blog post demonstrates a Linq API which can be used to query the WPF / Silverlight Visual Tree.

This blog post demonstrates a Linq API which can be used to query the WPF / Silverlight Visual Tree. You can find a few other Linq to Visual Tree techniques on other blogs, but what makes this one unique is that it retains, and allows queries that make use of the tree like structure rather than simply flattening it.

I have recently published an article on CodeProject which describes a technique for generating Linq API for querying tree-like structures. This blog post makes use of a generated API for WPF / Silverlight. If you are interested in the more generic approach, and how this API was constructed, (and how it is influenced by XPath) head on over to CodeProject …

What I will provide here is a brief overview of the Linq to Visual Tree API. The full source code for this API is at the end of this article.

The Linq to Visual Tree API defines a number of extension methods on DependencyObject that provide mechanisms for navigating to other DependencyObject instances. I will provide a few examples that query the following simple markup:

<Grid x:Name="GridOne" Background="White">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
 
    <TextBox x:Name="TextBoxOne" Text="One" Grid.Row="0" />
 
    <StackPanel x:Name="StackPanelOne" Grid.Row="1">
        <TextBox x:Name="TextBoxTwo" Text="Two" />
 
        <Grid x:Name="GridTwo">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
 
            <TextBox x:Name="TextBoxThree" Text="Three" Grid.Row="0" />
 
            <StackPanel x:Name="StackPanelTwo" Grid.Row="1">
                <TextBox x:Name="TextBoxFour" Text="Four"/>                    
            </StackPanel>
        </Grid>
    </StackPanel>
</Grid>

We’ll start with a simple example. Include the Linq to Visual Tree namespace, then use the Descendants method to obtain all the descendants (i.e. children and children’s children, etc. …) of an object within the visual tree.

Descendants

using LinqToVisualTree;

// all items within the visual tree
IEnumerable<DependencyObject> allDescendants = this.Descendants();
/*
gives ...
0 {Grid} 	[GridOne]
1 {TextBox} 	[TextBoxOne]
2 {StackPanel} 	[StackPanelOne]
3 {TextBox} 	[TextBoxTwo]
4 {Grid} 	[GridTwo]
5 {TextBox} 	[TextBoxThree]
6 {StackPanel} 	[StackPanelTwo]
7 {TextBox} 	[TextBoxFour]
*/
 
// all items within the visual tree of 'GridTwo'
var descendantsOfGridTwo = GridTwo.Descendants();
/*
gives ...
0 {TextBox} 	[TextBoxThree]
1 {StackPanel} 	[StackPanelTwo]
2 {TextBox} 	[TextBoxFour]
*/

Each of the extension methods also has a corresponding method with a generic type parameter that filters the collection to find elements of a specific type:

// all items within the visual tree of 'GridTwo' that are textboxes
var textBoxDescendantsOfGridTwo = GridTwo.Descendants()
                                         .Where(i => i is TextBox);
/*
0 {TextBox} 	[TextBoxThree]
1 {TextBox} 	[TextBoxFour]
*/
 
// a shorthand using the generic version of Descendants
var textBoxDescendantsOfGridTwo2 = GridTwo.Descendants<TextBox>();
/*
0 {TextBox} 	[TextBoxThree]
1 {TextBox} 	[TextBoxFour]
*/

Elements

The elements extension method obtains all the direct children of an item in the visual tree:

// find all direct children of this user control 
var userControlChildren = this.Elements();
/*
0 {Grid} 	[GridOne]
*/
 
// find all direct children of the grid 'GridTwo'
var gridChildren = GridTwo.Elements();
/*
0 {TextBox} 	[TextBoxThree]
1 {StackPanel} 	[StackPanelTwo]
*/
 
// find all direct children of the grid 'GridTwo' that are StackPanels
var gridChildren2 = GridTwo.Elements<StackPanel>();
/*
0 {StackPanel} 	[StackPanelTwo]
*/

There are also, ElementsBeforeSelf and ElementsAfterSelf methods that return the elements before and after the item which the method is being invoked upon.

Ancestors

The ancestors methods traverse the tree towards the root, finding all the ancestors:

// the ancestors for 'TextBoxFour'
var ancestors = TextBoxFour.Ancestors();
/*
0 {StackPanel} 	[StackPanelTwo]
1 {Grid} 	[GridTwo]
2 {StackPanel} 	[StackPanelOne]
3 {Grid} 	[GridOne]
4 {MainPage} 	[]
*/
 
// the ancestors for 'TextBoxFour' that are StackPanels
var stackPanelAncestors = TextBoxFour.Ancestors<StackPanel>();
/*
0 {StackPanel} 	[StackPanelTwo]
1 {StackPanel} 	[StackPanelOne]
*/

Putting it all Together

The Linq to Tree API not only defines extension methods on DependencyObject, but also the same extension methods on IEnumerable<DependencyObject>. Unless you have previous experience of Linq to XML, I would strongly suggest reading my CodeProject article to understand how this works!

This allows you to form much more complex queries. For example, you can find all TextBoxs that have a Grid as a direct parent:

var itemsFluent = this.Descendants<TextBox>()
                      .Where(i => i.Ancestors().FirstOrDefault() is Grid);
 
var itemsQuery = from v in this.Descendants<TextBox>()
                 where v.Ancestors().FirstOrDefault() is Grid
                 select v;
/*
0 {TextBox} 	[TextBoxOne]
1 {TextBox} 	[TextBoxThree]
*/

Here, you can also see we are mixing the fluent and query syntax for Linq. Both give the same result.

The next example finds all StackPanels that are within another StackPanels visual tree:

var items2Fluent = this.Descendants<StackPanel>()
                              .Descendants<StackPanel>();
 
var items2Query = from i in
                      (from v in this.Descendants<StackPanel>()
                       select v).Descendants<StackPanel>()
                  select i;
/*
0 {StackPanel} 	[StackPanelTwo]
*/

Finally, this one-liner, outputs the entire visual tree in ASCII! It makes use of the DescendantsAndSelf, Ancestors and ElementsBeforeSelf methods, plus the funky Linq Aggregate method.

string tree = this.DescendantsAndSelf().Aggregate("",
    (bc, n) => bc + n.Ancestors().Aggregate("", 
	(ac, m) => (m.ElementsAfterSelf().Any() ? "| " : "  ") + ac,
    ac => ac + (n.ElementsAfterSelf().Any() ? "+-" : "\\-")) + n.GetType().Name + "\n");
\-MainPage
  \-Grid
    +-TextBox
    | \-Grid
    |   +-Border
    |   | \-Grid
    |   |   +-Border
    |   |   \-Border
    |   |     \-ScrollViewer
    |   |       \-Border
    |   |         \-Grid
    |   |           +-ScrollContentPresenter
    |   |           | \-TextBoxView
    |   |           +-Rectangle
    |   |           +-ScrollBar
    |   |           \-ScrollBar
    |   +-Border
    |   +-Border
    |   \-Border
    |     \-Grid
    |       +-Path
    |       \-Path
    \-StackPanel
      +-TextBox
      | \-Grid
      |   +-Border
      |   | \-Grid
      |   |   +-Border
      |   |   \-Border
      |   |     \-ScrollViewer
      |   |       \-Border
      |   |         \-Grid
      |   |           +-ScrollContentPresenter
      |   |           | \-TextBoxView
      |   |           +-Rectangle
      |   |           +-ScrollBar
      |   |           \-ScrollBar
      |   +-Border
      |   +-Border
      |   \-Border
      |     \-Grid
      |       +-Path
      |       \-Path
      \-Grid
        +-TextBox
        | \-Grid
        |   +-Border
        |   | \-Grid
        |   |   +-Border
        |   |   \-Border
        |   |     \-ScrollViewer
        |   |       \-Border
        |   |         \-Grid
        |   |           +-ScrollContentPresenter
        |   |           | \-TextBoxView
        |   |           +-Rectangle
        |   |           +-ScrollBar
        |   |           \-ScrollBar
        |   +-Border
        |   +-Border
        |   \-Border
        |     \-Grid
        |       +-Path
        |       \-Path
        \-StackPanel
          \-TextBox
            \-Grid
              +-Border
              | \-Grid
              |   +-Border
              |   \-Border
              |     \-ScrollViewer
              |       \-Border
              |         \-Grid
              |           +-ScrollContentPresenter
              |           | \-TextBoxView
              |           +-Rectangle
              |           +-ScrollBar
              |           \-ScrollBar
              +-Border
              +-Border
              \-Border
                \-Grid
                  +-Path
                  \-Path

Note: This was invoked after the LayoutUpdated event so that we not only see the elements from our XAML, but also the elements created from their templates, giving us our full run-time visual tree.

You can download a simple Silverlight application that demonstrated all the examples given above: LinqToTree.zip.

Or, if you just want the Linq to VisualTree code, you can copy-n-paste from the windows below which has the entire API, which includes the methods illustrated above, plus their IEnumerable equivalents, and a few others I have not illustrated.

using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;

namespace LinqToVisualTree
{
    /// <span class="code-SummaryComment"><summary>
</span>    /// Adapts a DependencyObject to provide methods required for generate
    /// a Linq To Tree API
    /// <span class="code-SummaryComment"></summary>
</span>    public class VisualTreeAdapter : ILinqTree<dependencyobject>
    {
        private DependencyObject _item;

        public VisualTreeAdapter(DependencyObject item)
        {
            _item = item;
        }

        public IEnumerable<dependencyobject> Children()
        {
            int childrenCount = VisualTreeHelper.GetChildrenCount(_item);
            for (int i = 0; i < childrenCount; i++)
            {
                yield return VisualTreeHelper.GetChild(_item, i);
            }
        }

        public DependencyObject Parent
        {
            get
            {
                return VisualTreeHelper.GetParent(_item);
            }
        }
    }
}

namespace LinqToVisualTree
{
    /// <span class="code-SummaryComment"><summary>
</span>    /// Defines an interface that must be implemented to generate the LinqToTree methods
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><typeparam name="T"></typeparam>
</span>    public interface ILinqTree<t>
    {
        IEnumerable<t> Children();

        T Parent { get; }
    }

    public static class TreeExtensions
    {
        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of descendant elements.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		Descendants(this DependencyObject item)
        {
            ILinqTree<dependencyobject> adapter = new VisualTreeAdapter(item);
            foreach (var child in adapter.Children())
            {
                yield return child;

                foreach (var grandChild in child.Descendants())
                {
                    yield return grandChild;
                }
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection containing this element and all descendant elements.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		DescendantsAndSelf(this DependencyObject item)
        {
            yield return item;

            foreach (var child in item.Descendants())
            {
                yield return child;
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of ancestor elements.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> Ancestors(this DependencyObject item)
        {
            ILinqTree<dependencyobject> adapter = new VisualTreeAdapter(item);

            var parent = adapter.Parent;
            while (parent != null)
            {
                yield return parent;
                adapter = new VisualTreeAdapter(parent);
                parent = adapter.Parent;
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection containing this element and all ancestor elements.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		AncestorsAndSelf(this DependencyObject item)
        {
            yield return item;

            foreach (var ancestor in item.Ancestors())
            {
                yield return ancestor;
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of child elements.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> Elements(this DependencyObject item)
        {
            ILinqTree<dependencyobject> adapter = new VisualTreeAdapter(item);
            foreach (var child in adapter.Children())
            {
                yield return child;
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of the sibling elements before this node, 
        /// in document order.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		ElementsBeforeSelf(this DependencyObject item)
        {
            if (item.Ancestors().FirstOrDefault() == null)
                yield break;
            foreach (var child in item.Ancestors().First().Elements())
            {
                if (child.Equals(item))
                    break;
                yield return child;
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of the after elements after this node, in document order.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		ElementsAfterSelf(this DependencyObject item)
        {
            if (item.Ancestors().FirstOrDefault() == null)
                yield break;
            bool afterSelf = false;
            foreach (var child in item.Ancestors().First().Elements())
            {
                if (afterSelf)
                    yield return child;

                if (child.Equals(item))
                    afterSelf = true;
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection containing this element and all child elements.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		ElementsAndSelf(this DependencyObject item)
        {
            yield return item;

            foreach (var child in item.Elements())
            {
                yield return child;
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of descendant elements which match the given type.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		Descendants<t>(this DependencyObject item)
        {
            return item.Descendants().Where(i => i is T).Cast<dependencyobject>();
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of the sibling elements before this node, 
        /// in document order
        /// which match the given type.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		ElementsBeforeSelf<t>(this DependencyObject item)
        {
            return item.ElementsBeforeSelf().Where(i => i is T).Cast<dependencyobject>();
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of the after elements after this node, in document order
        /// which match the given type.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		ElementsAfterSelf<t>(this DependencyObject item)
        {
            return item.ElementsAfterSelf().Where(i => i is T).Cast<dependencyobject>();
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection containing this element and all descendant elements
        /// which match the given type.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
		DescendantsAndSelf<t>(this DependencyObject item)
        {
            return item.DescendantsAndSelf().Where(i => i is T).Cast<dependencyobject>();
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of ancestor elements which match the given type.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
			Ancestors<t>(this DependencyObject item)
        {
            return item.Ancestors().Where(i => i is T).Cast<dependencyobject>();
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection containing this element and all ancestor elements
        /// which match the given type.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
			AncestorsAndSelf<t>(this DependencyObject item)
        {
            return item.AncestorsAndSelf().Where(i => i is T).Cast<dependencyobject>();
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection of child elements which match the given type.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
			Elements<t>(this DependencyObject item)
        {
            return item.Elements().Where(i => i is T).Cast<dependencyobject>();
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns a collection containing this element and all child elements.
        /// which match the given type.
        /// <span class="code-SummaryComment"></summary>
</span>        public static IEnumerable<dependencyobject> 
			ElementsAndSelf<t>(this DependencyObject item)
        {
            return item.ElementsAndSelf().Where(i => i is T).Cast<dependencyobject>();
        }

    }

    public static class EnumerableTreeExtensions
    {
        /// <span class="code-SummaryComment"><summary>
</span>        /// Applies the given function to each of the items in the supplied
        /// IEnumerable.
        /// <span class="code-SummaryComment"></summary>
</span>        private static IEnumerable<dependencyobject> 
			DrillDown(this IEnumerable<dependencyobject> items,
            Func<dependencyobject,>>

Regards, Colin E.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Colin Eberhardt
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.
 
I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.
 
I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.
 
Visit my blog - Colin Eberhardt's Adventures in .NET.
 
Follow me on Twitter - @ColinEberhardt
 
-
Follow on   Twitter   Google+

Comments and Discussions

 
Questionusing visual tree against a ContentPresenter PinmemberMichael J. Eber22-Aug-11 13:19 
Generalquestion PinmemberNick Polyak18-Apr-11 10:46 
one thing that I would change though (unless there is something I do not understand) is - the templated methods should return a collection of the templated type, not a collection of DependencyObjects. This would save a conversion later.
Nick Polyak

General5 from me PinmemberNick Polyak18-Apr-11 10:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140718.1 | Last Updated 15 Jun 2010
Article Copyright 2010 by Colin Eberhardt
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid