Building your own Visual Studio Source Code Outliner extension


I’ll be giving my first Visual Studio Extensibility talk at the Seattle Code Camp on January 27, 2008.


My introduction to Visual Studio talk will be a 15 to 20 minute demo on how to build a real VS Tool Window.  I plan on creating a smaller version of the Source Code Outliner that’s available on http://codeplex.  The Source Code Outliner version I’m creating will show a tree view of the functions and classes in a .NET source file.  When you double click on a node in the tree view, it’ll highlight the function or class name and take you to that line of code.  A status message will also appear in the output window.


The VB code for this demo was provided by Aaron Marten, one of the developers on the Visual Studio Extensibility team.  He gave the same demo at DevTeach in Vancouver late last year.  I’ve ported the VB code to C# and will include the C# snippets in this blog.  However, the demo itself (and this blog) will be VB focused.


This blog post is going to walk you through step by step on how to build the add-in.  I plan on using this as a reference for anyone attending the Seattle Code Camp that wants to try and build this themselves.


Prerequisites:


1)      Install Visual Studio 2008.  If you don’t have a copy, you can download the trial edition from here


2)      Install the Visual Studio 2008 SDK from here


 


Stage 1 – Create the framework for the tool window


This is pretty easy as you just need to walk through the wizard.


1)      Launch Visual Studio 2008


2)      In the file menu, select File -> New -> Project


3)      Navigate to the Other Project Types -> Extensibility project


4)      Select the Visual Studio Integration Package


5)      Type in the Name, Location, and Solution name you want to use (or leave the defaults)


6)      Click OK


7)      The Visual Studio Integration Package Wizard will launch.  Click Next


8)      Select the language.  For this walkthrough (and my demo), I’ll be using Visual Basic.  Click on the Visual Basic radio button.  (Select C# if you plan on using the C# sample code below)


9)      Click Next


10)   Edit the company name, package name, and version information to anything you want.


11)   Leave the Minimal version as Standard (although you can also just use Pro) and add some detail information.


12)   Click Next


13)   In the VSPackage Options page, check Tool window


14)   Click Next


15)   Enter a window name.  I used “SouceCodeOutliner”


16)   Feel free to update the Command ID or leave it as the default


17)   Click Next


18)   Uncheck the test projects.  You can leave them but for the demo, I left them unchecked as they were beyond the scope of what I wanted to show.


19)   Click Finish


At this point, you’ve created a basic Tool window.  If you hit F5 at this point, you’ll launch a second instance of Visual Studio which uses the experimental hive.  The experimental hive is a copy of the registry keys that allows you to use Visual Studio in an environment that doesn’t affect or interfere with your current Visual Studio installation.  You can always reset your experimental hive by going to the short cut for the Visual Studio 2008 SDK Tools in your start menu.


You’ll also notice several files have been added to the project.  Here’s a quick description of the four important files (assuming you left the default project name):
















MyControl.vb


This is the design view and code view for your control


MyToolWindow.vb


This is the code for creating the tool window itself


VSPackage#.vsct


This is the XML file containing the definition on where the command shows up in the file menu


VSPackage#Package.vb


This is the file that contains the implementation for the IVSPackage class to make it a real VS Package.


 


For those using C#, replace .vb above with .cs in the above table and throughout this blog.


To view your tool window, in the second instance of Visual Studio, go to the View Menu -> Other Windows.  You’ll see your Source Code Outliner command there.  Selecting that will bring up a tool window which you can float around and dock within Visual Studio.


 


Stage 2 – Add some UI


1)      Open MyControl.vb form view


2)      Delete the default button


3)      Add a new button and set the Anchor to Top, Left, Right.  This can be done in the properties window.


4)      Add a new TreeView control and set the Anchor to Top, Left, Right, Bottom.


 


Stage 3 – Add a reference to the DTE (Designer Tools Environment) Object.  This is the automation object within Visual Studio.


1)      Open the code view for MyControl.vb


2)      In the file menu, select Project -> Add Reference


3)      Select EnvDTE


a.       There’s a bug that two copies of it shows up.


4)      At the top of the form, add the following import statements


 








VB Code


Imports EnvDTE


Imports Microsoft.VisualStudio.Shell


Imports Microsoft.VisualStudio


Imports Microsoft.VisualStudio.Shell.Interop


 


 








C# Code


using EnvDTE;


using Microsoft.VisualStudio.Shell;


using Microsoft.VisualStudio;


using Microsoft.VisualStudio.Shell.Interop;


 


 


5)      In the MyControl class, below the “Inherits UserControl” statement add the following code:


 








VB Code


Public dte As DTE


Public toolWindow As ToolWindowPane


 


 


 








C# Code


public DTE dte;


public ToolWindowPane toolWindow;


 


 


 


Stage 4 – Get the DTE Service


1)      Open the MyToolWindow.vb file


2)      In the New() function, add the following lines of code after the line “control = New MyControl()”


 


 








VB Code


control.dte = CType(Microsoft.VisualStudio.Shell.Package.GetGlobalService(GetType(Microsoft.VisualStudio.Shell.Interop.SDTE)), EnvDTE.DTE)


 


control.toolWindow = Me


 


 


       


 








C# Code


control.dte = (EnvDTE.DTE)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(EnvDTE.DTE));


 


control.toolWindow = this;


 


         


 


Stage 5 – Add an event handler for the button


1)      Go back to the form view for MyControl.vb and double click on the button


2)      In the event handler that gets generated, add the following code to clear the tree and get access to the document that’s loaded








VB Code


TreeView1.Nodes.Clear()


 


Dim fcm As FileCodeModel = dte.ActiveDocument.ProjectItem.FileCodeModel


 


 








C# Code


treeView1.Nodes.Clear();


 


FileCodeModel fcm = dte.ActiveDocument.ProjectItem.FileCodeModel;


 


 


Stage 6 – Add a class definition for the CodeElementNode


1)      Go to the code view for MyControl.vb


2)      We need to extend the Treenode class to get access to its properties.  Add the following class below the MyControl class


 








VB Code


Public Class CodeElementNode


Inherits TreeNode


 


Public CodeElement As EnvDTE.CodeElement


End Class


 


 


 








C# Code


public class CodeElementNode : TreeNode


{


    public CodeElementNode()


    {


    }


 


    public EnvDTE.CodeElement CodeElement;


}


 


 


Stage 7 – Add methods to recurse over the CodeMembers


1)      Go to the code view for MyControl.vb


2)      In the MyControl class, add the following methods to recurse over the Code Members.  I’ve added comments above the methods to describe more details what the method is doing.  At a high level, the code is iterating through the code model to find a class or function, then adding it to the tree view.


 








VB Code


 


 


‘ Recurse through the code model


‘ Get elements and add to the child of the current node.


‘ We only care about classes and functions


‘ Once we find the class or function, insert a node,


‘ then recurse its children


 


Private Sub RecurseCM(ByVal elements As CodeElements, ByRef nodes As TreeNodeCollection)


If elements IsNot Nothing Then


    For Each element As CodeElement In elements


        If (element.Kind = vsCMElement.vsCMElementNamespace _


            Or element.Kind = vsCMElement.vsCMElementClass _


            Or element.Kind = vsCMElement.vsCMElementFunction) Then


             Dim node As New CodeElementNode()


                node.CodeElement = element


                node.Text = element.Name


                nodes.Add(node)


                RecurseCM(GetMembers(element), node.Nodes)


        End If


    Next


End If


End Sub


 


 


 


‘ We can’t call element.members since members is not defined in the code elements class


‘ We need to cast to a specific type and then refer to the member on that derived type


Private Function GetMembers(ByRef element As CodeElement) As CodeElements


If Not element Is Nothing Then


    Select Case element.Kind


        Case vsCMElement.vsCMElementNamespace


            Dim ns As CodeNamespace = CType(element, CodeNamespace)


            Return ns.Members


        Case vsCMElement.vsCMElementClass


            Dim cls As CodeClass = CType(element, CodeClass)


            Return cls.Members


    End Select


End If


 


Return Nothing


 


End Function


 


 


 


 








C# Code


     


private void RecurseCM(CodeElements elements, TreeNodeCollection nodes)


{


    if (elements != null)


    {


        foreach (CodeElement element in elements)


        {


            if (element.Kind == vsCMElement.vsCMElementClass ||


                element.Kind == vsCMElement.vsCMElementNamespace ||


                element.Kind == vsCMElement.vsCMElementFunction)


                {


                    CodeElementNode node = new CodeElementNode();


                    node.CodeElement = element;


                    node.Text = element.Name;


                    nodes.Add(node);


                    RecurseCM(GetMembers(element), node.Nodes);


                }


        }


    }


}


 


private CodeElements GetMembers(CodeElement element)


{


    CodeElements members = null;


 


    if (element != null)


    {


        switch (element.Kind)


        {


            case vsCMElement.vsCMElementNamespace:


                members = ((CodeNamespace)element).Members;


                break;


            case vsCMElement.vsCMElementClass:


                members = ((CodeClass)element).Members;


                break;


        }


 


    }


       


    return members;


}


 


 


Stage 8 – Add a function call to RecurseCM


1)      We need to now call the code to recurse through the code model.  In the button event handler, add the following code:








VB Code


RecurseCM(fcm.CodeElements, TreeView1.Nodes)


TreeView1.ExpandAll()


 


 








C# Code


RecurseCM(fcm.CodeElements, treeView1.Nodes);


treeView1.ExpandAll();


 


 


Stage 9 – Add functionality to go to the line of code when a user double clicks on it in the tree view


1)      In the design view for MyControl.vb, add an event handler for NodeMouseDoubleClick


2)      Add the following code in the event handler:








VB Code


Dim node As CodeElementNode = CType(e.Node, CodeElementNode)


Dim doc As TextDocument = node.CodeElement.StartPoint.Parent


doc.Selection.MoveToPoint(node.CodeElement.StartPoint)


doc.Selection.SelectLine()


 


 








C# Code


CodeElementNode node = (CodeElementNode)e.Node;


TextDocument doc = node.CodeElement.StartPoint.Parent;


doc.Selection.MoveToPoint(node.CodeElement.StartPoint,false);


doc.Selection.SelectLine();


 


 


 


Stage 10 – Add text to the Output Window


1)      Every time someone double clicks on a node, we want to also write a message to the output window.  In the NodeMouseDoubleClick event handler, add the following code to get a reference to the Output Window pane and write to it.








VB Code


Dim generalPane As IVsOutputWindowPane = VsShellUtilities.GetOutputWindowPane(CType(Me.toolWindow.Package, System.IServiceProvider), VSConstants.GUID_OutWindowGeneralPane)


 


generalPane.OutputString(“Navigating to “ + node.Name + node.Text + System.Environment.NewLine)


 


 


 








C# Code


IVsOutputWindowPane generalPane;


 


generalPane = VsShellUtilities.GetOutputWindowPane((System.IServiceProvider)this.toolWindow.Package, VSConstants.GUID_OutWindowGeneralPane);


 


generalPane.OutputString(“Navigating to “ + node.Name + node.Text + System.Environment.NewLine);


 


 


At this point, your tool window is complete!  You can press F5 to start debugging through your new tool window.  In the View -> Other Windows menu, you can see your Source Code Outliner command.  Select that to see your tool window.  You can dock the window, drag it around, and do anything to it that all tool windows support.


To simplify this demo, I didn’t add any error handling.  Make sure you have a VB or C# code file loaded in the IDE of your experimental version of Visual Studio before clicking the button on your tool window. 


If it’s your first time launching VS in experimental mode, you will be prompted and asked what type of developer are you?  This happens every time you reset the experimental hive.


To see the full version of the Source Code outliner, go here.


To see more “How To” Videos to extend Visual Studio, go here.


To learn more about Visual Studio Extensibility, go here.


 

Comments (9)

  1. My last blog post was a step by step walkthrough on how to build a simple version of the source code

  2. My last blog post was a step by step walkthrough on how to build a simple version of the source code

  3. Quan is a new (and, I should say, very productive) PM on the VS Ecosystem Team. He’s now started blogging

  4. Quan is a new (and, I should say, very productive) PM on the VS Ecosystem Team. He's now started

  5. Last night, Ken Levy and I presented another VSX talk at the .NET Developer Association weekly meeting

  6. Last night, Ken Levy and I presented another VSX talk at the .NET Developer Association weekly meeting

  7. LinqToCodeModel is now on available on Code Gallery . LinqToCodeModel, created by Pablo Galiano of Clarius,

  8. Dom says:

    Can this be updated  for visual Studio 2010?