Freigeben über


SYSK 342: Learning WPF – Logical and Visual Element Tree

Whether you’re dealing with routed events or trying to figure out why your styling is not working quite as expected, you’ll need to understand these two new concepts in WPF… So, here it goes…

 

Consider the following XAML:

<Window x:Class="WPFWinApp.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="WPFWinApp" Height="300" Width="300"

    >

      <StackPanel>

            <Label>Please select one of the items below:</Label>

            <ListBox>

                  <ListBoxItem>Choice 1</ListBoxItem>

                  <ListBoxItem>Choice 2</ListBoxItem>

                  <ListBoxItem>Choice 3</ListBoxItem>

            </ListBox>

            <StackPanel Orientation="Horizontal">

                  <Button>OK</Button>

                  <Button>Cancel</Button>

            </StackPanel>

      </StackPanel>

</Window>

 

The logical tree is what you see described in XAML above and can be represented as follows (one dash per nesting level to indicate the nesting depth):

 

WPFWinApp.Window1

-System.Windows.Controls.StackPanel

--System.Windows.Controls.Label: Please select one of the items below:

---System.String: Please select one of the items below:

---System.Windows.Controls.ListBox Items.Count:3

----System.Windows.Controls.ListBoxItem: Choice 1

-----System.String: Choice 1

-----System.Windows.Controls.ListBoxItem: Choice 2

------System.String: Choice 2

------System.Windows.Controls.ListBoxItem: Choice 3

-------System.String: Choice 3

----System.Windows.Controls.StackPanel

-----System.Windows.Controls.Button: OK

------System.String: OK

------System.Windows.Controls.Button: Cancel

-------System.String: Cancel

 

 

The logical tree is known right after the InitializeComponent() method call in the constructor is completed. WPF provides a handy class called LogicalTreeHelper that allows you to easily see the logical tree hierarchy. Here is how I made use of it to produce the output above:

 

namespace WPFWinApp

{

    public partial class Window1 : System.Windows.Window

    {

        public Window1()

   {

            InitializeComponent();

            System.Diagnostics.Debug.WriteLine(GetLogicalTree(this, 0));

        }

        private string GetLogicalTree(object ctrl, int depth)

        {

            StringBuilder result = new StringBuilder(1024);

            if (ctrl.ToString().StartsWith(ctrl.GetType().FullName))

                result.AppendFormat(string.Format("{0}{1}\r\n", new string('-', depth), ctrl));

            else

                result.AppendFormat(string.Format("{0}{1}: {2}\r\n", new string('-', depth), ctrl.GetType().FullName, ctrl));

            if (ctrl is DependencyObject)

            {

                foreach(object child in LogicalTreeHelper.GetChildren(ctrl as DependencyObject))

                    result.Append(GetLogicalTree(child, ++depth));

            }

            return result.ToString();

        }

    }

}

 

 

A lot of runtime behavior, e.g. how the events are bubbled up, how ambient properties (a.k.a. property inheritance) affects your UI and much more is dependent on the logical tree.

 

 

Now, what is a visual tree?

 

A visual tree is a hierarchy of all elements that derive from System.Windows.Media.Visual or System.Windows.Media.Visual3D and that are created during rendering the UI.

 

For example, the logical tree above would result in the following visual tree:

 

 

WPFWinApp.Window1

-System.Windows.Controls.Border

--System.Windows.Documents.AdornerDecorator

---System.Windows.Controls.ContentPresenter

----System.Windows.Controls.StackPanel

-----System.Windows.Controls.Label: Please select one of the items below:

------System.Windows.Controls.Border

-------System.Windows.Controls.ContentPresenter

--------System.Windows.Controls.TextBlock

------System.Windows.Controls.ListBox Items.Count:3

-------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: Choice 1

---------------System.Windows.Controls.Border

----------------System.Windows.Controls.ContentPresenter

-----------------System.Windows.Controls.TextBlock

---------------System.Windows.Controls.ListBoxItem: Choice 2

----------------System.Windows.Controls.Border

-----------------System.Windows.Controls.ContentPresenter

------------------System.Windows.Controls.TextBlock

----------------System.Windows.Controls.ListBoxItem: Choice 3

-----------------System.Windows.Controls.Border

------------------System.Windows.Controls.ContentPresenter

-------------------System.Windows.Controls.TextBlock

-------------System.Windows.Documents.AdornerLayer

------------System.Windows.Controls.Primitives.ScrollBar Minimum:0 Maximum:0 Value:0

-------------System.Windows.Controls.Primitives.ScrollBar Minimum:0 Maximum:0 Value:0

-------System.Windows.Controls.StackPanel

--------System.Windows.Controls.Button: OK

---------Microsoft.Windows.Themes.ButtonChrome

----------System.Windows.Controls.ContentPresenter

-----------System.Windows.Controls.TextBlock

---------System.Windows.Controls.Button: Cancel

----------Microsoft.Windows.Themes.ButtonChrome

-----------System.Windows.Controls.ContentPresenter

------------System.Windows.Controls.TextBlock

----System.Windows.Documents.AdornerLayer

 

As you might expect, there is a VisualTreeHelper class, which I used as follows:

 

protected override void OnContentRendered(EventArgs e)

{

    base.OnContentRendered(e);

    // Remember, the visual tree is only available after the UI has been rendered

    System.Diagnostics.Debug.WriteLine(GetVisualTree(this, 0));

}

 

private string GetVisualTree(object ctrl, int depth)

{

    StringBuilder result = new StringBuilder(1024);

    if (ctrl.ToString().StartsWith(ctrl.GetType().FullName))

        result.AppendFormat(string.Format("{0}{1}\r\n", new string('-', depth), ctrl));

    else

        result.AppendFormat(string.Format("{0}{1}: {2}\r\n", new string('-', depth), ctrl.GetType().FullName, ctrl));

    if (ctrl is DependencyObject)

    {

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(ctrl as DependencyObject); i++)

        {

            result.Append(GetVisualTree(VisualTreeHelper.GetChild(ctrl as DependencyObject, i), ++depth));

        }

    }

    return result.ToString();

}

As you can see, the actual implementation information is available, and the knowledge of the elements in the visual tree is very useful when debugging style/theme related issues.

Important: it’s not recommended to write code that has built-in dependencies on the visual tree since applying a different theme/style is likely to break that type of logic.