Using Visual Studio .NET Macros

Now that the .NET mania is finally settling down and developers are starting
to adapt to life with the platform, it's a good time to consider a few
less-revolutionary (but very practical) details that can simply your
programming life. One of these is the often-overlooked macro engine and
extensibility model that's built into Visual Studio .NET. This model provides
almost 200 objects that give you unprecedented control over the IDE. This
includes the ability to access and manipulate the current project hierarchy,
the collection of open windows, and the integrated debugger. Using this model,
you can build everything from simple keystroke recordings to advanced add-ins
that present their own user interface and interact with the IDE. In this
article, we'll look at how you might use macros to automate common code-generation tasks, such as building property procedures for your classes.

Macro Basics

Macros are only one of the extensibility tools provided by Visual Studio .NET,
but they are easy to develop quickly, and they have surprising clout. Unlike
most other Microsoft products, Visual Studio .NET macros are built out of full
.NET code, which means they can use any part of the .NET class library. This
allows macros to write XML files, display forms, and even contact remote Web
services as part of their work. (For information about other types of Visual
Studio .NET extensibility, and help choosing which one suits a particular
problem, you can consult Microsoft's automation whitepaper
here.)

You can create a basic macro simply by recording your actions in the Visual
Studio .NET editor. Just follow these steps:

Click the stop button on the floating macro toolbar once you're finished.

You can now play the macro back using the menu or the convenience hotkey
Ctrl-Shift-P.

To view the code for a recorded macro, select Select Tool > Macros >
Macro Explorer. This window (shown below) shows a tree of macro modules, and
the macros they contain. Each macro corresponds to a Visual Basic .NET
subroutine.

To edit the macro you just created, right-click on the TemporaryMacro
subroutine in the RecordingModule and select Edit. A separate IDE will load for
editing macro code; it closely resembles the ordinary Visual Studio .NET
environment, right down to a Project Explorer and dynamic help. Visual Studio
only stores one temporary macro at a time, which is overwritten every time you
record a new one. To make a temporary macro permanent, you'll need to cut and
paste the code into a different subroutine.

As stated earlier, macro code uses ordinary .NET syntax and the class library.
However, macro code also has access to a special set of Visual Studio .NET
extensibility objects, which you won't recognize. These objects are used to
interact with windows, insert and read editor text, and so on. For example, to
add a new line into the editor, you would use the following macro code:

' Get the current insertion point (where the cursor is positioned).
Dim TS As TextSelection = DTE.ActiveDocument.Selection
' Move to the end of the line.
TS.EndOfLine()
' Add a new line (the programmatic equivalent of pressing Enter).
TS.NewLine()
' Insert some text.
TS.Insert("Sample Text")

All of these objects are contained in a special EnvDTE namespace, which is
contained in the EnvDTE.dll assembly. This assembly is referenced by default in
all macro projects.

Imports EnvDTE

A good way to start learning about macros is to use the record facility, and
then look at the code it generates.

Macros for Property Procedures

Visual Basic 6 included several add-ins that could automate class code,
including wizards that would generate a series of property procedures.
Unfortunately, Visual Studio .NET has nothing comparable. This problem becomes
apparent when developers begin to design a class. Unfortunately, it's far
simpler to type the following code statement:

Public MyVar As String

Than to create a full property procedure that ensures proper encapsulation of
private data:

Private _MyVar As String
Public Property MyVar As String
Get
Return _MyVar
End Get
Set(ByVal Value As String)
_MyVar = Value
End Set
End Property

The solution, clearly, is to create a macro that generates property procedures
automatically as needed. One approach is to create a straightforward macro that
examines the text at the current insertion point, and uses it to generate a
full property procedure. We'll begin coding this macro by writing a private
function (which will be added to the macro module) that takes a line of text
representing a private variable declaration, and creates a corresponding
property procedure declaration. In this case, the code assumes that private
variables will always start with a leading underscore (_), which will be
trimmed from the name of the property procedure. Depending on your conventions,
the code may need to be adjusted.

The next step is to create a macro that uses this function. The code below selects the current line,
generates a property procedure using GetInsertion(), and adds it after the
current line.

Public Sub ExpandPrivateMembersFromSelection()
Dim TS As TextSelection = DTE.ActiveDocument.Selection
Dim Insertion As String, Line As String
Dim Lines() As String = TS.Text.Split(vbNewLine)
For Each Line In Lines
Insertion &= GetInsertion(Line)
Next
TS.EndOfLine()
TS.NewLine()
TS.Insert(Insertion)
End Sub

The following figure shows this macro in the macro explorer. You can
double-click it to run it on the current editor line.

It's now easy to extend this macro to work with multiple lines. The
ExpandPrivateMembersFromSelection() macro below passes each selected line to
the GetInsertion() function, and builds up the returned text (any blank lines
will be ignored by GetInsertion() automatically). Then, the full text block is
inserted. For example:

Public Sub ExpandPrivateMembersFromSelection()
Dim TS As TextSelection = DTE.ActiveDocument.Selection
Dim Insertion As String, Line As String
Dim Lines() As String = TS.Text.Split(vbNewLine)
For Each Line In Lines
Insertion &= GetInsertion(Line)
Next
TS.EndOfLine()
TS.NewLine()
TS.Insert(Insertion)
End Sub

Now you simply need to type the following code:

Private _MyVar As String

And run one of the two macros to generate a corresponding property procedure
for any basic data type.

At this point, you may be wondering if it's possible to write this code in a
language-independent way to support any .NET language. This functionality is
called the CodeDOM in the .NET Framework. Using the CodeDOM is a complex
operation that is outside of the scope of this article.

Summary

Macros are a powerful tool in for automating repetitive tasks in any
application. However, when used correctly, macros can become much more, and
help enforce standards, promote good design, and improve consistency across an
entire organization. One way is by using add-ins or macros that generate
certain code structures in a standardized fashion.

Matthew MacDonald
is a developer, author, and educator in all things Visual Basic and .NET. He's worked with Visual Basic and ASP since their initial versions, and written over a dozen books on the subject, including The Book of VB .NET (No Starch Press) and Visual Basic 2005: A Developer's Notebook (O'Reilly). His web site is http://www.prosetech.com/.