An Application to Let You View WPF Logical Trees

In WPF, a logical tree is just the hierarchy of elements that make up your user interface.  If your user interface is defined in XAML, the logical tree is the set of elements from the XAML, organized into a tree based on their parent/child relationships.

Logical Tree Basics

The logical tree can also be thought of as a model that describes the relationships between objects at runtime.  Knowing the logical tree can be helpful in understanding:

  • Resource lookup
  • Property inheritance
  • Event routing

As an example, the logical tree for the following XAML:

 <Window x:Class="WpfApplication4.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="A window.." Height="350" Width="525">
     <StackPanel>
         <Button Content="Click Me" Height="23" HorizontalAlignment="Left" Width="75" Click="button1_Click" />
         <TextBox />
         <ListBox>
             <ListBoxItem Content="Barley"/>
             <ListBoxItem Content="Oats"/>
         </ListBox>
     </StackPanel>
 </Window>

can be represented by the following logical tree:

You can use the LogicalTreeHelper.GetChildren method to traverse a logical tree and enumerate all of the objects in the tree.

Every element in a logical tree is a DependencyObject, so you pass a top-level object that derives from DependencyObject into the GetChildren method.  The method returns a collection of children of that object.

 // Enumerate each immediate child of main window.  (Does NOT descend down tree)
 foreach (Object obj in LogicalTreeHelper.GetChildren(mainWindow as DependencyObject))
     Debug.WriteLine(obj.ToString());

An Application for Viewing Logical Trees

I wrote a little application that opens a specified .xaml file and then displays the logical tree from that .xaml file in a TreeView.

Here’s what the end result looks like:

To start, you just drag a .xaml file onto the surface of the application’s main window.  It then goes through the following steps:

  • Uses XamlReader.Load method to load the .xaml file into memory, creating objects to represent each element found in the .xaml file
  • Uses LogicalTreeHelper.GetChildren method to traverse the logical tree
  • For each node in the object tree, manually adds type name and (if it exists) object name to a TreeView

Current limitations:

  • Throws an exception if you try to load .xaml that includes an x:Class attribute  (you need to remove the attribute before loading the file)
  • Can’t load .xaml fragments if they don’t include the required XAML vocabulary namespaces
  • Have tested with Window as root element, but no guarantee that it works for other root elements

Where to Find WPF Logical Tree Application

You can find full source code on Codeplex, at wpflogicaltree.codeplex.com.

Under the Covers

The GUI for the application is simple–basically just a Label and a TreeView:

 <Window x:Class="DisplayWpfTrees.MainWindow"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="Drag XAML Files onto This Window to See WPF Logical Tree" Height="350" Width="525" AllowDrop="True"
     Drop="Window_Drop" >
     <ScrollViewer VerticalScrollBarVisibility="Auto">
         <StackPanel Name="spMain" >
             <Label Content="Logical Tree" Height="28" Background="AliceBlue"/>
             <TreeView Name="tvLogical" />
         </StackPanel>
     </ScrollViewer>
 </Window>

You use drag and drop to tell the application to load a .xaml file.  The DragEnter event allows limiting the application to files being dropped.  The Drop event picks up the name of the file being dropped and then calls a helper class to do the loading and initialization of the TreeView.


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xaml;

namespace DisplayWpfTrees
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// DragEnter allows prohibiting disallowed formats on drop
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_DragEnter(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effects = DragDropEffects.None;
                e.Handled = true;
            }
        }

        /// <summary>
        /// DragEnter allows prohibiting disallowed formats on drop
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_DragOver(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effects = DragDropEffects.None;
                e.Handled = true;
            }
        }

        /// <summary>
        /// When file is dropped on window, dump its logical tree to our TreeView
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Drop(object sender, DragEventArgs e)
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

            string filename = files[0];     // Only deal with first one
            FileInfo fi = new FileInfo(filename);

            if (fi.Extension == ".xaml")
            {
                tvLogical.Items.Clear();

               MyLogicalTreeHelper.LoadAndAddXAMLFile(tvLogical, filename);
            }

            e.Handled = true;
        }

    }
}

Finally, the MyLogicalTreeHelper class includes the method LoadAndAddXAMLFile, which does the work of loading the file and building up the TreeView elements.


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace DisplayWpfTrees
{
    public class MyLogicalTreeHelper
    {
        /// <summary>
        /// Load loose XAML, creating object graph, then dump that graph's logical
        ///   tree to a TreeView.
        /// </summary>
        /// <param name="tv"></param>
        /// <param name="filename"></param>
        public static void LoadAndAddXAMLFile(TreeView tv, string filename)
        {
            try
            {
                // Load loose XAML file
                DependencyObject root = (DependencyObject)LoadXAMLObjectGraphFromFile(filename);

                // Descend through logical tree, adding items to TreeView
                DumpLogicalTreeToTreeView(root, tv.Items);

                // If root object is Window, we need to call Close method to avoid process from
                //   running indefinitely.  ??
                Window win = root as Window;
                if (win != null)
                    win.Close();
            }
            catch (Exception xx)
            {
                MessageBox.Show(string.Format("Error while trying to load .xaml: {0}", xx.Message));
            }
        }

        /// <summary>
        /// Given filename, use XamlReader to construct XAML objects by reading the file
        /// </summary>
        /// <param name="xmlString"></param>
        /// <returns></returns>
        private static object LoadXAMLObjectGraphFromFile(string filename)
        {
            using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
            {
                return System.Windows.Markup.XamlReader.Load(fs);
            }
        }

        /// <summary>
        /// Add children of specified parent to TreeView, under specified node
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="lvi"></param>
        private static void DumpLogicalTreeToTreeView(object parent, ItemCollection tvItems)
        {
            TreeViewItem tvi = new TreeViewItem();
            tvi.Header = XamlElementAsString(parent);
            tvItems.Add(tvi);

            if (!(parent is DependencyObject))
                return;

            foreach (object child in LogicalTreeHelper.GetChildren(parent as DependencyObject))
            {
                DumpLogicalTreeToTreeView(child, tvi.Items);
            }
        }

        /// <summary>
        /// Render a XAML element as a string that contains full type name and optional
        ///   element Name.
        /// </summary>
        /// <param name="theElement"></param>
        /// <returns></returns>
        private static string XamlElementAsString(object theElement)
        {
            string asString;

            // Simple elements (e.g. text content), just convert to string
            if (!(theElement is DependencyObject))
                asString = theElement.ToString();

            else
            {
                // Get full type name
                Type objType = theElement.GetType();
                asString = objType.ToString();

                // Optionally, if the element has a name, append it
                PropertyInfo pi = objType.GetProperty("Name");
                if (pi != null)
                {
                    string name = (string)pi.GetValue(theElement, null);
                    if ((name != null) && (name != ""))
                        asString = string.Format("{0}: ({1})", asString, name);
                }
            }

            return asString;
        }
    }
}

Next Steps

This is all pretty clunky, but it works in a limited fashion.  Obvious next steps include:

  • Data bind the TreeView using HierarchicalDataTemplate, rather than building it up manually
  • Helper class should just build up an object model without having any knowledge of GUI elements (like the TreeView)
  • Automatically strip out x:Class attributes so that files can contain them
  • Better understanding of why process runs indefinitely if you don’t invoke Window.Close
  • Add support for also displaying the visual tree
Advertisements

2 thoughts on “An Application to Let You View WPF Logical Trees

  1. Pingback: #110 – An Application for Viewing a WPF Logical Tree « 2,000 Things You Should Know About WPF

  2. Pingback: #113 – An Application for Viewing a WPF Visual Tree « 2,000 Things You Should Know About WPF

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s