|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Mole v4 For Visual Studio - With Editing ReleasedAll 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! AuthoringNo 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 others 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 code. We have gone back and forth with our projects trying to deliver tools that developers will utilize. IntroductionMole 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
BackgroundThere are a growing number of 3rd 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. So 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 Mole displays an entire visual tree and over a hundred properties in a 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 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 supply 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 InstallationThere are a number of options available to you for getting Mole installed on your machine. You can just install Mole and being 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
Source Code InstallI have provided 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 XBAPsOne 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:
Thanks to Josh Smith and Code Project member "ivolved" for this tip! Using MoleMole is opened just like any other Visual Studio Visualizer, you click on the small magnifying glass icon, this displays the visualizers lists, 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
When Mole opens it looks like this. There is a 600px image width restriction here at Code Project. 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.
Element Tree - Mole's Garden
The Mole's Garden contains a Element Type - Element Name (Count Of Descendants) Some nodes were not assigned names and some nodes do not have any child nodes associated with them. Notice the Elements are displayed in one of four colors. White is an unselected element. Light Gray is the selected element when the The Light Brown 4 pixel line separating the two Select Initial button will select the object that you initially selected in Visual Studio. Expand All button will expand all Collapse All button will collapse all Expand Down button will expand all Collapse Down button will collapse all Selected Element Tab Header
TabControl has four TabPages.
The label below the Element Properties [Object Type] [Has Favorites] [Object Name] [location loaded from] Two of these need some explanation. [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 Element Properties Grid
The Element Properties grid displays each of the selected item's properties. Columns with 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 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 below image displays the collection from the Grid's Children property.
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" The "Show All" The "Show Attached Properties" The "If Has Favorites, Only Show Favorites" The "If Has Collections, Only Show Collections" 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" The displayed image can easily be copied to the Windows Clipboard by clicking on the "Copy Image" 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 Element XAMLExpanded View
Compressed View
The "Element XAML" The 'Font +' and 'Font -' The two You may right-click the This This Favorites
The Favorites LimitationsSo far, the only limitation I've run into is the Element XAML. Under certain circumstances the 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 Mole & Visualizers 101Before 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
Debuggee Side
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 ProjectIf 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 Second your Debuggee Side class which derives from 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.)")>
First attribute parameter is the type of our visualizer (Mole.Burrow). Second is the type of our data source (Mole.MoleVusualizerObjectSource). 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 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 SideImports 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. The objectProvider is the object the debugger side will use when communicating with the data object. Just make your API calls using the objectProvider. Read about the IVisualizerObjectProvider Interface. 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 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 SideCode abbreviated here for clarityPublic 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 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 is 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.
The Mole Visualizer Form - Debugger Side - UI Side - Requesting DataPrivate 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 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 Asynchronous Data Object Call The .NET Framework provides a rich set of multithreading tools for developers. The The call to The How To Debug A VisualizerPublic 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 In your Take notice of the 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 your want to visualize, you must add a reference to 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 ConceptsData ClassesWith 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 ClassThe 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 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 ClassThe TreeElementProperty ClassThe Element Tree TreeView ControlThe Not only are the properties, images and XAML lazy loaded, but the Cast ReductionAnother performance enhancement came from making a custom Drilling Back For Collection DataOne of Mole's cool features is its ability to execute an on the fly drill through operation to retrieve a properties 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 the every item in the collection, iterate through the column names and retrieve the property's value and add it to the Finally it returns the Moles Home PageFor everything Mole, visit Mole's home page by clicking here. Mole Speak (fun stuff)
Sample questions you can ask fellow WPF developers:
So do you Molenate? :P CloseWe 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
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||