Customizing Visual Studio


Visual Basic .NET Unleashed
By Paul Kimmel
Table of Contents
Chapter 4.  Macros and Visual Studio Extensibility

The Visual Studio .NET Automation Model is extensive . As with macros it is unlikely that you will extend the IDE regularly unless you are in the business of writing tools and add-ins, but when you have a manual, repetitive task, extending the VS .NET IDE may be just the ticket. (It is also likely that, just as new component vendors emerged to create third-party components , new vendors to build IDE extensions are likely to emerge, too.)

Macro code is reasonably safe to run because you can disable events when you load a macro project and then you have an opportunity to examine the macro code before running it explicitly. (The event macros in the last section ran because I enabled macros when the promptrefer to Figure 4.7was displayed.) Unfortunately, macros can also be viewed by others. If you want the source code that is your macro to be concealed from third parties, you can create an Add-In project and ship the compiled add-in, instead of the uncompiled macro.

In this section we will take a look at the Visual Studio Extensibility model first and then I will demonstrate how to create add-ins.

Reviewing the Language-Specific Extensibility Object Model

Visual Studio .NET includes two extensibility object models. One is a project-neutral object model that provides access to the IDE, and the second is a programmable object model that provides access to projects and items in projects for Visual Basic .NET and Visual C# specifically . This section covers the objects on the latter object model.

The objects in the programmable object model are accessible by referencing VSLangProj.dll. The VSLangProj namespace includes the top-level object VSProject, which provides access to References, Imports, Project, ProjectItem, Configuration, and BuildManager objects.

This section briefly discusses the services or information these objects provide access to. You can use objects in VSLangProj in an automation or extensibility project by adding a reference to VSLangProj.dll. As a reminder to add the reference to a specific project, open the Solution Explorer, right-click on the Reference item, and in the .NET tab of the Add Reference dialog box (see Figure 4.9), select vsLangProj.dll. Click Select to add the DLL to the Selected Components list and click OK. When you have added a reference to VSLangProj.dll, add the VSLangProj namespace to the list of project imports on the Imports page of the project's Property Pages.

Figure 4.9. Adding the VSLangProj.dll reference to a project.


A graphic view of the programmable object model is available in VS .NET by browsing to the help URL ms-help://MS.VSCC/MS.MSDNVS/vbcon/html/vblrfvslangprojhierarchychart.htm. The help URL can be entered in the URL combo box on the Web toolbar.


VSProject provides access to other objects in the programmable object model. Getting a reference to a VSProject object is a bit convoluted but can be easily demonstrated using the Macros IDE to quickly create the code.

The DTE (Development Tools for Extensibility) object represents the highest level of the development environment. The DTE.Solution property refers to all of the projects in the current instance of the environment. DTE.Solution.Projects is the collection containing a reference to all of the projects in the solution. DTE.Solution.Projects.Item allows you to index individual projects in the solution. For example, DTE.Solution.Projects.Item(1) returns the first project in the solution. DTE.Solution.Projects.Item(1).Object returns an Object property whose reference may be a VB project.

Listing 4.6 tests to determine whether the project Object property refers to a VB project and typecast the Object to the extensibility type VSProject, which in turn allows us to access specific project items, references, project imports, and configuration information.

Listing 4.6 Working with the extensibility model begins by getting a reference to the VSProject object.
  1:  Public Sub Verbose()  2:   3:  Dim Project As Project = _  4:  DTE.Solution.Projects.Item(1)  5:   6:  If (Project.Kind = PrjKind.prjKindVBProject) Then  7:   8:  Dim VSProject As VSProject  9:  VSProject = CType(Project.Object, VSProject)  10:   11:  Const sMessage As String = _  12:  "Project {0} 's first namespace is {1} "  13:   14:  MsgBox(String.Format(sMessage, _  15:  VSProject.Project.Name, _  16:  VSProject.Imports.Item(1).ToString))  17:   18:  End If  19:   20:  End Sub 

As mentioned, working with the extensibility model requires a little extra effort. Lines 3 and 4 get a reference to the first project in the current solution. Lines 6, 8, and 9 test to determine whether the Project.Kind is a Visual Basic project, and if it is, converts the Project.Object reference to the VSProject extensibility reference. Project.Object is defined as an Object type, but the type of the object assigned to it is a specific object like an instance of VSProject.

When we have the appropriately cast VSProject reference, we can access the members of VSProject. For example, as demonstrated in the listing, we can determine the project name and discover or modify the namespaces that the project imports.


The VSProjectItem object refers to a specific element in a project. A module is an example of a VSProjectItem. Continuing our previous example from the preceding section, we can request a ProjectItem from the currently selected project. (From Listing 4.6 we know that we have a reference to the first project in the solution.) The VSProject.Project.ProjectItems collection contains the ProjectItem objects in the project.


The example refers to the first ProjectItem object's Document.FullName property. The sample code displayed the assemblyinfo.vb project item in a message box.

As discussed in the previous example, each of these objects provides specific capabilities. For example, you can add modules to the project by adding a module to the ProjectItems collection. Document defines capabilities for managing documents like activating a window or saving the document.

References and Reference Objects

The VSProject.References collection supports programmatically managing references in a project. Because the References property is a collection, knowing how to use a collection in general means that you know how to use the References collection.

Assuming we have used code to get a reference to the VSProject object for a specific project, we can programmatically examine, add, or remove references at the project level. Listing 4.7 (macro code defined in MacroProject.vsmacros on this book's Web site) adds a reference to VSLangProj to the project referred to by the VSProject object.

Listing 4.7 Adding a reference to a project programmatically
  1:  Public Sub AddReference()  2:  VSProject.References.Add("VSLangProj")  3:  End Sub  4:   5:  Private Function VSProject() As VSProject  6:  Const sMessage As String = _  7:  "Project is not a Visual Basic project."  8:   9:  If (Project.Kind = PrjKind.prjKindVBProject) Then  10:  Return CType(Project.Object, VSProject)  11:  Else  12:  Throw New System.Exception(sMessage)  13:  End If  14:  End Function  15:   16:  Private Function Project(Optional ByVal Index _  17:  As Integer = 1) As Project  18:  ' By default return the first project!  19:  Return DTE.Solution.Projects.Item(Index)  20:  End Function 


Flatten complicated object models with query methods , as demonstrated in Listing 4.7.

In the example, the macro AddReference relies on the VSProjectlines 5 through 14and Projectlines 16 through 20query methods to obtain a reference to a project.

Imports Object

The Imports collection works in a manner identical to the References collection. If you have a reference to the VSProject object, you can programmatically examine, add, and remove individually imported namespaces at the project level.

BuildManager Object

The BuildManager object is available to third-party vendors to facilitate integrating portable executable files. BuildManager was implemented to support developers who want to build custom extensions to the Visual Studio .NET IDE. (This is one of those subjects that demonstrates how big and complex the .NET Framework is; it could justifiably fill a book of its own. For this reason, I am not going to explore the subject here.)

Project Object

The Project object represents a single project in a solution. The Project query method on lines 16 through 20 of Listing 4.7 demonstrates how to get a reference to a single Project object.

Configuration Object

A Configuration object belongs to a Project object and allows you to programmatically access configuration information about a project. The macro demonstrates retrieving and displaying the configuration name of the active configuration.

 Public Sub ConfigurationDemo()   Dim Config As Configuration   Config = _     Project.ConfigurationManager.ActiveConfiguration   MsgBox(Config.ConfigurationName()) End Sub 

Note that the code fragment example depends on the Project query method in Listing 4.7.

ProjectItem Object

A ProjectItem represents an individual element in a project. Refer to the example in the earlier section "VSProjectItem."

Reviewing the ProjectNeutral Extensibility Object Model

The project-neutral object model provides access to non-language-specific elements that can be manipulated programmatically, like windows , command bars, add-ins, documents, and debugger elements. You can view the complete model in the Automation Object Model Chart by entering the following URL in the VS .NET IDE:


The whole object model is too big to cover in one chapter, but this subsection covers some of the highlights of the project-neutral, or Automation Object, model.

CodeModel Object

The CodeModel provides you with programmatic access to specific constructs in a source code file. CodeModel objects allow you to programmatically access constructs in a source code file.

You can programmatically add constructs using a CodeModel object. The methods that allow you to add constructs are prefixed with Add followed by the construct type. For example, the method to add a structure is AddStruct.

The CodeModel.CodeElements collection property contains CodeElement objects. Generally there is a CodeElement for each declarative statement in a source code file. Listing 4.8 uses recursive descent to walk a CodeModel.

Listing 4.8 Recursively walking through all of the elements of a CodeModel
  1:  Option Explicit On  2:  Option Strict Off  3:   4:  Imports EnvDTE  5:  Imports System.Diagnostics  6:  Imports VSLangProj  7:  Imports System.Reflection  8:   9:  Public Module VSProjectDemo  10:   11:  [ General Extensibility Object Model Demos ]  12:   13:  #Region " Project Neutral Extensibility Object Model "  14:   15:  #Region " Macro Entry Points "  16:  'Assumes an open project and module  17:  Public Sub WriteDocument()  18:  Dim Doc As Document  19:  Doc = Project.ProjectItems.Item(1).Document  20:  GetActivePane.OutputString(Doc.FullName)  21:  End Sub  22:   23:  Public Sub WalkSourceCode()  24:  WalkCodeElements(_  25:  ActiveDocument.ProjectItem.FileCodeModel.CodeElements)  26:  End Sub  27:  #End Region  28:   29:  #Region " Private Implementation Details "  30:   31:  Private Sub WalkCodeElements(ByVal Elements As CodeElements)  32:   33:  Dim Element As CodeElement  34:  For Each Element In Elements  35:  DisplayElement(Element)  36:  If (HasChildren(Element)) Then  37:  WalkCodeElements(Element.Members)  38:  End If  39:  Next  40:  End Sub  41:   42:  Private Function HasChildren(ByVal Element As CodeElement) As Boolean  43:  Return ((Element.IsCodeType()) And _  44:  (Element.Kind <> EnvDTE.vsCMElement.vsCMElementDelegate)) Or _  45:  (Element.Kind = EnvDTE.vsCMElement.vsCMElementNamespace)  46:  End Function  47:   48:  Private Sub DisplayElement(ByVal CodeElement As CodeElement)  49:  GetActivePane.OutputString(Formatted(CodeElement))  50:  End Sub  51:   52:  Private Function Formatted(_  53:  ByVal CodeElement As CodeElement) As String  54:   55:  Const Text As String = _  56:  "{0}  element {1}  found in {2} ." & vbCrLf  57:   58:  Return _  59:  String.Format(Text, CodeElement.Name, _  60:  CodeElement.Kind.ToString, _  61:  CodeElement.ProjectItem.Name)  62:  End Function  63:   64:  Private Function GetOutputWindow() As OutputWindow  65:  Return _  66:  DTE.Windows.Item(_  67:  Constants.vsWindowKindOutput).Object  68:  End Function  69:   70:  Private Function GetActivePane() As OutputWindowPane  71:  Return GetOutputWindow.ActivePane  72:  End Function  73:   74:  #End Region  75:   76:  #End Region  77:   78:  End Module 


When you see code enclosed in brackets ([...]) in a printed code listing, it's a placeholder representing condensed code; for the full uncondensed version, you can refer to the complete source listing on the Web site for this book.


IntelliSense will not display the Members property of a CodeElement used on line 33. Uncheck Hide Advanced Members on the General page in the dialog box that you access by choosing Tools, Options, Text Editor, All Languages Options in both the Macros IDE and VS .NET IDE to see advanced options. Members will still not show up in this instance, but other advanced members will.

WalkSourceCode beginning on line 23 and ending on line 26 is the entry point for the macro. WalkCodeElements from lines 31 through 40 uses recursion to walk through the code elements of a project. GetActivePane and GetOutputWindow were introduced in Listing 4.4 and return the IDE's Output window. DisplayElement and Formatted produce a nicely formatted output.

The biggest benefit of the CodeModel object is the capability of manipulating code without writing a parser. Find the CodeElement you need and manipulate it through the properties of the CodeElement object.

Document Object

The Document object is the reference to a physical document in a Project. (Refer to the section on the VSProjectItem earlier in the chapter for an example using a Document object.)

Debugger Object Model

The Debugger object model provides access to breakpoints, debugger events, expressions, the language object, processes, programs, the stack frame, and threads. (Refer to the section "Macros for Enabling and Disabling Breakpoints" for an example using the Debugger object.)


Visual BasicR. NET Unleashed
Visual BasicR. NET Unleashed
Year: 2001
Pages: 222 © 2008-2017.
If you may any questions please contact us: