WPF Commands – A Pattern that Works

Using commands in WPF can be frustrating. Having so many different ways to do commanding in WPF leads to some confusion. There are multiple ways to wire up commands that are equally correct, though some patterns are cleaner than others.

Below is a pattern that works.

Create static property to expose command

Making the command a property rather than a variable makes the syntax in XAML a bit cleaner.  Using RoutedUICommand allows setting the text for the command. This is useful if you want to use the command in multiple places.

Here’s code for creating the command, e.g. in a MainWindow class:

private static RoutedUICommand _pressMeCommand = new RoutedUICommand("Press Me", "PressMe", typeof(MainWindow));
public static RoutedUICommand PressMeCommand
{
    get { return _pressMeCommand; }
}

Add CanExecute and Executed handlers

Add code in code-behind (or ViewModel) for handlers. Below, we have private handlers in MainWindow. In this example, we have a boolean indicating whether the command can be executed. You could also put the logic directly in CanExecute, if it’s not used elsewhere.

private void PressMe_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = CowboyCanTalk;
}

private void PressMe_Executed(object sender, ExecutedRoutedEventArgs e)
{
    MessageBox.Show("Howdy howdy I'm a cowboy");
}

Set up command binding

You can do this from code or XAML.  Doing it in XAML is perhaps a bit cleaner. Below is an example of binding the above command to its handlers from XAML (for MainWindow). Note that we can just use command name instead of {x:Static syntax because we made the command a property.

<Window.CommandBindings>
    <CommandBinding Command="local:MainWindow.PressMeCommand"
                    CanExecute="PressMe_CanExecute"
                    Executed="PressMe_Executed"/>
</Window.CommandBindings>

 

Wire up a button

Below, we wire a button up to the command.  You could just set Content to text directly. This would be fine to do and is simpler than what we’re doing below. But putting the text into the RoutedUICommand is helpful if you use the command in more than one place (since you specify text in just one place—in the command). And menu items automatically pick up the command text. (NOTE: If you used this pattern for Content regularly, you could just put it in a style to hide the complexity).

<Button Command="local:MainWindow.PressMeCommand"
        Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}" />

Wire up a menu item

Below, we wire up a menu item in a context menu to the same command. Note that here we do have to use the x:Static syntax. But also note that we don’t need to specify text for the menu item—it comes from the RoutedUICommand.

<Window.ContextMenu>
    <ContextMenu>
        <MenuItem Command="{x:Static local:MainWindow.PressMeCommand}" />
    </ContextMenu>
</Window.ContextMenu>

Bottom line

  • We used RoutedUICommand so that we could attach text to the command and avoid specifying text for both button and menu item
  • Binding in XAML is a (tiny) bit less code than doing it in code-behind

Other thoughts

  • You could also use a DelegateCommand pattern, passing in lambdas for both CanExecute and Executed. Use of a DelegateCommand is common in MVVM architectures.

 

 

TechEd NA 2014 – Windows Desktop Development – Panel Discussion

TechEd North America 2014, Houston
Windows Desktop Development – A Panel Discussion – Dmitry Lyalin, Tim Heuer, Habib Heydarian, Chipalo Street, Eric Battalio, Jay Schmelzer

Day 3, 14 May 2014, 1:30PM-2:45PM (DEV-B327)

Disclaimer: This post contains my own thoughts and notes based on attending TechEd North America 2014 presentations. Some content maps directly to what was originally presented. Other content is paraphrased or represents my own thoughts and opinions and should not be construed as reflecting the opinion of either Microsoft, the presenters or the speakers.

Executive Summary—Sean’s takeaways

  • Universal App platform really pervades everything that dev teams are doing
  • .NET Native – for faster Windows Store apps
  • Worth looking at PRISM—good way to build apps
  • If using C++, make sure you’re using modern C++, i.e. C++11

Full video

Dmitry Lyalin, Product Manager, Visual Studio, Microsoft

Panel members are all Engineering team members from the Visual Studio team.

Agenda

  • What’s new for WPF devs
  • Panel discussion

On the panel:

  • Tim Heuer – Program Manager, XAML UI
  • Habib Heydarian – Principal Group Manager, .NET Team
  • Chipalo Street – Program Manager, XAML
  • Eric Battalio – Senior Program Manager, C++
  • Jay Schmelzer – Partner Director, .NET Team

VS 2013 XAML improvements

  • Intellisense for data binding & resources
  • Go to definition (F12)
  • Nesting comments in markup
  • Renaming start tag renames end tag
  • New XAML code snippets

PRISM v5 (WPF & .NET 4.5)

  • PCL version of Prism library
  • Separate features into separate assemblies
  • Address high priority items

Debugging and Diagnostics

  • Memory analysis
    • New managed memory analyzer
  • Debugging improvements (4.5.1)
    • 64-bit edit and continue
    • Improved async debugging

Q: Deploy 4.5 app in 4.0 environment?

  • 4.5 is in-place update, so you can upgrade
  • ASP.NET vNext – new model
  • To get new functionality, you do need to install .NET 4.5

.NET Native vs. Traditional

  • Framework displayed as one single unit
  • .NET Native not yet available for desktop apps

Q: Problem when View tries to load other DLLs and they get called when I go into design surface

  • See Hari, try to repro this

On the radar – new deployment models for desktop apps

  • Maybe sort of Windows Store model for company apps within the enterprise

Q: Do you recommend always working in x86 mode in designer?

  • Yes

Q: If you put custom controls in shared UI library, seems like they never show up in the designer.

  • Take this offline

Q: Can you auto format when you change tag?

  • It’s either everything on one line, or one line per property

Q: XAML verbosity – anything planned to make XAML more concise? E.g. Data bindings, Setters, Converters, etc. Improvements to XAML syntax coming?

  • Point to tools, Intellisense / Auto Completion, code snippets
  • Language evolving – has evolved just a bit, simplifying
  • Tough to change XAML itself, because it’s a public language spec
  • Thought about inline scripting, but thinking about ways to do less markup and have XAML compiler generate stuff
  • “More of a compiled approach” in tools

Q: 4.53 coming? 5.0? Compatibility?

  • For 4.5.1, did around 300 compatibility fixes
  • 4.5.2, 120+ compatibility fixes
  • So 4.5.2 more compatible than 4.5 was
  • Cadence is going to continue to be quick—new framework every few months
  • Can no longer just release stuff every 3 years

Q: WPF dev doing LOB apps that will never be touch-based. Screens too busy. Any chance of writing desktop apps, but using Windows RT

  • When Win 8 came out, it was really focused on mobile. But now more focused on desktop
  • Now, with Universal Apps, spacing that is nice for both desktop usage but also okay for touch
  • E.g. For Outlook, Microsoft will try to have one single GUI that works on both Win RT and desktop

Q: WPF pain point – implicit styles for windows in user controls. Couldn’t define implicit style that applies to all user controls.

  • No plans, but follow-up with us

Q: For cross-platform, we still have to do View on specific platforms. But is there any way to use XAML solution for other platforms?

  • Third party doing this
  • We’re not thinking about this right now
  • If you need a cross-platform UI language, HTML is the way to go
  • Shared projects in Visual Studio
  • UI abstraction layers are tough. Not sure that it’s something that would work for users
  • Goal is to provide best native for Windows and also best web for cross-platform UI

Q: In 2012, we used Blend. Now, in VS 2013, about 90% of the stuff that was in Blend is now in Visual Studio. What’s the future of Blend and Visual Studio?

  • Purely design-oriented things like states will stay in Blend

Q: Can I do Roslyn plug-ins?

  • Yes, can do little plug-ins. E.g. Diagnostics, distribute in your team, flag violations of coding standards
  • Yes, you can do that on top of Microsoft’s copy of Roslyn

Q: What are the C++ guys working on?

  • See the VC blog
  • We hear a lot about performance and MFC
  • Talk at BUILD about C++ performance
  • MFC – no definite plans
  • MSDN guys are focused on scenarios, rather than on purely API documentation

Q: Future planning for Windows, e.g. Minority Report or 3D UI experience

  • Contact lenses? Tongue-in-cheek comment—makes Oculus Rift look like child’s play
  • Kinect for Windows, continued work
  • Nothing for augmented reality at the moment

Q: What does .NET Native mean for C++?

  • “Productivity of C# and performance of C++”
  • Recommend: continue developing in whatever paradigm you’re comfortable with
  • .NET Native still has garbage collector, so C++ might be preferable to some who want to do their only memory management
  • .NET Native v1 targeted just at Windows Store apps
  • In modern C++, you’re not typically doing new/delete explicitly
  • Should be using C++11
  • “It’s not like it used to be”

Q: Why is .NET Native limited to Windows Store apps and not desktop?

  • Win Phone, “compiler in the cloud”, you upload IL and it compiles down
  • Started with phone, no technological hurdles to do it on the desktop

Q: What is the next thing for which support will drop off (like XP)?

  • The .NET Framework versions support is matched to the corresponding OS version
  • Lifecycle Policy for out of band releases
    • E.g. Framework 4.0, we do something like 2-4 yrs
  • So when next version of Windows is deprecated, associated .NET Framework for that OS will also be deprecated/expired

Q: Moving from 2.0 to 4.0, customers still on XP. Can we go directly to 4.5 and run on XP?

  • As of 4.5, we no longer support XP

Q: Universal Apps – WPF ?

  • “Head” projects in solution – Windows Store, Phone 8, WPF
  • Don’t expect everything to work with shared projects
  • But can do partial classes

Q: Apps don’t scale well on different resolution devices (pre-WPF). Some controls scale, some don’t. Will you revise scalability for older apps? Win Forms. Or Wizard to convert from Win Forms to WPF

  • There are people thinking about multi-resolutions. Starting with immersive (phones), then WPF now.
  • Not sure how much they’ve thought about Win32 / Win Forms stuff, though
  • Some new lower-level APIs in Windows 8 to help app get more info about DPI stuff. Could call directly from your app
  • .NET 4.5.2, some fixes in Win Forms stack to make control a tiny bit more DPI aware
  • Jay: No silver bullet. There’s stuff that just won’t automatically scale
  • Maybe something in the future, but will have to go back and touch your apps to fix things

Q: OpenGL support on Win Phone 8.1?

  • Unknown

Q: When is next update to DirectX that will get pulled into WPF?

  • Newer features on older platforms..
  • DirectX – struggle with value of just updating to DX11. What does it give you?
  • Maybe just D3Dimage improvement?
  • Not sure what ROI is, just going to DX11

Q: Recommendation for native application that does graphics?

  • Eric: No answer.
  • Universal App, includes support for Native C++ / XAML
  • Office internally, tablet apps are using Native XAML
    • So we’ll have to give them something for native desktop apps

Creating a ListBox that Shows All Predefined System Colors

The System.Windows.SystemColors class contains a series of static properties that expose the current predefined system colors.  The properties come in triplets.  For each system color Xxx, there are XxxBrush, XxxBrushKey and XxxColor properties.

We can easily create a little WPF application to display the current system colors by building a collection of brushes and then binding the collection to a ListBox.

Here’s the final result:

Below is the code that I used to create the list.

I start with a little utility class that wraps up a named system color and its Brush:

    public class ColorInfo
    {
        public string Name { get; set; }
        public Brush Brush { get; set; }

        public ColorInfo(string name, Brush brush)
        {
            Name = name;
            Brush = brush;
        }
    }

Next, I add a collection to my main Window class, which will store a list of ColorInfo objects:

    private ObservableCollection<ColorInfo> allSystemColors;
    public ObservableCollection<ColorInfo> AllSystemColors
    {
        get { return allSystemColors; }
    }

In the window’s constructor, I populate this list, using reflection.  Notice that I iterate through all of the properties in the SystemColors class and grab only those whose names end in “Brush”.

        allSystemColors = new ObservableCollection<ColorInfo>();

        Type scType = typeof(SystemColors);
        foreach (PropertyInfo pinfo in scType.GetProperties())
        {
            if (pinfo.Name.EndsWith("Brush"))
                allSystemColors.Add(new ColorInfo(pinfo.Name.Remove(pinfo.Name.Length - 5),
                                                (Brush)pinfo.GetValue(null, null)));
        }

All that remains is to bind this collection to a ListBox.  We do this in the XAML:

    <ListBox ItemsSource="{Binding ElementName=mainWindow, Path=AllSystemColors}"
		ScrollViewer.HorizontalScrollBarVisibility="Disabled"
		ScrollViewer.VerticalScrollBarVisibility="Auto" >
    	<ListBox.ItemsPanel>
    		<ItemsPanelTemplate>
    			<WrapPanel />
			</ItemsPanelTemplate>
		</ListBox.ItemsPanel>
		<ListBox.ItemTemplate>
	        <DataTemplate>
	        	<StackPanel Orientation="Vertical">
	                <Rectangle Fill="{Binding Path=Brush}" Stroke="Black" Margin="5" StrokeThickness="1" Height="74" Width="120"/>
	                <TextBlock Text="{Binding Path=Name}" HorizontalAlignment="Center"/>
				</StackPanel>
	        </DataTemplate>
	    </ListBox.ItemTemplate>
    </ListBox>

Voila!

Creating a ListBox that Shows All Predefined WPF Colors

In WPF, you can use the Colors class to access a series of predefined colors, defined as static properties of the Colors class.  You reference each color just using the name of the color.

For reference, here’s a little application that shows all colors in a ListBox.  (Credit to casperOne, in a stackoverflow post that shows how to create an object that encapsulates the list of properties in the Colors class).

Here’s the final result.  (Click on the image to see it full-sized).

The XAML for generating this list is pretty straightforward:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
	xmlns:local="clr-namespace:WpfApplication1"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    x:Class="WpfApplication1.MainWindow"
	x:Name="Window"
	Title="All Colors"
	Width="640" Height="480" >

    <Window.Resources>
        <ObjectDataProvider MethodName="GetType"
        ObjectType="{x:Type sys:Type}" x:Key="colorsTypeOdp">
            <ObjectDataProvider.MethodParameters>
                <sys:String>System.Windows.Media.Colors, PresentationCore,
                Version=3.0.0.0, Culture=neutral,
                PublicKeyToken=31bf3856ad364e35</sys:String>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>

        <ObjectDataProvider ObjectInstance="{StaticResource colorsTypeOdp}"
        MethodName="GetProperties" x:Key="colorPropertiesOdp">
        </ObjectDataProvider>
    </Window.Resources>

    <ListBox ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
		ScrollViewer.HorizontalScrollBarVisibility="Disabled"
		ScrollViewer.VerticalScrollBarVisibility="Auto" >
    	<ListBox.ItemsPanel>
    		<ItemsPanelTemplate>
    			<WrapPanel />
			</ItemsPanelTemplate>
		</ListBox.ItemsPanel>
		<ListBox.ItemTemplate>
        <DataTemplate>
        	<StackPanel Orientation="Vertical">
                <Rectangle Fill="{Binding Path=Name}" Stroke="Black" Margin="4" StrokeThickness="1" Height="50" Width="81"/>
                <Label Content="{Binding Path=Name}" />
			</StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
    </ListBox>
</Window>

Setting Up a Bare Minimum WPF Development Environment

Microsoft now offers Visual Studio 2010 in an Express edition–which is free.  You’re limited to a single language, but you can develop WPF applications using Express.

In this post, I walk through the exact steps to install and use Visual Studio 2010 Express (C# edition).  The install was done a clean Windows 7 machine.

To start with, go to the Microsoft Express download site.  Click on the link for the development language of your choice (e.g. C#, VB, C++) and then select a language (e.g. English).  The install package will start downloading.

This will download file named vcs_web.exe.  Double-click on the file to start the install process.

Click Yes on the UAC dialog.

Installation files will be extracted:

You’ll get to the first install dialog.  Be a good citizen and agree to send information about your “setup experience” back to Microsoft.  After all, they’re giving you a free product.

Carefully read every word in the license agreement and then agree to it.  (Yes, of course I’m being tongue-in-cheek when I say to read every word).

You can optionally decide to install Silverlight and/or SQL Server Express.  For this example, I’ll install Silverlight only.

Decide what directory to install into.  The default is fine.

Things start downloading and installing.  Sit back and wait–this will take a while.

You may need to restart in the middle of the installation.

After the reboot, the install continues.

Your patience will eventually be rewarded, as the installation completes.

Oh wait–one more restart, just for good measure.

Now you’ll see Visual Studio C# 2010 Express in your Start Menu.

When you start Visual Studio for the first time, you’ll see the Start Page.  You can click on the New Project link to create your first project.  The New Project dialog lists the various types of projects that you can create:

  • Windows Forms Application
  • WPF Application
  • Console Application
  • Class Library
  • WPF Browser Application
  • Empty Project

(Notice that we can’t create Silverlight projects–the Silverlight option that we chose during the install likely just installed the Silverlight runtime, rather than the Silverlight tools for Visual Studio).

At this point, you’re all set.  You have everything that you need, to create Windows client applications–either Windows Forms or WPF.  If you’re building something new, you’ll likely want to start with WPF.

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