Home > Articles > Programming

WPF Fundamentals

  • Print
  • + Share This
This chapter is from the book

This chapter is from the book

Logical and Visual Trees

XAML is natural for representing a user interface because of its hierarchical nature. In WPF, user interfaces are constructed from a tree of objects known as a logical tree.

Listing 3.1 defines the beginnings of a hypothetical About dialog, using a Window as the root of the logical tree. The Window has a StackPanel child element (described in Chapter 5) containing a few simple controls plus another StackPanel that contains Buttons.

LISTING 3.1. A Simple About Dialog in XAML

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  Title="About WPF 4.5 Unleashed" SizeToContent="WidthAndHeight"
  Background="OrangeRed">
  <StackPanel>
    <Label FontWeight="Bold" FontSize="20" Foreground="White">
      WPF 4.5 Unleashed
    </Label>
    <Label>© 2013 SAMS Publishing</Label>
    <Label>Installed Chapters:</Label>
    <ListBox>
      <ListBoxItem>Chapter 1</ListBoxItem>
      <ListBoxItem>Chapter 2</ListBoxItem>
    </ListBox>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
      <Button MinWidth="75" Margin="10">Help</Button>
      <Button MinWidth="75" Margin="10">OK</Button>
    </StackPanel>
    <StatusBar>You have successfully registered this product.</StatusBar>
  </StackPanel>
</Window>

Figure 3.2 shows the rendered dialog (which you can easily produce by pasting the content of Listing 3.1 into a tool such as the XAMLPAD2009 sample from the previous chapter), and Figure 3.3 illustrates the logical tree for this dialog.

FIGURE 3.2

FIGURE 3.2. The rendered dialog from Listing 3.1.

FIGURE 3.3

FIGURE 3.3. The logical tree for Listing 3.1.

Note that a logical tree exists even for WPF user interfaces that aren’t created in XAML. Listing 3.1 could be implemented entirely in C#, and the logical tree would be identical.

The logical tree concept is straightforward, but why should you care about it? 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 tree to child elements automatically, and raised events can travel up or down the tree. This behavior of property values is discussed later in this chapter, and this behavior of events is discussed in Chapter 6.

The logical tree exposed by WPF is a simplification of what is actually going on when the elements are rendered. The entire tree of elements actually being rendered is called the visual tree. You can think of the visual tree as an expansion of a logical tree, in which nodes are broken down into their core visual components. Rather than leaving each element as a “black box,” a visual tree exposes the visual implementation details. 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 more.

Not all logical tree nodes appear in the visual tree; only the elements that derive from System.Windows.Media.Visual or System.Windows.Media.Visual3D are included. Other elements (and simple string content, as in Listing 3.1) are not included because they don’t have inherent rendering behavior of their own.

Figure 3.4 illustrates the default visual tree for Listing 3.1. This diagram exposes some inner components of the user interface that are currently invisible, such as the ListBox’s two ScrollBars and each Label’s Border. It also reveals that Button, Label, and ListBoxItem are all composed of the same elements. (These controls have other visual differences as the result of different default property values. For example, Button has a default Margin of 10 on all sides, whereas Label has a default Margin of 0.)

FIGURE 3.4

FIGURE 3.4. The visual tree for Listing 3.1, with logical tree nodes emphasized.

Because they enable you to peer inside the deep composition of WPF elements, visual trees can be surprisingly complex. Fortunately, although visual trees are an essential part of the WPF infrastructure, you often don’t need to worry about them unless you’re radically restyling controls (covered in Chapter 14, “Styles, Templates, Skins, and Themes”) or doing low-level drawing (covered in Chapter 15). Writing code that depends on a specific visual tree for a Button, for example, breaks one of WPF’s core tenets—the separation of look and logic. When someone restyles a control such as Button using the techniques described in Chapter 14, its entire visual tree is replaced with something that could be completely different.

However, you can easily traverse both the logical and visual trees using the somewhat symmetrical System.Windows.LogicalTreeHelper and System.Windows.Media.VisualTreeHelper classes. Listing 3.2 contains a code-behind file for Listing 3.1 that, when run under a debugger, outputs a simple depth-first representation of both the logical and visual trees for the About dialog. (This requires adding x:Class="AboutDialog" and the corresponding xmlns:x directive to Listing 3.1 in order to hook it up to this procedural code.)

LISTING 3.2. Walking and Printing the Logical and Visual Trees

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;

public partial class AboutDialog : Window
{
  public AboutDialog()
  {
    InitializeComponent();
    PrintLogicalTree(0, this);
  }

  protected override void OnContentRendered(EventArgs e)
  {
    base.OnContentRendered(e);
    PrintVisualTree(0, this);
  }

  void PrintLogicalTree(int depth, object obj)
  {
    // Print the object with preceding spaces that represent its depth
    Debug.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);
  }

  void PrintVisualTree(int depth, DependencyObject obj)
  {
    // Print the object with preceding spaces that represent its depth
    Debug.WriteLine(new string(' ', depth) + obj);

    // Recursive call for each visual child
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
      PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));
  }
}

When calling these methods with a depth of 0 and the current Window instance, the result is a text-based tree with exactly the same nodes shown in Figures 3.2 and 3.3. Although the logical tree can be traversed within Window’s constructor, the visual tree is empty until the Window undergoes layout at least once. That is why PrintVisualTree is called within OnContentRendered, which doesn’t get called until after layout occurs.

Navigating either tree can sometimes be done with instance methods on the elements themselves. For example, the Visual class contains three protected members (VisualParent, VisualChildrenCount, and GetVisualChild) for examining its visual parent and children. FrameworkElement, the common base class for controls such as Button and Label, and its peer FrameworkContentElement both define a public Parent property representing the logical parent and a protected LogicalChildren property for the logical children. Subclasses of these two classes often publicly expose their logical children in a variety of ways, such as in a public Children collection. Some classes, such as Button and Label, expose a Content property and enforce that the element can have only one logical child.

  • + Share This
  • 🔖 Save To Your Account