Logical and Visual Trees

Summary

 

Introduction

Windows Presentation Foundation (WPF) uses several tree structure metaphors to define relationships between program elements. The primary tree structure in WPF is the element tree. If you create an application page in XAML, then the tree structure is created based on the nesting relationships of the elements in the markup. understanding the concepts of how the trees interact is a way to understand how property inheritance and event routing work in WPF.

Logical Trees

In WPF, you add content to elements using properties. For example, you add items to a ListBox control using its Items property. To add elements to a DockPanel, you use its Children property, and so on. In WPF, user interfaces are constructed from a tree of objects known as a logical tree. A logical tree exists even for WPF user interfaces that aren't created in XAML. The logical tree exists so that content models can readily iterate over their possible child elements, and so that content models can be extensible. Also, the logical tree provides a framework for certain notifications, such as when all elements in the logical tree are loaded. For example, for a ListBox with one ListViewItem displaying a string, the logical tree is this:

System.Windows.Controls.ListBox
    System.Windows.Controls.ListBoxItem
        System.String

Why should you care about logical trees? Because just about every aspect of WPF (properties, events, resources, and so on) has behavior tied to the logical tree. For example, property values are sometimes propagated down the logical tree to child elements automatically, and raised events can travel up or down the tree. In addition, resource references are resolved by looking upwards through the logical tree for Resources collections on the initial requesting element and then parent elements.

Visual Trees

A similar concept to the logical tree is the visual tree. A visual tree is basically an expansion of a logical tree, in which nodes are broken down into their core visual components. For example, if a ListBox was an element in a logical tree, then the visual tree will have all the core visual component making up the ListBox such as the two scroll bars, borders, edit areas, etc., then The visual tree describes the structure of visuals represented by the Visual base class. When you write a template for a control, you are defining or redefining the visual tree that applies for that control. For example, for a ListBox with one ListViewItem displaying a string  (logical tree shown above) the visual tree is this:

System.Windows.Controls.ListBox
 System.Windows.Controls.Border
  System.Windows.Controls.ScrollViewer
    System.Windows.Controls.Grid
     System.Windows.Shapes.Rectangle
     System.Windows.Controls.ScrollContentPresenter
      System.Windows.Controls.ItemsPresenter
       System.Windows.Controls.VirtualizingStackPanel
        System.Windows.Controls.ListBoxItem
         System.Windows.Controls.Border
          System.Windows.Controls.ContentPresenter
           System.Windows.Controls.TextBlock
      System.Windows.Documents.AdornerLayer
     System.Windows.Controls.Primitives.ScrollBar
     System.Windows.Controls.Primitives.ScrollBar

The following briefly describes some of the classes shown in the visual tree:

You often don't need to worry about visual trees unless you're doing low-level drawing. For example, although a ListBox is logically a single control, its default visual representation is composed of more primitive WPF elements: a Border, two ScrollBars, and many other visual elements. All controls that have a rendering behaviour (those derive from System.Windows.Media.Visual or System.Windows.Media.Visual3D) will appear in the visual tree. However, unlike the visual tree, the logical tree can represent nonvisual data objects, such as ListItem. One exposure of the visual tree as part of conventional WPF application programming is that event routes for a routed event mostly travel along the visual tree, not the logical tree.

Trees and Content Elements

Content Elements are elements that inherit from ContentElement class. ContentElement class is a core-level base-class that defines common content characteristics such as basic input from keyboard, mouse, drag-and-drop, focus, and events. Content elements are not part of the visual tree; they do not inherit from Visual and do not have a visual representation. In order to appear in a UI at all, a ContentElement must be hosted in a content host that is both a Visual and a logical tree element, usually a FrameworkElement.

Trees and Elements

When you instantiate a WPF class, several aspects of object initialization are not part of class constructor code. Particularly for a control class, most of the visual representation of that control is not defined by the constructor. Instead, the visual representation is defined by the control's template. The template potentially comes from a variety of sources, but most often the template is obtained from theme styles. Templates are effectively late-binding; the necessary template is not attached to the control in question until the control is ready for layout. And the control is not ready for layout until it is attached to a logical tree that connects to a rendering surface at the root. It is that root-level element that initiates the rendering of all of its child elements as defined in the logical tree.

Examples

Tree Traversal

The LogicalTreeHelper class provides the GetChildren, GetParent, and FindLogicalNode methods for logical tree traversal. In most cases, you should not have to traverse the logical tree of existing controls, because these controls almost always expose their logical child elements as a dedicated collection property that supports collection APIs such as Add, an indexer, and so on. The visual tree also supports a helper class for visual tree traversal, VisualTreeHelper.

The figure below shows XamlPad rendering a simple XAML code. XamlPad contains a button in its toolbar that reveals the visual tree (and property values) for any XAML that it renders. It doesn't work when hosting a Window, but you can change the Window element to a Page (and remove the SizeToContent property) to take advantage of this functionality:

To programmatically traverse both the logical and visual trees, you can use System.Windows.LogicalTreeHelper and System.Windows.Media.VisualTreeHelper classes, respectively.

The following code shows how to traverse both the visual tree and the logical tree of the XAML given above. Note that whereas a logical tree is static without programmer intervention (such as dynamically adding/removing elements), a visual tree can change simply by a user switching to a different Windows theme. Therefore, avoid writing code that depends on a specific visual tree. Also note that while the logical tree can be traversed within a Window's constructor, the visual tree is empty until the Window undergoes layout at least once. That is why PrintVisualTree must be called after content has been rendered (not before OnContentRendered event was called):

<!-- Sample XAML as above except that the button is used to invoke tree traversal -->
<Window x:Class="WPFTrees.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPFTrees" Height="300" Width="300">
    <DockPanel LastChildFill="True">
         <StackPanel DockPanel.Dock="Top" Height="100" Background="LightBlue">
            <TextBlock>Upper StackPanel</TextBlock>
        </StackPanel>
 
        <StackPanel Background="LightGreen">
            <TextBlock>Lower StackPanel</TextBlock>
            <GroupBox Name="GroupBox" Height="100" Header="Group of Buttons" Width="200">
                <Button Content="Element Trees" Height="20" Width="100" Click="VisualTree_Handler"/>
            </GroupBox>
        </StackPanel>
    </DockPanel>
</Window>

public partial class Window1 : System.Windows.Window
{
    public Window1()
    {
        InitializeComponent();
    }
 
    // Button handler
    private void VisualTree_Handler(object sender, RoutedEventArgs args)
    {
        PrintLogicalTree(0, this);
        PrintVisualTree(0, this);
    }
 
    private void PrintLogicalTree(int depth, object obj)
    {
        // Print the object with preceding spaces that represent its depth
        Trace.WriteLine(new string(' ', depth) + obj);
 
        // Sometimes leaf nodes aren't DependencyObjects (e.g. strings)
        if (!(obj is DependencyObject)) return;
 
        // Recursive call for each logical child
        foreach (object child in LogicalTreeHelper.GetChildren(obj as DependencyObject))
            PrintLogicalTree(depth + 1, child);
    }
 
    // Traversing a visual tree. Note the use of GetChildrenCount and GetChild. VisualTreeHelper
    // does not have a GetChildren method
    private void PrintVisualTree(int depth, object obj)
    {
        // Print the object with preceding spaces that represent its depth
        Trace.WriteLine(new string(' ', depth) + obj);
 
        // Recursive call for each visual child
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj as DependencyObject); i++)
            PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj as DependencyObject, i));
    }
}

 The following shows output from traversing both trees:

WPFTrees.Window1                    // Start of logical tree
 System.Windows.Controls.DockPanel
  System.Windows.Controls.StackPanel
   System.Windows.Controls.TextBlock
    Upper StackPanel
  System.Windows.Controls.StackPanel
   System.Windows.Controls.TextBlock
    Lower StackPanel
   System.Windows.Controls.GroupBox Header:Group of Buttons Content:Element Trees
    Group of Buttons
    System.Windows.Controls.Button: Element Trees
     Element Trees

WPFTrees.Window1                // Start of visual tree
 System.Windows.Controls.Border
  System.Windows.Documents.AdornerDecorator
   System.Windows.Controls.ContentPresenter
    System.Windows.Controls.DockPane
     System.Windows.Controls.StackPanel
      System.Windows.Controls.TextBlock
     System.Windows.Controls.StackPanel
      System.Windows.Controls.TextBlock
      System.Windows.Controls.GroupBox Header:Group of Buttons Content:Element Trees
       System.Windows.Controls.Grid
        System.Windows.Controls.Border
        System.Windows.Controls.Border
         System.Windows.Controls.ContentPresenter
          System.Windows.Controls.TextBlock
        System.Windows.Controls.ContentPresenter
         System.Windows.Controls.Button: Element Trees
          Microsoft.Windows.Themes.ClassicBorderDecorator
           System.Windows.Controls.ContentPresenter
            System.Windows.Controls.TextBlock
        Microsoft.Windows.Themes.ClassicBorderDecorator
   System.Windows.Documents.AdornerLayer

Accessing Specific Visual Elements

The following example shows how VisualTreeHelper can be used to access and change the background colour a ListBox scrollbars:

// The input is assumed to be a ListBox object.
public static void GetScrollBars(object obj)
{
    // Get all children and examine if the child is a ScrollBar
    object obChild;
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj as DependencyObject); i++)
    {
        obChild = VisualTreeHelper.GetChild(obj as DependencyObject, i);
        if (obChild is ScrollBar)
        {
            // We found scroll bars. Change background color based on scroll bar orientation
            if (((ScrollBar)obChild).Orientation == System.Windows.Controls.Orientation.Horizontal)
                ((ScrollBar)obChild).Background = Brushes.Aquamarine;
            else
                ((ScrollBar)obChild).Background = Brushes.Orange;
        }
        else
            GetScrollBars(obChild);

    }
}