Chapter 50. the automation model

WHAT'S IN THIS CHAPTER?

  • Understanding the Visual Studio extensibility options

  • Working with the Visual Studio automation model

Often you will find yourself performing repetitive tasks when working in Visual Studio, and wish you could bundle all those tasks into a single automated task, streamlining your workflow, decreasing your frustration at doing the same thing repeatedly, and consequently increasing your productivity. Alternatively, perhaps you want to add functionality to Visual Studio to share with other developers in your company (or even around the world). Fortunately, Visual Studio has been designed to be very extensible — in fact, many features that you may have thought were built into Visual Studio are actually extensions themselves! This extensibility is exposed to make it very easy to add the functionality to Visual Studio that suits your requirements. Extensibility points include automating tasks, adding new tool windows, adding features to the code editor, adding your own menu items (including items to the code editor's context menu), creating debug visualizers, creating your own wizards, extending existing dialogs, and even adding your own editors/designers and programming languages! This chapter looks at the options available for extending Visual Studio, and takes a look at the automation model used by both macros and add-ins.

VISUAL STUDIO EXTENSIBILITY OPTIONS

Unfortunately, the extensibility story in Visual Studio is a bit murky, because a number of different means exist to extend Visual Studio and it can be hard to determine which method you should use for what you want to achieve. Here are the various extensibility options available for Visual Studio, and the context in which it is most appropriate to use each:

  • Macros are the easiest way to automate Visual Studio, and can be thought of as a scripting language for the IDE. Macros are best suited to quickly automating a task (such as manipulating the text in the code editor, or automating a repeated set of tasks in the IDE). Macros are rather limited in their capabilities, and suited only to simple automation tasks. Macros must be written in VB — no other language is supported. Macros can be shared between developers, but require sharing macro project files (including their source code). Chapter 52 covers how to develop macros for Visual Studio 2010.

  • Add-ins are more powerful than macros (despite both working against the Visual Studio automation model), enabling you to also create tool windows and wizards, and integrate other features seamlessly within the IDE itself. Add-ins are compiled projects (in your favorite .NET language or Visual C++), enabling you to ship a binary to other developers rather than the code itself. Chapter 51 covers how to develop add-ins for Visual Studio 2010.

  • VSPackages are a part of the Visual Studio SDK (a separate download and install), and provide even more power than add-ins. VSPackages enable you to access the core internal interfaces in Visual Studio, and thus are ideally suited to integrating your own editors, designers, and programming languages into Visual Studio. Coverage of VSPackages, however, is beyond the scope of this book. More information of VSPackages can be found in the book Professional Visual Studio Extensibility by Keyvan Nayyeri.

  • Managed Extensibility Framework (MEF) component parts enable you to extend the new WPF-based code editor in Visual Studio 2010 in order to change its appearance and behavior. If you want to add features to the code editor, this is the best option for your need. Chapter 53 covers how to develop code editor extensions for Visual Studio 2010.

The next few chapters take you through some of the various ways in which you can extend Visual Studio, including using add-ins, macros, and the Managed Extensibility Framework (MEF). However, we continue in this chapter by looking at the core Visual Studio 2010 automation model that both macros and add-ins rely upon to interact with Visual Studio.

THE VISUAL STUDIO AUTOMATION MODEL

The Visual Studio automation model, also known as Development Tools Extensibility (abbreviated as DTE, which you will see used in the automation model), is an object model exposed by Visual Studio that you can program against to interact with the IDE. This object model allows you to perform many actions in Visual Studio to achieve a required behavior, handle events raised by Visual Studio (such as when a command has been activated), and various other functions such as displaying a custom dockable tool window within the Visual Studio IDE.

This object model is the means by which both add-ins and macros interact with the Visual Studio IDE, so this section takes a deeper look at its structure and the functionality that it exposes.

An Overview of the Automation Model

The Visual Studio automation model (DTE) is a COM-based object model that has been added to with each new version of Visual Studio, over time making it somewhat confusing and messy.

DTE consists of various COM interfaces and their associated implementations covering the facets of functionality in Visual Studio. Because the concrete classes mostly implement a corresponding interface, you can expect to see lots of pair classes: an interface and its implementation. For example, the root object is the DTE class, which implements the _DTE interface.

By their very nature, interfaces don't support extensibility and should never be changed, because any change in their structure breaks the structure of any class that implements the original interface. As Visual Studio matured and new versions were released (each requiring new functionality to be added to the existing classes in the object model), this created a problem. Microsoft couldn't update the existing interfaces or it would cause problems with existing add-ins, so instead it decided to create new versions of the interfaces with each new Visual Studio version by deriving from the previous version and adding the new requirements to it. These new interfaces were suffixed with a revision number so they didn't have the same name as their predecessor, thus creating the messy and unfriendly model we have today where multiple interfaces/classes represent the same part of the object model.

For example, you can check out the Debugger, Debugger2, Debugger3, Debugger4, and Debugger5 interfaces. The Debugger interface was a part of Visual Studio 2003 and was the original interface. Debugger2 is an updated version of Debugger for Visual Studio 2005, Debugger3 came with Visual Studio 2008, Debugger4 came with Visual Studio 2008 SP1, and Debugger5 came with Visual Studio 2010. The root DTE interface also has a revision called DTE2, and you will normally use this rather than its predecessor.

What this means in practical terms is that navigating the object model hierarchy isn't straightforward. The model will expose the methods on the classes in the early manifestation of the model, but you will need to cast the object to a more recent interface to access the functions it exposes. For example, the first iteration of the Solution object didn't provide the ability to create a solution folder — this didn't come until later where the AddSolutionFolder method was exposed on the object by the Solution2 interface. So the following macro code will not work:

Public Sub AddSolutionFolder()
    DTE.Solution.AddSolutionFolder("TestFolder") 'Will not work
End Sub

but this macro code will:

Public Sub AddSolutionFolder()
    Dim solution As Solution2 = DirectCast(DTE.Solution, Solution2)
    solution.AddSolutionFolder("TestFolder")
End Sub

As you can see, this makes using the automation model difficult with it commonly necessary to cast objects to interfaces, also creating somewhat messy code.

Because the underlying automation model is COM-based and we are using managed code to interact with it, we need to use interop assemblies to provide the bridge between our managed code and the COM object model. Unfortunately, like the object model itself, these are somewhat messy too. An additional interop assembly has been added with each version of Visual Studio, so your project will need to reference each interop assembly, from the base interop assembly up to the one released with the lowest version of Visual Studio that your add-in or macro will support. For example, add-ins or macros that support only Visual Studio 2010 will need to have references to your project to EnvDTE.dll (from Visual Studio 2003), EnvDTE80.dll (from Visual Studio 2005), EnvDTE90 .dll (from Visual Studio 2008), EnvDTE90a.dll (from Visual Studio 2008 SP1), and EnvDTE100.dll (from Visual Studio 2010).

Note

It's worth noting that the Visual Studio SDK is now somewhat taking the place of the Visual Studio automation model going forward, with fewer new features in Visual Studio being added to the automation model and more focus and emphasis being placed on using VSPackages instead (in the Visual Studio SDK). However, despite its flaws, the Visual Studio automation model is still very functional and able to perform most common tasks when integrating with Visual Studio.

Let's now take a look at some of the various functional areas of Visual Studio that the automation model exposes to us, including solutions and projects, documents and windows, commands, debuggers, and events. All of these exist under the root DTE object (which should be cast to DTE2 to expose the more recent revision of this object).

Note

Code examples are macro code, with macros discussed in detail in Chapter 52. Most examples output information using the Debug.Print command, which you can view in the Output window in the Macros IDE.

Solutions and Projects

The DTE.Solutions object enables you to automate the currently open solution, such as enumerate the projects that it contains, create a new project in the solution (or remove a project), add a solution folder, get/update solution configuration and properties, get/update its build configuration, or even open a new solution in the Visual Studio IDE and work with that. The following code demonstrates enumerating the projects in a solution, printing the project names and the number of project items in each project to the Output window:

Public Sub EnumerateProjects()
    For Each project As Project In DTE.Solution.Projects
        Debug.Print(project.Name & " contains " & _
                    project.ProjectItems.Count.ToString() & " project items")
    Next project
End Sub

Note

Note that you can also enumerate the projects in the active solution using the DTE.ActiveSolutionProjects collection.

You can also automate the projects in the solution. This includes enumerating the project items in a project, and the files it contains. You can also get/update the project's configuration and properties, and add or remove items from the project:

Public Sub EnumerateProjectsItems()
    Dim project As Project = DTE.Solution.Projects.Item(1) 'Get first project

    For Each projectItem As ProjectItem In project.ProjectItems
        Debug.Print(projectItem.Name)
    Next projectItem
End Sub

Windows and Documents

Windows in Visual Studio are either tool windows (such as the Solution Explorer, Tasks window, and so on) or document windows (files open in the code editor or a designer). Working with all types of windows is relatively simple.

You can enumerate through all the open windows and get details of each window as follows:

Public Sub EnumerateOpenWindows()
    'This includes both tool windows and document windows
    For Each window As Window2 In DTE.Windows
        Debug.Print(window.Caption & " | State = " & window.WindowState.ToString())
    Next window
End Sub

Next, take a look at how to work with tool windows. Use the following code to get a reference to a window (whether or not it's open) and interact with the window itself (activating it, showing it, hiding it, collapsing it, pinning it, and so on):

Public Sub ShowandDockTaskListWindow()
    Dim window As Window2 = DTE.Windows.Item(Constants.vsWindowKindTaskList)
    window.Visible = True 'Show it
    window.IsFloating = False 'Dock it
    window.AutoHides = False 'Pin it
    window.Activate()
End Sub

You can get a reference to a specific tool window (such as the Task List), and interact with its functionality (such as adding tasks to the Task List):

Public Sub AddTaskToTaskList()
    Dim tasksWindow As TaskList = DTE.ToolWindows.TaskList
    tasksWindow.TaskItems.Add("", "", "Created by a macro")
End Sub

As you can see, working with the tool windows is fairly straightforward. Now look at how to work with document windows. You can get a reference to the active window in the IDE like so:

Dim window As Window2 = DTE.ActiveWindow

You can even obtain a reference to the Visual Studio IDE window itself to manipulate:

Dim window As Window2 = DTE.MainWindow

It's possible to automate the document windows in Visual Studio, including opening or closing a document window, activating it, and getting the project item object opened in the document window. The following example enumerates through the open document windows, printing the filename and its path to the output window:

Public Sub EnumerateOpenDocuments()
    For Each document As Document In DTE.Documents
        Debug.Print(document.Name & ", Path=" & document.Path)
    Next document
End Sub

To get a reference to the active document window, use:

Dim document As Document = DTE.ActiveDocument

You can use the DTE.WindowConfigurations collection to manipulate the configuration of windows in the IDE.

Commands

Every executable action in Visual Studio is represented by a command. For example, all menu items execute a command when selected. Every command has a unique name, numeric ID (within its grouping), and GUID designating its grouping. Visual Studio has thousands of commands, as you can see by enumerating the DTE.Commands collection like so:

Public Sub EnumerateCommands()
    For Each command As Command In DTE.Commands
        Debug.Print(command.Name & ", ID=" & command.ID & ", GUID=" & command.Guid)
    Next command
End Sub

To perform an action in Visual Studio through your add-in or macro, you will need to get a reference to the appropriate command and execute it. For example, say you want to comment out the selected code in the code editor. This command is called Edit.CommentSelection, and you can execute it using the following code:

Public Sub ExecuteCommentSelectionCommand()
    DTE.ExecuteCommand("Edit.CommentSelection")
End Sub

Note

Finding the command name for a specific action can be difficult considering the number of commands that exist. The easiest way to find the name of a command so you can use it is to record a macro (see Chapter 52) of you performing the action, and inspect the code the macro recorder generates.

You can also listen for commands being executed, which are raised by Visual Studio as events that you can handle. An event will be raised before the command is executed, and another event will be raised after the command has completed. For example, you may want to do something particular when text is pasted into the code editor (that is, respond to the Edit.Paste command). Handling events is covered later in this chapter.

Debugger

You can control the various functions of the Visual Studio debugger using the DTE.Debugger automation object. This allows you to work with breakpoints, control code execution, and examine various aspects of the application being debugged (including processes and threads).

The following code demonstrates enumerating through all the breakpoints in the current solution:

Public Sub EnumerateBreakpoints()
    For Each breakpoint As Breakpoint2 In DTE.Debugger.Breakpoints
        Debug.Print(breakpoint.Name & " | File: " & breakpoint.File & _
                    " | Function: " & breakpoint.FunctionName & _
                    " | Line: " & breakpoint.FileLine)
    Next breakpoint
End Sub

You can also control the execution of code when debugging an application, such as starting debugging, terminating debugging, stepping over a line of code, running to the current cursor position, and so on. The following code demonstrates starting the current solution in the debugger:

Public Sub RunApplicationInDebugger()
    DTE.Debugger.Go()
End Sub

Events

The automation model enables you to listen for various actions in Visual Studio and respond to them by raising events that you can handle. The events are categorized into a number of objects according to their functional area under the DTE.Events object, including DocumentEvents, WindowEvents, BuildEvents, SolutionEvents, ProjectsEvents, DebuggerEvents, and many others. Chapter 51 demonstrates handling events in add-ins. In macros, the EnvironmentEvents module that is automatically added to each macro project defines event variables that you can create event handler methods for (using the Handles keyword). The following code captures the DocumentOpened event on the DTE.Events.DocumentEvents object (an instance of which has previously been created and stored in the DocumentEvents variable in the default EnvironmentEvents module). Place it in the EnvironmentEvents module, save the module, and close the Macros IDE:

Private Sub DocumentEvents_DocumentOpened(ByVal Document As EnvDTE.Document) _
                                          Handles DocumentEvents.DocumentOpened
    MsgBox("Document opened: " & Document.Name & " at " & DateTime.Now)
End Sub

This event handler will be raised whenever you open a new document in the Visual Studio IDE.

Summary

In this chapter you were introduced to the various means of extending the functionality of Visual Studio 2010, and you then took a look at the structure and capabilities of the Visual Studio automation model, which both add-ins and macros use to extend Visual Studio. The following two chapters look at these two means of extending Visual Studio using this object model.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.11.227