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

Understanding the Visual Tree and Logical Tree in WPF

By , 5 Dec 2007
Rate this:
Please Sign up or sign in to vote.

Introduction

This article discusses nuances of and differences between the visual tree and logical tree in WPF. It also presents a small application with which you can investigate this topic further. If you are completely unfamiliar with the concepts of the visual tree and/or logical tree, I suggest you read this page in the SDK documentation first.

Background

The existing documentation about the visual tree and logical tree in the Windows SDK leaves much to be desired. Ever since I started with WPF, I have felt unsure about what exactly differentiates the two. I knew that the logical tree and visual tree both only contain visual elements and controls, right? Wrong. I knew that a Window/Page/Control/etc. contains one and only one logical tree, right? Wrong. I knew what I was doing, right? Wrong.

It turns out that the element trees in WPF are rather complicated and require detailed knowledge of low-level WPF classes to work with them correctly. Walking an element tree in a generic fashion; where you assume no knowledge of its constituents; is not as simple as it might seem. Unfortunately WPF does not publicly expose a class which simplifies walking the element trees to the point where it is "really easy".

At this point you might be wondering what makes walking the element trees so complicated. Good question. The answer has several parts, which are discussed in the following sections.

The Visual Tree

The visual tree represents all of the elements in your UI which render to an output device (typically, the screen). The visual tree is used for many things like rendering, event routing, locating resources (if an element has no logical parent), and more. Walking up and down the visual tree can be a simple matter of just using VisualTreeHelper and some simple recursive methods.

However, there is one wrinkle which makes this a little more complicated. Anything which descends from ContentElement can appear in a user interface, but is not actually in the visual tree. WPF will "pretend" that those elements are in the visual tree, to facilitate consistent event routing, but it's just an illusion. VisualTreeHelper does not work with ContentElement objects because ContentElement does not derive from Visual or Visual3D. Here are all of the classes in the Framework which descend from ContentElement, as seen in Reflector:

Classes that descend from ContentElement

Here is how the docs explain what it means that ContentElements are not really in the visual tree:

Content elements (derived classes of ContentElement) are not part of the visual tree; they do not inherit from Visual and have no visual representation. In order to appear in a UI at all, a ContentElement must be hosted within a content host that is a Visual, usually a FrameworkElement. You can conceptualize that the content host is somewhat like a "browser" for the content and chooses how to display that content within the screen region the host controls. Once the content is hosted, the content can be made a participant in certain tree processes that are normally associated with the visual tree. Generally the FrameworkElement host class includes implementation code that adds any hosted ContentElement to the event route through subnodes of the content logical tree, even though the hosted content is not part of the true visual tree. This is necessary so that a ContentElement can source a routed event that routes to any element other than itself.

So what does all this mean? Well, it means that you can't always just use VisualTreeHelper to traverse the visual tree. If you pass a ContentElement to VisualTreeHelper's GetParent or GetChild methods, an exception will be thrown because ContentElement does not derive from Visual or Visual3D. In order to walk up the visual tree, you need to check each element along the way to see if it descends from Visual or Visual3D, and if it does not, then you must temporarily walk up the logical tree until you encounter another visual object. For example, here's some code which walks up to the root element in a visual tree:

DependencyObject FindVisualTreeRoot(DependencyObject initial)
{
    DependencyObject current = initial;
    DependencyObject result = initial;

    while (current != null)
    {
        result = current;
        if (current is Visual || current is Visual3D)
        {
            current = VisualTreeHelper.GetParent(current);
        }
        else
        {
            // If we're in Logical Land then we must walk 
            // up the logical tree until we find a 
            // Visual/Visual3D to get us back to Visual Land.
            current = LogicalTreeHelper.GetParent(current);
        }
    }

    return result;
}

This code walks up the logical tree when necessary, as seen in the else clause. This is useful if, say, the user clicks on a Run element within a TextBlock and in your code you need to walk up the visual tree starting at that Run. Since the Run class descends from ContentElement, the Run is not "really" in the visual tree so we need to walk up out of "Logical Land" until we encounter the TextBlock which contains the Run. At that point we will be back in "Visual Land" since TextBlock is not a ContentElement subclass (i.e. it is a real part of a visual tree).

The Logical Tree

The logical tree represents the essential structure of your UI. It closely matches the elements you declare in XAML, and excludes most visual elements created internally to help render the elements you declared. WPF uses the logical tree to determine several things including dependency property value inheritance, resource resolution, and more.

Working with the logical tree is not nearly as clear-cut as the visual tree. For starters, the logical tree can contain objects of any type. This differs from the visual tree, which only contains instances of DependencyObject subclasses. When working with the logical tree, you must keep in mind a leaf node in the tree (a terminus) can be of any type. Since LogicalTreeHelper only works with DependencyObject subclasses, you need to be careful about type checking the objects while walking down the tree. For example:

void WalkDownLogicalTree(object current)
{
    DoSomethingWithObjectInLogicalTree(current);

    // The logical tree can contain any type of object, not just 
    // instances of DependencyObject subclasses.  LogicalTreeHelper
    // only works with DependencyObject subclasses, so we must be
    // sure that we do not pass it an object of the wrong type.
    DependencyObject depObj = current as DependencyObject;

    if (depObj != null)
        foreach(object logicalChild in LogicalTreeHelper.GetChildren(depObj))
            WalkDownLogicalTree(logicalChild);
}

A given Window/Page/Control will have one visual tree, but can contain any number of logical trees. Those logical trees are not connected to each other, so you cannot just use LogicalTreeHelper to navigate between them. In this article, I refer to the top level control's logical tree as the "main logical tree" and all of the other logical trees within it as "logical islands". Logical islands are really just regular logical trees, but I think that the term "island" helps to convey the fact that they are not connected to the main logical tree.

This weirdness can all be boiled down to one word: templates.

Controls and data objects have no intrinsic visual appearance; instead they rely on templates to explain how they should render. A template is like a cookie-cutter which can be "expanded" to create real live visual elements used to render something. The elements that are part of an expanded template, hereafter referred to as "template elements", form their own logical tree which is disconnected from the logical tree of the object for which they were created. Those little logical trees are what I refer to as "logical islands" in this article.

You have to write extra code if you need to jump between logical islands/trees. Bridging those logical islands together, while walking up logical trees, involves making use of the TemplatedParent property of FrameworkElement or FrameworkContentElement. TemplatedParent returns the element which has the template applied to it, and, thus, contains a logical island. Here is a method which finds the TemplatedParent of any element:

DependencyObject GetTemplatedParent(DependencyObject depObj)
{
    FrameworkElement fe = depObj as FrameworkElement;
    FrameworkContentElement fce = depObj as FrameworkContentElement;

    DependencyObject result;
    if (fe != null)
        result = fe.TemplatedParent;
    else if (fce != null)
        result = fce.TemplatedParent;
    else
        result = null;

    return result;
}

Walking down the logical tree and jumping from one tree to the next is more difficult because there is no TemplatedChild property. You need to check the visual children of an element at the end of one logical tree, and see if those children (or perhaps their descendants) are members of a different logical tree. That code is left as an exercise for the reader to create.

The Research Tool

This article is accompanied by a small console application which allows you to experiment with and investigate the element trees. It opens a WPF Window and will write out to a console window either the visual tree or logical tree for any element on which you click. The instructions for how to use it appear in the Window, so let's just see what it looks like and what information it provides us.

When you start the app, it looks like this:

After maximizing the console window and moving the WPF Window over it, I held down Ctrl and left-clicked on the Button in the middle of the Window (but not over the display text). At that point, the app dumped the logical tree of the ButtonChrome element on which I clicked, and it looked like this:

Notice that the [YOU CLICKED HERE] text appears on the line in the console window which represents a Button, not the on the line for the ButtonChrome element on which I actually clicked. That's because this logical tree does not care about or contain ButtonChrome elements, which is simply a rendering artifact created by Button's default control template.

Since Button is a ContentControl, it also has a ContentPresenter within its visual tree. The ContentPresenter is what hosts and displays the content seen in the Button, which is the string "Clear the console window". That string is rendered by expanding a simple data template which displays the text in a TextBlock element.

If I were to hold Ctrl and left-click on the text itself, meaning on the TextBlock element, the console window displays this:

Notice how the logical tree is very different now. It is much smaller than before; the root is a ButtonChrome instead of a Button; and the terminus is a ContentPresenter instead of a string. The reason for this change is that we are now looking at a logical island. This logical island is the logical tree of the template elements created to display the content of the Button.

If we were to hold Ctrl and right-click on the ButtonChrome (or anywhere else for that matter), the console window would display the entire visual tree and indicate on which element we clicked. It would look like this:

Obviously the visual tree seen here is much larger than the original logical tree seen previously. It is interesting to note that the visual tree contains all of the visual elements involved with the Button being examined earlier. It does not care about whether elements come from templates or not, which makes it much easier to understand.

Conclusion

At first glance, the element trees in WPF might seem to be fairly self-explanatory. Upon closer examination, though, it becomes apparent that they are not that simple. For most WPF programming tasks it is not important to be familiar with these details, but for some more advanced scenarios it becomes crucial information. Hopefully this article has helped shed some light on these arcane details.

Revision History

  • November 30, 2007 - Created the article
  • December 5, 2007 - Fixed the article's links after the CodeProject site upgrade munged them

License

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

About the Author

Josh Smith
Software Developer (Senior) Cynergy Systems
United States United States
Josh creates software, for iOS and Windows.
 
He works at Cynergy Systems as a Senior Experience Developer.
 
Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.
 
Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.
 
Check out his Advanced MVVM[^] book.
 
Visit his WPF blog[^] or stop by his iOS blog[^].
Follow on   Twitter

Comments and Discussions

 
BugConverting to Visual Studio 2010 fails Pinmemberjohnyjj223-Mar-13 6:34 
AnswerRe: Converting to Visual Studio 2010 fails Pinmemberquadpus30-Mar-13 3:50 
QuestionPlease include reference to object hierarchy for the newbies Pinmemberdevvvy31-Jan-13 18:50 
GeneralMy vote of 5 Pinmemberabjr25-Nov-12 3:56 
GeneralBest article i read in wpf tree PinmemberCandyJoin23-May-12 22:53 
QuestionSharing Pinmemberdevvvy5-Jul-11 23:34 
GeneralMy vote of 5 Pinmembervalery possoz30-May-11 23:53 
GeneralSeems like <Button> breaks the chain (in walking logical tree...) Pinmemberdevvvy7-Apr-11 0:19 
GeneralMy vote of 5 Pinmembercplotts14-Sep-10 5:03 
QuestionTest The Tree Working By Clicking on "TREE FAILS" Pinmemberpc.rajesh.singh26-Nov-09 7:39 
NewsHere is another explanation of the WPF element trees PinmvpJosh Smith3-Jun-08 2:30 
GeneralRe: Here is another explanation of the WPF element trees Pinmemberdevvvy13-Apr-11 18:46 
GeneralLogicalTree Question Pinmemberken_roberts@intuit.com12-Mar-08 9:24 
GeneralRe: LogicalTree Question PinmvpJosh Smith13-Mar-08 3:12 
GeneralRe: LogicalTree Question Pinmemberken_roberts@intuit.com13-Mar-08 3:31 
GeneralRe: LogicalTree Question PinmvpJosh Smith13-Mar-08 3:36 
GeneralVery Nice PinmemberPaul Conrad1-Dec-07 16:35 
GeneralRe: Very Nice PinmvpJosh Smith1-Dec-07 19:00 
GeneralGreat Article Pinmembermarlongrech30-Nov-07 9:39 
GeneralRe: Great Article PinmvpJosh Smith30-Nov-07 9:45 
GeneralVery useful as usual PinmemberSacha Barber30-Nov-07 4:34 
GeneralRe: Very useful as usual PinmvpJosh Smith30-Nov-07 4:43 
GeneralNavigating WPF''s Forest PinmemberKarl Shifflett30-Nov-07 0:26 
GeneralRe: Navigating WPF''s Forest PinmvpJosh Smith30-Nov-07 1:49 

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
Web01 | 2.8.140415.2 | Last Updated 5 Dec 2007
Article Copyright 2007 by Josh Smith
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid