Foreword

This article presents a debugger visualizer for WPF called Woodstock. After Woodstock was released to the world, it was used as a prototype to design and develop a superior visualizer called Mole v4 For Visual Studio - With Editing. We recommend that you use Mole v4 For Visual Studio - With Editing as your primary WPF and Visual Studio visualizer, because its functionality is a superset of what Woodstock has to offer. However, if you are interested in learning about the evolution of Mole by seeing what came before it, be sure to read this article.

This article presents a Visual Studio debugger visualizer called "Woodstock" which enables you to view the visual tree. It provides detailed information about all properties on every element in the visual tree, and a snapshot image of each element, allowing you to more easily debug complex WPF user interfaces.

Every WPF developer knows and loves Snoop, the powerful free utility created by Pete Blois. Amongst other things, Snoop allows you to view the visual tree of any WPF application, and inspect the properties of any element. This is a huge time-saver, and a great way to learn about WPF.

Despite the tremendous powers of Snoop, it does not really mesh well with debugging WPF code in Visual Studio. Snoop is a separate application which attaches itself to a WPF application's process. You cannot step through WPF code and use Snoop on that UI at the same time. I, Josh, often found myself debugging some WPF code and wishing that Snoop somehow worked in Visual Studio. That's why Woodstock was born.

I created a small subset of Snoop's functionality, and implemented it as a debugger visualizer. Since the uber-visual tree inspection application is called "Snoop", I decided to name my little visualizer "Woodstock". In case you are not familiar with Peanuts, here's the lowdown on Woodstock.

Throughout the article, when you see the word "I", it is referring to me, Josh Smith. I created Woodstock and wrote this article about it. The burden of updating the code, and this article, falls squarely on my shoulders. Woodstock is my invention, but it would not be nearly as useful and efficient without the numerous suggestions and bug reports I received from dozens of people across the globe!

Karl Shifflett has been a huge help in making Woodstock a great debugging tool. Ever since I published the initial article, he has made many great suggestions, sent me code snippets, reported bugs, etc. To thank him and show my appreciation, I decided to add him as an author of this article.

Shortly after this article was published, Florian Kruesch published an article about a visualizer which shows you an image of the element you're inspecting in the debugger. We agreed to merge his visualizer and article into Woodstock. Karl and I changed his code a bit so that it would work well in Woodstock, but the initial idea of an image visualization came from Florian.

If you just want to download the visualizer and install it, follow these steps:

At the top of this article, click the "Download the visualizer" link.

Save the ZIP file to disk and extract it.

Place "JoshSmith.WpfVisualizer.Woodstock.dll" at one of these locations:

VS install path\Common7\Packages\Debugger\Visualizers

My Documents\Visual Studio 2005\Visualizers

Instead, if you click the "Download the code and demo" link, you will get the Woodstock.zip file, in which there are two Visual Studio solutions. The one called "WpfVisualizer" contains the Woodstock visualizer code. The other, "WpfVisualizerTestApp", is a demo app which allows you to try out Woodstock. Be sure to drop the visualizer DLL into your Visualizers directory first before running the test app.

If you have any trouble installing the visualizer, there is a bleak amount of documentation on how to do so here.

If you are using this visualizer in Visual Studio 2005, then be sure to download the DLL which was compiled for VS2005. It turns out that the Microsoft.VisualStudio.DebuggerVisualizers.DLL assembly has a different version for VS2008, so I made a separate build of Woodstock for VS2008 users. The source code and demo app are compiled against VS2005 with the Orcas extensions, so that people who have not yet switched to VS2008 can use it too.

However, if you decide to build Woodstock in VS2008, be sure to remove the reference to Microsoft.VisualStudio.DebuggerVisualizers.DLL and add a reference to version 9.0.0.0 of that assembly. If you do not, you will get a nasty exception when trying to use Woodstock.

You can use Woodstock just like any other debugger visualizer. Here's what the datatip looks like when you mouse over a DependencyObject-derived object after hitting a breakpoint:

When you open the Woodstock visualizer, it looks like this:

The TreeView on the left represents the entire visual tree of the UI you are debugging. Initially, the element which you hovered the mouse over in the code editor will be selected in the TreeView, but you are free to select any element in the visual tree. If an element has its Name property set, then that name will appear next to the element's type. Each element that has descendants has the number of descendants displayed beside it in parentheses.

The DataGridView on the right shows all of the properties of the selected element. If a property is a dependency property, then the "Value Source" column shows what is providing the effective value of that property (i.e., is it a locally provided value? a value provided by a template? a Style? a system theme? etc). Having the source of a dependency property's value can make it much easier to track down issues where one property might be set from any number of external influences (which is the heart and soul of DPs).

Clicking on a button to the left of a property name will copy a Google search query to your clipboard, so that you can paste that URL into a Web browser's address box and research the property. I wanted to just open a Web browser to that URL, but attempting to do so always made Visual Studio crash when running on Windows XP (but, oddly enough, not Vista).

You can filter the properties shown in the grid by typing a case insensitive filter string into the TextBox toward the bottom of the Form. If you do not want to view the attached properties, simply uncheck the CheckBox next to the TextBox. If you only want to view attached properties, leave the CheckBox checked and type "." as your filter text string.

When you click on the "Selected Element Snapshot" tab, you will see an image of the element currently selected in the TreeView. Here's a screenshot of that:

UIElements and FrameworkElements must be initialized and loaded before they can be displayed. As a result, when you put a breakpoint in, for example, a Window's constructor, you won't see anything if you check out the visuals in that Window.

In case you have never used a debugger visualizer before and want some more information, read this page in the docs.

There is one issue which might possibly make using Woodstock difficult. Visual Studio's "Debugger Visualizer" feature only allows a visualizer to have a brief amount of time to serialize its data. If your visual tree is huge, or your machine is bogged down processing other things, etc., then Woodstock might take too long and it will time out. Visual Studio will show an error dialog stating: "Function evaluation timed out."

I did my best to work around this limitation by optimizing the way that information about the visual tree is retrieved and represented. Before making the optimizations, I tested Woodstock against an application with a gigantic visual tree and long-running background processes, and it timed out frequently. After making the optimizations, it never timed out. Hopefully, those optimizations will prevent you from seeing that annoying error message, too.

Debugger visualizers are strange little creatures. You have to keep in mind that there are two logical parts involved: debugger-side code and debuggee-side code. Visual Studio's debugger allows you to inject some code into itself and the process being debugged. The code in the debugger process shows the visualizer UI. The code in the process being debugged allows you to package up the data that you want to display in the visualizer. Once that data has been serialized, it gets shipped over to your visualizer in the debugger process, at which point you can turn it back into live objects and display them. For more information about the architecture of visualizers, read this article in the SDK.

A visualizer must specify a class for which it provides visualizations, including subclasses of that class. We want our visualizer to work for any DependencyObject, or subclass thereof. The problem is that DependencyObject is not serializable, so we cannot rely on the standard serialization behavior of the debugger visualizer framework to make life nice and easy for us. Instead, I created a class which intercepts requests to serialize the visual tree, and then created a parallel hierarchy of serializable objects which contain the visual tree information we want to display in the visualizer. Here is that class:

///<summary>/// Serializes the element tree into DTOs.
///</summary>publicclass ElementTreeVisualizerObjectSource : VisualizerObjectSource
{
publicoverridevoid GetData(object target, Stream outgoingData)
{
DependencyObject depObj = target as DependencyObject;
// This is the magic line of code which creates a serializable
// representation of the entire visual tree of which the target
// element is a member. The element property information and
// snapshots are not created at this time.
WpfElementTree elementTree = new WpfElementTree(depObj);
_binaryFormatter().Serialize(outgoingData, elementTree);
}
// Other methods omitted for now...
}

The visualizer itself is a simple class which only shows a custom WinForms Form, as seen below:

One piece of the code which took me (Josh) a while to get right was the logic which stores information about all of an element's properties. Information about a property is stored in an instance of my WpfElementProperty struct. A WpfElement object has a WpfElementPropertyList which stores information about every property of that element. The logic which populates that list is seen below:

A far more challenging thing to implement was the logic which lazily loads the property information and snapshot image of each element in the visual tree. Initially, I serialized all of that information for every element in the tree in the ElementTreeVisualizerObjectSource's GetData method, seen above. It turns out that Visual Studio has a timeout monitor for that method (deadlock detection?), which caused the method to be terminated before it was able to complete serializing the data for large visual trees. When the timeout occurs, the Woodstock UI does not appear, and the visualizer is unusable.

I worked around this problem by redesigning the way that the property information and the snapshot image are created for each WpfElement instance. Instead of creating them all in one fell swoop, I create them on an as-needed basis. This prevents all of that data from being serialized at once, which makes the GetData method lightning fast (so it won't timeout).

When the WpfElementTree is created in the ElementTreeVisualizerObjectSource.GetData method, the WpfElement instances do not have any of that extra information. When the user selects an element in Woodstock's TreeView, it checks to see if the newly selected WpfElement is already populated with the property information and snapshot data. If not, we send that element over to the debuggee-side code and expect it to be returned with the missing data. That code is in the ElementTreeVisualizerForm class, as seen below:

The method which actually requests that the ElementTreeVisualizerObjectSource populates the WpfElement is seen here:

WpfElement CreatePopulatedElement(WpfElement element)
{
// Since serializing and deserializing all of this element's
// children might take a very long time, remove them for now
// and we'll add them back in later.
List<WpfElement> children = new List<WpfElement>(element.Children);
element.Children.Clear();
// Ask the ElementTreeVisualizerObjectSource,
// which is running in the debuggee process,
// to fill up this element's property info
// and take a snapshot of it.
MemoryStream inputStream = new MemoryStream();
_binaryFormatter.Serialize(inputStream, element);
Stream outputStream = _objectProvider.TransferData(inputStream);
inputStream.Close();
// Get the populated WpfElement back from the debuggee process.
WpfElement populatedElement =
(WpfElement)_binaryFormatter.Deserialize(outputStream);
// Add the element's children back to its list.
populatedElement.Children.AddRange(children);
// Keep a reference to the snapshot image so
// that we can easily dispose of it later.
_elementSnapshots.Add(populatedElement.Snapshot);
return populatedElement;
}

When the object provider's TransferData method is invoked, it results in the ElementTreeVisualizerObjectSource's TransferData method to be called back in the debuggee process. That logic is seen below:

One crucial thing to notice here is that we have an association between the real WPF element and a WpfElement instance which represents it. That link is formed by a simple integer ID value. WpfElement has an ID property, and the real WPF element is put into a Dictionary<int, DependencyObject>, where the integer key is the ID value.

The two ID values are set up in the WpfElement constructor:

private WpfElement(
DependencyObject currentElem,
DependencyObject initialElem,
Dictionary<int, DependencyObject> elementMap)
{
// Create an association between this object and the real WPF object
// that it represents by giving them both the same ID.
_id = WpfElement.elementCount++;
elementMap.Add(_id, currentElem);
// Other code elided...
}

When the debugger process sends over a WpfElement to be populated, the element's ID is used to look up the real WPF object in the dictionary.

November 13, 2007 - Added the "Visual Studio Version Information" section, and provided a VS2008 version of the Woodstock assembly.

November 13, 2007 - Improved performance by changing WpfElementProperty to a struct instead of a class. Added logic which ensures the display order of columns in the DataGridView. Added a number to every non-leaf node in the TreeView indicating how many descendants that element has. Updated all of the file downloads.

November 14, 2007 - Improved the way that an element's properties are discovered, thanks to a tip from Andrew Smith at Infragistics. Now that code is faster, and all attached properties will be included as well. Added the "Limitation" section. Updated all of the file downloads.

November 15, 2007 - After receiving tons of great feedback and encouragement, I added several features: the property filtering TextBox, the CheckBox which allows you to hide the attached properties in the grid, and a column of links which copy a Google search to your clipboard. Also, I made some optimizations to the serialization code, hoping to avoid the dreaded "Function evaluation timed out" error message. I updated all of the file downloads.

November 15, 2007 - Fixed the way that Google search URLs are created for attached properties. Updated the file downloads.

November 15, 2007 - Thanks to Karl Shifflett, I discovered a bug which only exists on Vista where exceptions are thrown as soon as Woodstock was opened. To fix the problem, I changed the "Property Name" column to use buttons instead of links. That solved the weird remoting/GDI+ issue. I updated the file downloads.

November 15, 2007 - Wow, a lot of updates today! I added in Florian Kruesch's code and added him as an author of this article. Adding in his code pointed out a bug in my logic where DependencyObjects that are not in a visual tree were being handled incorrectly. That has been fixed. I updated the file downloads.

November 16, 2007 - I implemented a great suggestion from Karl Shifflett, so that the Woodstock window appears immediately and shows a "splash screen" while the visual tree information is created on a worker thread. This really improves the responsiveness of the UI, making Woodstock more usable. I also cleaned up the code a bit and added more comments. I updated the file downloads.

November 17, 2007 - I made the element information lazy-loaded to vastly improve performance and avoid timing out. I added the "Lazily Loading Element Information" subsection. Doing this also enabled me to provide a snapshot of every element in the tree, not just the one the user initially selected. Also, I fixed a few bugs and gave the snapshot image some scrollbars so that you can view the entire image without having to resize the window (thanks to Rick Eberle for the suggestion). I updated all the file downloads.

November 18, 2007 - I got rid of the attached ID property for elements in the visual tree, and instead use a dictionary to associate a WpfElement with a DependencyObject. Thanks to Karl Shifflett for pointing out that simpler and more efficient way of creating the object associations. I updated the file downloads and modified the "Lazily Loading Element Information" subsection.

November 18, 2007 - I cleaned up the code a bit and updated the relevant snippets in the article. Added the "Making Woodstock Work with XBAPs" section. Updated the file downloads.

November 19, 2007 - I implemented performance optimizations after performing stress tests. Now the nodes in the TreeView are loaded on-demand, and the children of a WpfElement are temporarily removed when it is being sent over to the debuggee process to be populated. I updated the file downloads.

November 20, 2007 - Karl Shifflett fixed the visual snapshot code so that the elements are not clipped. I then took his code and simplified it a little bit and put it into Woodstock. Due to his amazing efforts in this project, I decided to add him as an author of this article! I updated the file downloads.

November 20, 2007 - Dropped a new build of Woodstock which was compiled in the RTM of VS2008. Also, I removed the images of Woodstock which I got from the Peanuts web site, because some weenies were whining about it, and one guy even threatened to report me to whoever officially owns those images (geez, talk about a buzz-kill...). All the files were updated.

November 21, 2007 - I fixed a bug in the snapshot image logic, and updated the file downloads.

November 23, 2007 - I put in a bug fix which Karl Shifflett pointed out in the VisualCapture class. Elements whose ActualWidth or ActualHeight is between 0.0 and 1.0 were causing an exception to be thrown because the corresponding dimension of the Bitmap for that element has an invalid value of 0. I updated the file downloads.

Comments and Discussions

Thanks for integrating my little contribution. It would be very cool to send an image for each element, but I think you mentioned a timeout going on in the remoting connection between Visualizer and ObjectProvider that prevents this...

Yes, the debugger-side code must be very efficient because it is racing against the clock. I'd love to provide an image for each element, but it just takes too long. Perhaps there is a way to skin this cat, but I'm not aware of it yet...

:josh:My WPF Blog[^]Without a strive for perfection I would be terribly bored.