- A Tour of the Class Hierarchy
- Logical and Visual Trees
- Dependency Properties
- Summary
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. The rendered dialog from Listing 3.1.
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. 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
(!(objis
DependencyObject
))return
;// Recursive call for each logical child
foreach
(object
childin
LogicalTreeHelper
.GetChildren( objas
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.