Peter McGrattan’s Weblog

Silverlight, WCF, ASP.NET, AJAX, Graphics, RIA

LINQ to Visual Tree (Beta)

Posted by petermcg on March 4, 2009

(Code Download)

Introduction

This post presents an early prototype of ‘LINQ to Visual Tree’ for Silverlight 2 and WPF applications.  The ultimate goal is to provide applications with the ability to quickly and easily query the Silverlight 2 or WPF Visual Tree via LINQ to Objects.

The prototype provides this functionality by enabling enumeration of an application’s in-memory Visual Tree through the IEnumerable<T> interface.  This is distinct to implementing a custom query provider: the prototype does not implement IQueryable<T> or execute and translate expression trees.

This post demonstrates the basics so far and makes the prototype available for download, it’s aim is to get feedback and ideas on how the simple API can be improved.

Trees in Silverlight and WPF

The one fundamental tree of objects that make up a Silverlight 2 or WPF application is the object tree.  WPF provides two conceptual views of the object tree called the logical tree and the visual tree that if combined would expose the full object tree.  The two conceptual trees are exposed via the LogicalTreeHelper and VisualTreeHelper static classes respectively.  The logical tree is a map of elements in the object tree’s element hierarchy (defined in Xaml or in code) that make up an applications user interface, including inferred elements not explicitly typed by the programmer.  The visual tree is a map of elements in the object tree’s element hierarchy that derive from Visual or Visual3D, each element is exploded into it’s constituent visual ingredient elements.

Silverlight does not expose the full object tree, there is more than likely a logical tree behind the scenes but there is currently no LogicalTreeHelper static class to expose it.  Silverlight does however expose a visual tree through VisualTreeHelper but the Visual and Visual3D types do not exist in the API.  The criteria for an object’s inclusion in the Silverlight visual tree according to MSDN is that the object has a "rendering implication".

LINQ to Visual Tree

So every Silverlight and WPF application has a Visual Tree exposed through VisualTreeHelper.  ‘LINQ to Visual Tree’ extends this model by enabling enumeration of the results returned from VisualTreeHelper for an object and it’s descendants.  The prototype is exactly two days old – I’m still experimenting at this stage, it currently consists of the following two extension methods:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
 
namespace LinqToVisualTree.Common
{
    public static class VisualEnumerable
    {
        /// <summary>
        /// Gets the Visual Tree filtered by Type for a DependencyObject with that DependencyObject as the root.
        /// </summary>
        public static IEnumerable<T> GetVisualOfType<T>(this DependencyObject element)
        {
            return GetVisualTree(element).Where(t => t.GetType() == typeof(T)).Cast<T>();
        }
 
        /// <summary>
        /// Gets the Visual Tree for a DependencyObject with that DependencyObject as the root.
        /// </summary>
        public static IEnumerable<DependencyObject> GetVisualTree(this DependencyObject element)
        {
            int childrenCount = VisualTreeHelper.GetChildrenCount(element);
 
            for (int i = 0; i < childrenCount; i++)
            {
                var visualChild = VisualTreeHelper.GetChild(element, i);
 
                yield return visualChild;
 
                foreach (var visualChildren in GetVisualTree(visualChild))
                {
                    yield return visualChildren;
                }
            }
        }
    }
}

            

Although there are currently only two extension methods, they provide an abundance of scope for querying the Visual Tree in Silverlight or WPF.  Let’s take a look at some usage scenarios from the code download to see how ‘LINQ to Visual Tree’ can be used in practice to query the Visual Tree of a Silverlight 2 application consisting of the following simple Xaml:

<UserControl x:Class="LinqToVisualTree.Sil.Tasks.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Loaded="Page_Loaded">
    <UserControl.Resources>
        <SolidColorBrush x:Key="ForegroundBrush" Color="#FFFF0000"/>
    </UserControl.Resources>
 
    <Grid x:Name="GridOne" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
 
        <TextBox x:Name="TextBoxOne" Text="One" Foreground="{StaticResource ForegroundBrush}" 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" Foreground="{StaticResource ForegroundBrush}" />
                </StackPanel>
            </Grid>
        </StackPanel>
    </Grid>
</UserControl>

             

Xaml similar to the above is also used for the test WPF application in the code download, for testing it’s really the hierarchy of the elements in the Xaml that is important – this can be visualized as follows:

Example Xaml Hierarchy

The code download defines seven tasks that demonstrate how to use the ‘LINQ to Visual Tree’ API, starting off with basic queries and progressing to slightly more involved ones.  Each task shows the code to produce the required result using both Extension Method syntax and LINQ syntax.  Each task is represented by a method called from the Page_Loaded event handler defined in the above Xaml:

          

/// <summary>
/// Task 1: Get the Visual Tree of this UserControl
/// </summary>
private void Task1()
{
    // Extension Method syntax
    var pageTree1 = this.GetVisualTree();
 
    // Linq syntax
    var pageTree2 = from v in this.GetVisualTree()
                    select v;
 
    Debug.Assert(pageTree1.SequenceEqual(pageTree2));
}

          

Silverlight Result WPF Result
Silverlight Task 01 Result Wpf Task 01 Result

          

/// <summary>
/// Task 2: Get all the TextBox controls in this UserControl's Visual Tree
/// </summary>
private void Task2()
{
    // Extension Method syntax
    var pageTextBoxes1 = this.GetVisualOfType<TextBox>();
 
    // Linq syntax
    var pageTextBoxes2 = from v in this.GetVisualOfType<TextBox>()
                        select v;
 
    Debug.Assert(pageTextBoxes1.SequenceEqual(pageTextBoxes2));
}

          

Silverlight Result WPF Result
Silverlight Task 02 Result Wpf Task 02 Result

          

/// <summary>
/// Task 3: Get the Visual Tree of GridTwo
/// </summary>
private void Task3()
{
    // Extension Method syntax
    var gridTwoTree1 = GridTwo.GetVisualTree();
 
    // Linq syntax
    var gridTwoTree2 = from v in GridTwo.GetVisualTree()
                       select v;
 
    Debug.Assert(gridTwoTree1.SequenceEqual(gridTwoTree2));
}

          

Silverlight Result WPF Result
Silverlight Task 03 Result Wpf Task 03 Result

          

/// <summary>
/// Task 4: Get all the TextBox controls in GridTwo's Visual Tree 
/// </summary>
private void Task4()
{
    // Extension Method syntax
    List<TextBox> gridTwoTextBoxes1 = GridTwo.GetVisualOfType<TextBox>().ToList();
 
    // Linq syntax
    List<TextBox> gridTwoTextBoxes2 = (from v in GridTwo.GetVisualOfType<TextBox>()
                                       select v).ToList();
 
    Debug.Assert(gridTwoTextBoxes1.SequenceEqual(gridTwoTextBoxes2));
}

          

Silverlight Result WPF Result
Silverlight Task 04 Result Wpf Task 04 Result

          

/// <summary>
/// Task 5: Get all TextBox controls with a red foreground in this UserControl's Visual Tree
/// </summary>
private void Task5()
{
    // Extension Method syntax
    List<TextBox> redTextBoxes1 = this.GetVisualOfType<TextBox>()
                                  .Where(t => t.Foreground == this.Resources["ForegroundBrush"])
                                  .ToList();
 
    // Linq syntax
    List<TextBox> redTextBoxes2 = (from t in this.GetVisualOfType<TextBox>()
                                   where t.Foreground == this.Resources["ForegroundBrush"]
                                   select t).ToList();
 
    Debug.Assert(redTextBoxes1.SequenceEqual(redTextBoxes2));
}

          

Silverlight Result WPF Result
Silverlight Task 05 Result Wpf Task 05 Result

          

/// <summary>
/// Task 6: Get all Grid controls in a StackPanel in this UserControl's Visual Tree
/// </summary>
private void Task6()
{
    // Extension Method syntax
    List<Grid> gridsInSp1 = (this.GetVisualOfType<StackPanel>()
                            .SelectMany(s => s.GetVisualOfType<Grid>())
                            ).ToList();
 
    // Linq syntax
    List<Grid> gridsInSp2 = (from s in this.GetVisualOfType<StackPanel>()
                            from g in s.GetVisualOfType<Grid>()
                            select g).ToList();
 
    Debug.Assert(gridsInSp1.SequenceEqual(gridsInSp2));
}

          

Silverlight Result WPF Result
Silverlight Task 06 Result Wpf Task 06 Result

          

/// <summary>
/// Task 7: Get all TextBox controls in a Grid in a StackPanel in this UserControl's Visual Tree
/// </summary>
private void Task7()
{
    // Extension Method syntax
    List<TextBox> textBoxesInGridInSp1 = (this.GetVisualOfType<StackPanel>()
                                        .SelectMany(s => s.GetVisualOfType<Grid>())
                                        .SelectMany(g => g.GetVisualOfType<TextBox>())
                                        ).ToList();
 
    // Linq syntax
    List<TextBox> textBoxesInGridInSp2 = (from s in this.GetVisualOfType<StackPanel>()
                                          from g in s.GetVisualOfType<Grid>()
                                          from t in g.GetVisualOfType<TextBox>()
                                          select t).ToList();
 
    Debug.Assert(textBoxesInGridInSp1.SequenceEqual(textBoxesInGridInSp2));
}

          

Silverlight Result WPF Result
Silverlight Task 07 Result Wpf Task 07 Result

          

Feel free to download the code and run the tasks to see how the API is used from both Silverlight and WPF.  As I say I’m still experimenting with this so I’d love to hear your suggestions and get feedback and ideas on how the simple API can be improved.

About these ads

5 Responses to “LINQ to Visual Tree (Beta)”

  1. Hi Peter,

    I was just fiddling around with the visual tree and was wondering whether Linq to Visual Tree might be a good option. A quick google search and here I am – great work.

    My only concern here is that your queries always operate on the flattened list … you cannot perform a query based on the tree like structure. Have you thought about this at all?

    Task 8: Get all TextBox controls that are children of Grids.

    Regards, Colin E.

  2. Hi Colin,

    Thanks for your comments, more exposure of the parent to querying is definitely a good idea and is on the todo list:

    – Include the hierarchy level in the output, instead of a flattened list produce something like a tree view.

    – More exposure of parent controls.

    – Finer grained control over queries based on the hierarchy, at present you can get all Textboxes in a Grid control, in future allow where the TextBox’s immediate parent is a Grid.

    – Possible interaction with a debugger visualizer that would display a diagram of a LINQ to Visual Tree query result.

    There are probably other things I’ll come across but if anyone has ideas not mentioned above I’d love to hear about them.

    Thanks again,

    Peter

  3. Hi Peter,

    It’s good to see that you are taking this idea further. I have also been working on a novel, yet very different mechanism for querying the visual tree. I’ll give you a poke when I have something to show.

    Regards, Colin E.

  4. Luke said

    Hi Peter – I hope you don’t mind, I took your code here and made a few changes to solve some issues I was having. I’ve a blog entry on it.

    http://www.lukepuplett.com/2009/11/linq-to-visual-tree.html

    Thanks for your original efforts.

    Luke

  5. Colin E. said

    Hi Peter,

    Finally got round to exploring the querying of trees a little further:

    http://www.codeproject.com/KB/linq/LinqToTree.aspx

    Colin E.

Sorry, the comment form is closed at this time.

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: