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

Mole for WPF - Multifunction Visual Studio Visualizer for WPF

, , 31 Dec 2007
Rate this:
Please Sign up or sign in to vote.
A multifunction visualizer that allows inspection of the visual tree. For each element in the visual tree, the developer can view all properties, visual images, run-time XAML, and can drill through to property collections to view their values.

Mole 2010 is Now Available

Mole2010.gif

If you are using Visual Studio 2010 and would like to use the Mole debugging tool, please visit http://www.molosoft.com to get your copy today. Mole 2010 makes debugging easier.

Mole v4 for Visual Studio - With Editing Released

All versions of Mole have been replaced by Mole v4 for Visual Studio.

Please refer to this article for the latest information and code: Mole v4 for Visual Studio - With Editing.

This article is here for informational purposes. The downloads have been removed. Please download and use Mole v4 for Visual Studio - With Editing. Thank you!


Authoring

No question about it, Mole was a team effort.

Karl created Mole and wrote this article. Josh Smith and Karl developed Woodstock and Mole during the same time frame, and were able to draw from each other's efforts. Woodstock acted as a prototype, or proof-of-concept, upon which the design and some algorithms in Mole are based. Mole adds a rich array of user-friendly, useful, and refined additions to the core functionality chiseled out in Woodstock.

I really appreciate working together with Josh Smith on critical sections of the code. We have gone back and forth with our projects trying to deliver tools that developers will utilize.

Introduction

Mole is a multifunction Visual Studio Visualizer which allows you to inspect elements in the WPF visual tree, as well as all properties of those elements..

The focus of this article is to introduce Mole, explain how to use it, and to introduce techniques for creating and debugging visualizers.

Mole's Feature Set

  • View the structure of a WPF visual tree in a treeview control
  • View all properties of any element in a visual tree
  • For properties that are collections, view the values inside these collections
  • View an image which represents the element being visualized
  • View run-time XAML in an HTML viewer for an element
  • Multithreaded, lazy loading for fast performance
  • Persisted application state, and reloads settings from a previous Mole session
  • Search for properties and property data values in various ways
  • Configure which properties to view for any element/control

Background

There are a growing number of third party development tools for WPF developers. In addition, there have been very good articles recently posted on CodeProject presenting visualizers for WPF. When a great friend and coauthor Josh Smith wrote Woodstock for WPF a few weeks ago, I got pretty excited about how Visual Studio Visualizers that target WPF can really assist developers. I started making a list of features I wanted and began working on Mole.

Mole was my first visualizer and first real WinForms program I've done with .NET 2.0. I spent most of my time with WPF, ASP.NET, SQL Server, and Windows Services. Starting from scratch would prove to be challenging at times.

Mole grew alongside Woodstock as it was being developed and refined. Josh and I worked hard to develop fast performing visualizers. We both tried some crazy things during this process of refinement, some of these I wouldn't post on a billboard.

To give you a comparison of what we were up against from a data loading perspective, consider the Text Visualizer which ships with Visual Studio. It allows you to view long strings in a multiline TextBox. Text Visualizer opens quickly and displays the string.

Mole displays an entire visual tree and over a hundred properties in a DataGridView for any object selected in the TreeView! Let's face it, the visualizer needs to load fast or it won't get used. What you'll see in this article is a highly refined visualizer which runs lightning fast. When running Mole in stress tests with absurdly large visual trees (over 10,000 elements), it was able to open in less than a second. Bearing in mind that most sane developers would never create visual trees of that size, it is safe to say that Mole is fast enough to be usable by the most impatient developers out there.

Where did the name Mole come from? The original name was Lucy, another member of the Peanuts gang. After a possible legal problem with using cartoon images in our visualizers was brought to light, I decided to change the name and colors, and came up with Mole and the earth colors. Moles love to dig. This Mole burrows around your visual tree and returns the requested information.

Being a fan of Business Intelligence applications and Drill Through or Drill Back, I wanted to give my visualizer some Drill Through capabilities. That is where the idea to be able to view property data that is stored in collections came from. Ask yourself, how many times have you been using the Visual Studio debugger and come upon a property that has its values stored in a collection and you can't see the values without having to do some more typing or supplying an indexer to see the data? Not with Mole. If the property stores its data in a collection, just click on the collection icon and all the values in the collection will be displayed. In testing, I've viewed trigger collections, row and column collections, and items collections that are custom classes, just to name a few. You can also see collection data in the run-time XAML viewer.

If you are debugging and perform a Quick Watch on a ComboBox, 99% of the time you want to view the SelectedValue, SelectedIndex, Selected... properties, right? Those other 130 properties I really never look at. So I added in the ability to select "favorite properties" by Framework type. That way, you can view just the properties for that type that you usually want to view. With the click of the mouse, you can toggle showing just favorites or all properties. These favorites are persisted so that you'll have them the next time the visualizer opens.

Installation

There are a number of options available to you for getting Mole installed on your machine. You can just install Mole and be Molenating, or you can download the source, build and install your build.

Mole has been tested on x64 and x32 systems, Vista and XP.

Standalone Install

  • Download either MoleVisualizer_VS_2005_Update.zip or MoleVisualizer_VS_2008_Update.zip by clicking on the links here or at the top of this article.
  • Save and unzip.
  • Copy the files to one of the following directories:
    • vs install path\Common7\Packages\Debugger\Visualizers
    • My Documents\Visual Studio 2005\Visualizers {VS2005}
    • My Documents\Visual Studio 2008\Visualizers {VS2008}

Source Code Install

I have provided the source for both VS2005 and VS2008. The only difference is the reference to Microsoft.VisualStudio.DebuggerVisualizers.DLL. For VS2005, the file version is 8.0.0.0, and for VS2008, the file version is 9.0.0.0.

Visual Studio Visualizers and XBAPs

One big advantage Mole and Woodstock have over some other tools is that you can use them to debug XBAP applications. Here is what you need to do to enable that:

  1. Right-click on the XBAP project in Solution Explorer and open the Properties page.
  2. Under the Security tab, select the "This is a full trust application" radio button.
  3. Be sure to set it back later on to Partial Trust in order to properly test deployment.

Thanks to Josh Smith and CodeProject member "ivolved" for this tip!

Using Mole

Mole is opened just like any other Visual Studio Visualizer, you click on the small magnifying glass icon, and this displays the visualizers lists, and then you select the visualizer you want to use. Mole is programmed to show up in the visualizers list when your mouse is over an object that is of type DependencyObject. This snapshot was taken from the code window. You can also open visualizers from the Watch windows.

When Mole opens, it looks like this. There is a 600px image width restriction here at CodeProject. I reduced this image from the default width of 1024 to 600. I will cover each element in detail with full size images. When you resize Mole, the window persists its size, so on your next Mole session, the window will be the same size as you left it.

Screenshot - mole2.gif

Element Tree - Mole's Garden

The Mole's Garden contains a TreeView which displays the current visual tree of the WPF application being debugged. The TreeView initially selects the object you selected in Visual Studio. The TreeNode text is formatted as follows: Element Type - Element Name (Count Of Descendants).

Some nodes are not assigned names and some nodes do not have any child nodes associated with them. Notice the ListBox named lbTest has 1,111 items in it. Mole's Garden has a minimum width set so that the buttons beneath it will always be visible.

Elements are displayed in one of four colors. White is an unselected element. Light Gray is the selected element when the TreeView has lost focus. Blue is the selected element when the TreeView has focus. Moccasin is the initially selected object you moused over in Visual Studio.

The Light Brown 4 pixel line separating the two TabControls is a GridSplitter. You may size the two regions. The size of the two panels is persisted when the size is changed so that the panels will be displayed the same when Mole is reopened.

The Select Initial button will select the object that you initially selected in Visual Studio. The Expand All button will expand all TreeNodes in the entire TreeView. The Collapse All button will collapse all TreeNodes in the entire TreeView. The Expand Down button will expand all TreeNodes that are children of the currently selected TreeNode. The Collapse Down button will collapse all TreeNodes that are children of the currently selected TreeNode.

Selected Element Tab Header

The selected element TabControl has four TabPages.

The label below the Element Properties TabPage heading is formatted as follows: [Object Type] [Has Favorites] [Object Name] [location loaded from].

Two of these need some explanation.

The [Has Favorites] text will appear if the selected element Framework Type has favorites associated with it. This is useful to remind you that this type has favorites associated with it.

[location loaded from] informs you whether the data was fetched from the process being debugged or read from the local cache. Data from the Properties tab and Visual tab is lazy loaded. The data is only loaded when the element is selected in the TreeView and the tab page for that type of data is visible. The Visual Studio Visualizer architecture allows for two way conversations to take place across process boundaries, by using .NET Remoting. Mole takes full advantage of this technology, and only requests the data the users want to display. This lazy loading design drastically reduces the time it takes for Mole to load up.

Element Properties Grid

The Element Properties grid displays each of the selected item's properties. Columns with an underlined header text can be sorted by clicking on the column header. Like many features in Mole, the selected column sort is persisted between Mole debugging sessions. It is very easy to add more columns to this grid, so any suggestions from the community for more data to view are welcome.

When the help icon is clicked, it opens Google.com using your default browser and sets the query string to search for the property name and selected item type.

The Favorites column (Fav) indicates if this property is one of your favorites for the selected type. You may select or deselect favorites by clicking the checkbox. See the information below for a full explanation of the favorites feature.

The Category Name column is the name of the category assigned to it on the property declaration with the Sysem.ComponentModel.Category attribute. To see an example of this attribute, look at the MoleTabControl.vb file in the solution source.

Value Source identifies the source for the property value. Only dependency properties can have a value source. If you are not familiar with the idea of value sources, please read up on the topic at MSDN BaseValueSource Enumeration.

The rightmost column, Is DP, identifies whether this property is a dependency property or not.

If a property stores its values in a collection and the collection actually contains items, the collection icon will display in the Coll column. When this icon is clicked, a modal window will open with a grid containing all the values of all the items in the collection. The image below displays the collection from the grid's Children property.

Screenshot - molecollections.gif

Search

Mole allows you to search and filter the properties displayed. Mole searches are logical AND searches. If you have more than one search condition applied, then the property must meet both conditions in order to be displayed.

  • The "Select Search Location" combobox allows you to select where and how you want the search text to be used. The search is conducted as you type in your search text.
  • The "Show All" button clears any searching/filtering you have performed so that you can view all of the properties.
  • The "Show Attached Properties" checkbox toggles the display of properties that are attached properties.
  • The "If Has Favorites, Only Show Favorites" checkbox toggles the "favorite properties" display behavior. If checked and the selected element's type has one or more favorites assigned, then only properties assigned as favorites will be displayed. The beauty of this technique is that it permits you to leave this checkbox checked all the time, and when you select a type which has favorites, the displayed properties will only be those that you want. If the selected element's type has no favorite properties associated with it, then you will see all of that element's properties. After using Mole for a day, you'll have your favorites all established.
  • The "If Has Collections, Only Show Collections" checkbox works the same as the above checkbox, except its logic is applied to properties with collections.

The Molenator's Blog link will take you to my blog. I will establish a special section on my blog for Mole and future visualizers.

Element Visual

The "Element Visual" tabpage displays an image of the selected element. The above image is from my RoutedEvent visual trace program. The image is lazy loaded, and the label under the Elements Visual header indicates where the image was loaded from, cache or fetched.

The displayed image can easily be copied to the Windows Clipboard by clicking on the "Copy Image" button.

This tab is pretty cool because it gives you insight into how the various elements in the visual tree participate in the rendering process. You can select this tab to display it, then click on an item in the element tree, and press the arrow key and go up or down the treeview's nodes to see what each element looks like. The "Copy Image" button makes it super easy to get snapshot images and copy them to other documents, emails, etc.

Element XAML

Expanded View

Screenshot - molexamlexpanded.gif

Compressed View

Screenshot - molexamlcompressed.gif

The "Element XAML" tabpage displays the run-time XAML which was transformed into HTML and loaded in the WebBrowser control. The label under the WebBrowser control follows the format of the other two tabpages.

The 'Font +' and 'Font -' buttons allow you to change the font size of the displayed text. This setting is also persisted between Mole sessions.

The two radio buttons allow you to select the format for the displayed XAML, expanded or compressed. This setting is also persisted between Mole sessions.

You may right-click the WebBrowser control to access the copy, print, and view source functions.

This tabpage took longer to write than any other section of code. I really wish Microsoft would expose the default XSLT object which the WebBrowser control uses internally, or just add a public method like RenderXML, and the control would just perform the transformation like it does when it opens a file. This would make this control so much easier to use. At any rate, I downloaded a defaultss.xslt and edited it to get the above transformation. This file is in the solution source, and is also an embedded resource.

This tabpage is cool because if the element contains collections, those collections will be displayed here. I have loaded 20,000 items into a ListBox and viewed the XAML for them in this XAML viewer.

Favorites

The Favorites tabpage presents a treeview of your favorite properties, grouped by Framework type.

Limitations

So far, the only limitation I've run into is the Element XAML. Under certain circumstances, the System.Windows.Markup.XamlWriter.Save function can throw a StackOverFlow Exception or a GenericTypeSerialization Exception. This program has no control over this. The StackOverFlow will be reported in a message box and the visualizer will close. Just reopen it and don't select that element and view its XAML.

These two exceptions are thrown on the other side of the remoting conversation that handles data transfer requests. Once its stack is corrupted, that process must be terminated, thus ending our visualizer session.

You would get these same exceptions in your own code if you called System.Windows.Markup.XamlWriter.Save and passed this same dependency object.

Mole & Visualizers 101

Before going any further, please get some background on visualizers. Once you have the basics, the rest is just plain fun!

The first article I read was Creating Debugger Visualizers with Visual Studio 2005 by Julia Lerman. She does an outstanding job of getting you started. Another good starting point is the MSDN Visualizer Architecture article. Also, there many articles here on CodeProject and the Internet covering visualizers.

There are four partners, running in two processes that are working together in the visualizer world.

Debugger Side
  • Visual Studio Debugger - provides UI to select a visualizer to open.
  • Visualizer UI - runs within the VS debugger process.
Debuggee Side
  • Your program - the program you are debugging. This process has the visual tree we want to visualize.
  • Visualizer Data Object - runs within your program's process.

Visual Studio plays the middle man handling communications between the two processes. The bottom line is, your visualizer UI has no data, until it makes a request to the visualizer data object. All data which moves between the two processes must be serialized. This is important to remember when building your data structures.

Both sides of the conversation are active throughout the visualizer's life. This allows your UI to make requests to the data object whenever it needs more data to display. The communications plumbing Microsoft provided is incredibly fast, even with all the serialization and deserialization going on.

Visualizer Project

If you are used to writing executables, this is going to blow your mind. Your visualizer complies into one class library .dll, part of which runs in one process and the other part is running in the other process. The classes from the Debugger process communicate through Visual Studio to classes in the Debuggee process.

How Does Mole's Project Fit Into This?

Visual Studio is only concerned about two classes in your visualizer: the debugger and debuggee.

First, your debugger-side class which derives from DialogDebuggerVisualizer. In Mole, this is the Mole.Burrow class.

Second, your debuggee-side class which derives from VisualizerObjectSource. In Mole, this is the Mole.MoleVisualizerObjectSource class.

Visual Studio identifies these classes by looking at the following attribute you have placed on the assembly in which your visualizer lives. Since all visualizers must be in certain directories, it is very easy for Visual Studio to locate all visualizers on your system.

<Assembly: System.Diagnostics.DebuggerVisualizer(GetType(Mole.Burrow), _
  GetType(Mole.MoleVisualizerObjectSource), _
  Target:=GetType(System.Windows.DependencyObject), _
  Description:="Mole WPF Visualizer (Burrow back into your visual tree.)")>

The first attribute parameter is the type of our visualizer (Mole.Burrow).

Second is the type of our data source (Mole.MoleVusualizerObjectSource).

The third tells Visual Studio what type our visualizer wants to visualize. (In Mole speak, that is "what type Mole wants to Burrow into and molenate".) In our case, Mole can visualize DependencyObjects, or any class which descends from DependencyObject.

The fourth attribute parameter is the description Visual Studio displays in the listing of available visualizers when you are debugging. Glance back to the first image in this article, and you'll see the above description.

Mole.Burrow - Debugger Side - UI Side
Imports Microsoft.VisualStudio.DebuggerVisualizers
Public Class Burrow
       Inherits DialogDebuggerVisualizer

    Protected Overrides Sub Show(ByVal windowService As _
      IDialogVisualizerService, _
      ByVal objectProvider As IVisualizerObjectProvider)

        'rather than following the normal pattern of calling GetData or 
        'GetObject at this point, we will defer these calls to frmMole
        'by passing the objectProvider to the form
        '

        Using frm As New frmMole(objectProvider)
            frm.StartPosition = Windows.Forms.FormStartPosition.CenterScreen
            windowService.ShowDialog(frm)
        End Using
    End Sub

End Class

Here is how the communication really works. Notice the second parameter, objectProvider. objectProvider is the object the debugger side will use when communicating with the data object. Just make your API calls using objectProvider. Read about the IVisualizerObjectProvider Interface here.

In most visualizer examples on the web, you'll see calls being made to the Data Object in this class, with the results being passed to a form or message box. Mole needs to constantly communicate with the debuggee-side data object, so we just passed objectProvider to the form and allow the form to take over the responsibility of data communication.

So you can see, Visual Studio does not open your form, you do. Visual Studio only makes a method call to Sub Show. The rest is up to you.

Mole.MoleVisualizerObjectSource - Debuggee Side - Data Object Side

Code abbreviated here for clarity.

Public Class MoleVisualizerObjectSource
    Inherits VisualizerObjectSource

    ''' <summary />
    ''' Returns an abstract view of the entire visual tree 
    ''' of the application being debugged.
    ''' This sub is actually called by Visual Studio when the frmMole 
    '''makes the objectProvider.GetObject call to the IVisualizerObjectProvider
    ''' </summary />
    Public Overrides Sub GetData(ByVal target As Object, _
           ByVal outgoingData As System.IO.Stream)
        MoleVisualizerObjectSource.Serialize(outgoingData, BuildTree(target))
    End Sub
End Class

When your visualizer UI first opens, it requests data by calling the GetObject method on objectProvider. This, in turn, calls the GetData method seen above.

Your Visualizer Object Source class is passed a reference to the object that was selected in the Visual Studio debugger. This class is working with and has full access to the selected object. This allows Mole to access the visual tree of the application being debugged.

One more item I need to make perfectly clear. Visual Studio only creates one instance of your Visualizer Object Source and makes its calls to this same object. This allows you to have module level objects that will survive between your calls to the Visualizer Object Source. Mole takes full advantage of this, as you will see in a moment.

MoleVisualizerObjectSource.Serialize is a Shared helper function that wraps the BinaryFormatter.Serialize method. This method, along with the BinaryFormatter.Deserialize method, is used by both the debuggee and the debugger sides.

The BuildTree function does all the work building the outgoing data structure that represents your visual tree.

Mole Visualizer Form - Debugger Side - UI Side - Requesting Data
Private Sub LoadItemsOnUIThread()
    _objTree = CType(_objectProvider.GetObject(), Tree)

    CompleteLoading()

End Sub

Private Sub BackgroundWorker1_DoWork( _
          ByVal sender As System.Object, _
          ByVal e As System.ComponentModel.DoWorkEventArgs) _
          Handles BackgroundWorker1.DoWork

    ' Get the BackgroundWorker object that raised this event.
    Dim worker As System.ComponentModel.BackgroundWorker = _
      CType(sender, System.ComponentModel.BackgroundWorker)
  
    'Assign the result of the computation to the Result property 
    'of the DoWorkEventArgs object.
    'This is will be available to the RunWorkerCompleted eventhandler.
    e.Result = _objectProvider.GetObject()

End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted( _
          ByVal sender As Object, _
          ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
          Handles BackgroundWorker1.RunWorkerCompleted

    If e.Error IsNot Nothing Then
        MessageBox.Show("Bummer. Exception while loading data." & _
          vbCrLf & vbCrLf & e.Error.ToString, "Problems...", _
          MessageBoxButtons.OK, MessageBoxIcon.Stop)
          
        Me.Close()
        Exit Sub
    End If

    _objTree = CType(e.Result, Tree)

    CompleteLoading()

End Sub

Now things can get a little confusing. You'll notice that the above code fragments both make the same call to _objectProvider.GetObject(). In the lifetime of the visualizer, only one of these methods will get called. Which one depends on how you are using the visualizer.

If you are just simply using the visualizer, the background worker code will be called.

Please read this note: If you want to debug Mole (meaning you want to step through Mole's code while it's visualizing an object), you can't spawn background worker threads because Visual Studio will throw an exception. Because of this limitation and the fact that I wanted to be able to debug Mole, I had to provide two ways, sync and async for Mole, to get its data from the Data Object.

Synchronous Data Object Call

The LoadItemsOnUIThread sub calls _objectProvider.GetObject on the form's UI thread and gets the data. Again, this type of call is required when debugging a visualizer.

Asynchronous Data Object Call

The .NET Framework provides a rich set of multithreading tools for developers. The BackgroundWorker object simplifies multithreading by exposing a very simple interface to developers that encapsulates the complexities of multithreading. Visual Studio allows you to drop a BackgroundWorker right on to your form, you could also create the BackgroundWorker in code. So how you code your multithreading is up to you.

The call to _objectProvider.GetObject is made asynchronously. When it completes, the BackgroundWorker1.RunWorkerCompleted sub casts the data.

The CompleteLoading sub processes the populated _objTree object, filling the Element Tree and Element Properties grid.

How to Debug a Visualizer
Public Class MoleVisualizerObjectSource
    Inherits VisualizerObjectSource

    Public Overrides Sub GetData(ByVal target As Object, _
      ByVal outgoingData As System.IO.Stream)
        MoleVisualizerObjectSource.Serialize(outgoingData, BuildTree(target))
    End Sub
    
    ''' <summary />
    ''' This function makes debugging a Visualizer a snap.
    ''' 1. Set desired breakpoints inside your Visualizer
    ''' 2. Call this method from another project
    ''' </summary />
    ''' Note, that other project will need to reference 
    '''   Microsoft.VisualStudio.DebuggerVisualizers
    ''' 

    Public Shared Sub TestMoleVisualizer(ByVal obj As DependencyObject)

        Dim vdh As VisualizerDevelopmentHost = _
          New VisualizerDevelopmentHost(obj, GetType(Mole.Burrow), _
          GetType(Mole.MoleVisualizerObjectSource))

        vdh.ShowVisualizer()

    End Sub    
End Class

After looking at this architecture, you might be wondering, how am I going to debug my visualizer? Microsoft really did an outstanding job making this task simple by providing the VisualizerDevelopmentHost object.

In your VisualizerObjectSource derived class, add a Shared method that takes the same type of object that your visualizer does. In Mole's case, it's DependencyObject.

Take notice of the VisualizerDevelopmentHost constructor. It looks very similar to the attribute I described before which decorates the class library.

Calling TestMoleVisualizer from Another Application
'you must disable all multithreading inside the visualizer when using 
'this method

Mole.MoleVisualizerObjectSource.TestMoleVisualizer(Me.btnViewVisualTree)

To debug your visualizer, you'll need to do a few simple things. First, in the project which has the object you want to visualize, you must add a reference to Microsoft.VisualStudio.DebuggerVisualizers.

Next, place the above line of code where you want the visualizer to start. This call replaces you putting a breakpoint in your code, right clicking on the object, and selecting the visualizer you want to use. The parameter needs to be the same type object your visualizer supports.

Next, set breakpoints on one or both sides of your visualizer, debugger and debuggee.

Now run the project, and when the breakpoints are reached, Visual Studio will break and allow you to debug your visualizer.

Mole Concepts

Data Classes

With all the new visualizer concepts introduced above, I wanted to simplify the design of Mole. Since Mole's data objects are used on both the debugger and debuggee sides, I decided early on to not attach code to the data objects. You'll find a similar pattern being used in WCF with respect to data contracts. All of the data processing logic is separate from the data itself, which makes the entire system easier to understand and maintain. It can be confusing when some methods on an object can only be called when running in the debugger or debuggee process.

The debuggee side creates data. The debugger side consumes the data.

Mole's three data classes are in the Tree.vb, TreeElement.vb, and TreeElementProperty.vb files.

Tree Class

The Tree class is used to contain the entire visual tree you are visualizing. When the class is first created, most of the data about individual elements in the tree is not loaded. This radically speeds up returning the visual tree from the data source. The lazy loading feature of Mole will load those unpopulated objects when the user requests to view them for the first time.

One bridge we had to cross was how to get the additional information that the debugger side object needed when the user wants to inspect an element in the tree. We use a generic Dictionary on the debuggee side to hold a reference to every object in the visual tree and use a unique integer as the key. That key is assigned to the Id property in the TreeElement class. The code fragment below shows how the dependency object reference is saved in the Dictionary. When the debugger side needs to request information from the debuggee side, it passes the key along with the request.

Private Function BuildElement(ByVal root As DependencyObject, _
               ByVal objFirstVisual As DependencyObject, _
               ByRef intInitialElementId As Integer) As TreeElement
    'this is the value used to uniquely identify each element
    'allows both sides debugger and debuggee to refer to the same
    'object across process boundaries
    _intElementIdCounter += 1
   
    'save dependency property for future calls to TransferData
    _dictVisualTree.Add(_intElementIdCounter, root)
    
    ...
    ...
End Function

TreeElement Class

The TreeElement class holds data displayed in the Element Tree treeview control on the left side of the UI. This object tracks the state of lazy loaded objects' XAML, Image, and Properties.

TreeElementProperty Class

The TreeElementProperty class holds the data which is loaded into the Element Property DataGridView control. This data is lazy loaded. If you want to extend Mole to display more information in the DataGridView, this is the class to modify.

Element Tree TreeView Control

The TreeView control is, metaphorically speaking, the 175lbs brick slowing Mole down. It is the loading of the TreeView nodes that takes most of the time, especially if you have thousands of visual tree elements. Special thanks to Josh for his code to lazy load the TreeView's nodes. His version was better than mine, so Mole uses his.

Not only are the properties, images, and XAML lazy loaded, but the TreeView control nodes are lazy loaded. This is what gives Mole the performance it requires.

Cast Reduction

Another performance enhancement came from making a custom MoleTreeNode class that derives from TreeNode. This class exposes a TreeElement property to store a reference to the above TreeElement class that is associated with a node in the Element Tree treeview. Using the TreeNode.Tag property is a viable alternative to subclassing TreeNode, but then you have to cast the Tag property object every time you want to access the data.

Drilling Back for Collection Data

One of Mole's cool features is its ability to execute an on the fly drill through operation to retrieve a property's values that are stored in a collection. I'll show the code for this feature here.

'function takes an object and returns a sorted list of all 
'public instance property names.
'

Private Function GetColumns(ByVal obj As Object) As List(Of String)
    Dim objList As New List(Of String)
    For Each mi As MemberInfo In obj.GetType.GetMembers( _
       BindingFlags.Public Or BindingFlags.Instance)
       
        If mi.MemberType = MemberTypes.Property Then
            objList.Add(mi.Name)
        End If
    Next
    objList.Sort()
    Return objList
End Function

'function takes a dependency object, a property name and returns a DataTable
'with all property values for each object in the collection.
'

Private Function MakeDataTableFromIEnumerable(ByVal target As _
        DependencyObject, ByVal strPropertyName As String) As DataTable

    Dim prop As PropertyInfo = target.GetType.GetProperty(strPropertyName)
    Dim obj As IEnumerable = CType(prop.GetValue(target, Nothing), _
        IEnumerable)
    Dim dt As New DataTable
    Dim bolColumnsCreated As Boolean = False
    Dim intX As Integer

    'get a little more performance
    dt.RemotingFormat = SerializationFormat.Binary
    
    Try
        For Each objItem As Object In obj
            'this code block only runs once to get the column names
            If Not bolColumnsCreated Then
                bolColumnsCreated = True
                'this calls the above GetColums function 
                For Each s As String In GetColumns(objItem)
                    dt.Columns.Add(s)
                Next
            End If

            If dt.Columns.Count = 0 Then
                Return dt
            End If

            intX += 1

            Dim dr As DataRow = dt.NewRow

            For Each c As DataColumn In dt.Columns

                Dim propInfo As PropertyInfo = _
                    objItem.GetType.GetProperty(c.ColumnName)

                If propInfo IsNot Nothing Then

                    Dim value As Object = _
                        propInfo.GetValue(objItem, Nothing)

                    If value IsNot Nothing Then
                        dr.Item(c.ColumnName) = value.ToString
                    Else
                        dr.Item(c.ColumnName) = "null"
                    End If

                End If

            Next

            dt.Rows.Add(dr)

            If intX > _
               INTEGER_MAXIMUM_ROWS_RETURNED_BY_COLLECTION_DATA_TABLES Then
                Exit For
            End If
        Next

    Catch ex As Exception
        'during debugging you can place a breakpoint here to stop and 
        'troubleshoot
        Debug.WriteLine(ex.ToString)
    End Try

    dt.AcceptChanges()
    Return dt

End Function

The above code is simple Reflection in action. The function will iterate though the target collection items. After reading the first item, iterate through the property names, and add a table column for each property. Now for every item in the collection, iterate through the column names and retrieve the property's value and add it to the DataTableRow.

Finally, it returns the DataTable for rendering in the UI. Note the DataTable's RemotingFormat property has been set to SerializationFormat.Binary. This is a performance enhancement from .NET 2.0.

Mole's Home Page

For everything Mole, visit Mole's home page by clicking here.

Mole Speak (fun stuff)

  • Molenator - (n) A creator of Mole
  • Burrow - (v) To dig through the visual tree
  • Molenate - (v) To use Mole

Sample questions you can ask fellow WPF developers:

  • Are you Mole'n? Response: Oh yea, I'm Mole'n.
  • Do you Molenate? Response: Doesn't everyone?
  • Do you Burrow? Response: Every time I need to debug my WPF code.

So do you Molenate? :P

Close

We hope this article can help someone learn a little more about Visual Studio Visualizers for WPF. We also hope that Mole helps WPF developers across the world have an easier time debugging complex user interfaces.

History

  • 26 November 2007 - Initial release.
  • 26 November 2007 - Added a new feature to the XAML viewer. You can now view the XAML in compressed or expanded format (attributes on single line or each on their own line). Changed the datagrids to not display the row headers. Uploaded new builds.
  • 27 November 2007 - Josh revised the article a bit, sanded a few rough edges, put some polish on, added a few sentences, and removed a little here and there.
  • 27 November 2007 - Karl added link to Mole's home page.

License

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

About the Authors

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

Karl Shifflett
Architect Gayle Manufacturing Company
United States United States
Karl loves .NET, WPF, WCF, ASP.NET, VB.NET and C#.
 
Awards:
 
  • December 2008 VB.NET Code Project Article Award
  • 2009 Code Project MVP
  • 2008 Code Project MVP
  • 2008 Microsoft MVP - Client App Dev
  • December 2007 VB.NET Code Project Article Award
  • Gold Medal Winner at IBM's 1998 PROIV Programming Contest in Las Vegas
Click here to check out my Blog
 
Click here to learn about Mole 2010 debugging tool for Visual Studio 2010
 
Click here to read about XAML Power Toys
 

Just a grain of sand on the worlds beaches.

Follow on   Twitter

Comments and Discussions

 
GeneralRe: Solution Re: I am getting a Mole.TransferDataRequest InvalidCastException PinmvpJosh Smith9-Dec-08 6:40 
GeneralGreat PinmemberPaul Conrad29-Dec-07 7:36 
GeneralRe: Great PinmemberKarl Shifflett30-Dec-07 12:25 
GeneralFeature Requests PinmemberDrewS28-Nov-07 5:10 
GeneralRe: Feature Requests PinmvpJosh Smith28-Nov-07 5:18 
GeneralRe: Feature Requests PinmemberKarl Shifflett28-Nov-07 5:36 
GeneralRe: Feature Requests [modified] PinmemberKarl Shifflett28-Nov-07 8:18 
GeneralRe: Feature Requests PinmvpJosh Smith28-Nov-07 8:31 
AnswerRe: Feature Requests PinmemberKarl Shifflett28-Nov-07 8:41 
GeneralRe: Feature Requests PinmvpJosh Smith28-Nov-07 8:44 
GeneralRe: Feature Requests PinmemberKarl Shifflett28-Nov-07 8:56 
GeneralRe: Feature Requests PinmvpJosh Smith28-Nov-07 8:59 
NewsRe: Feature Requests PinmemberKarl Shifflett28-Nov-07 17:07 
AnswerRe: Feature Requests PinmemberKarl Shifflett29-Nov-07 2:11 
GeneralRe: Feature Requests PinmemberDrewS1-Dec-07 3:31 
GeneralRe: Feature Requests PinmemberKarl Shifflett1-Dec-07 3:44 
GeneralRe: Feature Requests PinmemberDrewS1-Dec-07 3:52 
GeneralRe: Feature Requests PinmemberKarl Shifflett1-Dec-07 3:54 
GeneralComments/Suggestions PinmemberDrewS27-Nov-07 19:26 
AnswerRe: Comments/Suggestions PinmemberKarl Shifflett28-Nov-07 0:57 
GeneralRe: Comments/Suggestions PinmemberDrewS28-Nov-07 3:10 
AnswerRe: Comments/Suggestions PinmemberKarl Shifflett28-Nov-07 3:20 
AnswerRe: Comments/Suggestions PinmemberKarl Shifflett28-Nov-07 2:08 
GeneralRe: Comments/Suggestions PinmvpJosh Smith28-Nov-07 3:25 
Generalthe only trouble... Pinmembernull_nil27-Nov-07 5:35 
GeneralRe: the only trouble... PinmvpJosh Smith27-Nov-07 5:50 
AnswerRe: the only trouble... Pinmembernull_nil27-Nov-07 6:29 
GeneralRe: the only trouble... PinmvpJosh Smith27-Nov-07 11:31 
AnswerRe: the only trouble... [modified] PinmemberKarl Shifflett27-Nov-07 6:20 
GeneralEvery once and a while.... PinmemberBrennon Williams26-Nov-07 13:30 
GeneralRe: Every once and a while.... PinmemberKarl Shifflett26-Nov-07 14:02 
GeneralRe: Every once and a while.... PinmvpJosh Smith27-Nov-07 3:54 
GeneralRockin! PinmvpJosh Smith26-Nov-07 8:04 
GeneralRe: Rockin! PinmemberKarl Shifflett26-Nov-07 8:10 
GeneralRe: Rockin! Pinmembermike.strobel27-Nov-07 6:30 
GeneralRe: Rockin! PinmemberKarl Shifflett27-Nov-07 6:44 
GeneralRe: Rockin! Pinmembermike.strobel27-Nov-07 6:58 
GeneralRe: Rockin! PinmemberKarl Shifflett27-Nov-07 7:07 
GeneralGreat PinmvpRama Krishna Vavilala26-Nov-07 4:17 
GeneralRe: Great PinmemberKarl Shifflett26-Nov-07 4:32 
Generalyou 2 are mental PinmemberSacha Barber26-Nov-07 3:58 
GeneralRe: you 2 are mental PinmemberKarl Shifflett26-Nov-07 4:05 
GeneralRe: you 2 are mental PinmemberSacha Barber26-Nov-07 4:49 
GeneralRe: you 2 are mental PinmemberKarl Shifflett26-Nov-07 5:04 
GeneralRe: you 2 are mental [modified] PinmemberSacha Barber26-Nov-07 5:20 
GeneralRe: you 2 are mental PinmemberMuammar©26-Nov-07 19:22 
GeneralIm not as bad as this PinmemberSacha Barber26-Nov-07 20:58 

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
Web03 | 2.8.140721.1 | Last Updated 31 Dec 2007
Article Copyright 2007 by Josh Smith, Karl Shifflett
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid