Automating Repetitive Tasks


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

Automating Repetitive Tasks

Macros have existed for quite some time now in applications for PCs. If recollection serves, macros were available in early versions of Quattro Pro and Lotus 1-2-3, perhaps as early as 1987. Historically macros were implemented as a token-based language with a simple grammar and keywords that represented actions in an application. More recently macros were implemented and supported by applications in Microsoft Office through programming in Visual Basic for Applications (VBA).

With the introduction of .NET, macros are available and are written using Visual Basic .NET. The same language that you write your VB .NET applications with can be used to automate repetitive tasks. Macros still use commands to represent tasks in the IDE, but when you write customizations to these macros, you can write them in the same VB .NET code you use to write applications.

You gain two advantages in VS .NET. Macros can be written in VB, and more of the Visual Studio .NET IDE has been exposed in the extensibility model, making it easier to access aspects of the IDE for customization.

Demo: Recording a Macro

The easiest way to automate a task is to record a series of steps in the IDE and then play them back when you need that series of steps performed again.

In production code that I am building for a customer, I always include a brief comment and copyright block indicating ownership and authorship of the code. This code is tedious to write but beneficial to have. The code block varies only slightly between projects, so it is one of the first things I automate when I sit down to begin a new project. A sample of the copyright block follows .

 ' filename.vb - Raison d'etre ' Copyright  2001. All Rights Reserved. ' Software Conceptions, Inc. ' Written by Paul Kimmel. 

Although you will not see these blocks much in this book (because listings in this book already are protected by copyright), they are added to production code. Perhaps this copyright block is not perfectly legal, but it is functional. (Check with your company's legal department or an attorney specializing in copyright law if you adopt this strategy.)

Every time a new source code file is added to a project, the block is added to the code. If I turn the macro recorder on the first time I manually type the copyright block, I can play back the block of text for each successive file added to the project. To record the copyright block macro, follow these steps:

  1. In Visual Studio .NET, choose Tools, Macros, Record TemporaryMacro (or press Ctrl+Shift+R) to start recording.

  2. Type the block of text as you would like it to appear at the top of a source code .vb module.

  3. Choose Tools, Macros, Stop Recording (or press Ctrl+Shift+R) to stop recording.

The macro will be recorded as a temporary macro named TemporaryMacro in a module named RecordingModule. We will come back to this in a minute. For now you should know that you can press Ctrl+Shift+P to play the macro back, which is the shortcut for the Tools, Macros, Run TemporaryMacro menu item in the Visual Studio .NET IDE (hereafter just called IDE).

Keep in mind that if you record another macro, the current temporary macro will be overwritten. To protect your macro from being overwritten, you need to save a copy of it outside of TemporaryMacro. First, let's take a moment to view the macro code generated by the recording.

Viewing a Recorded Macro

You can view a recorded macro by opening the Macro Explorer. To open the Macro Explorer, choose Tools, Macros, Macro Explorer or press Alt+F8 (see Figure 4.1). From the Macro Explorer, double-click on the RecordingModule to look at the generated code.

Figure 4.1. Macro Explorer view.



You can export macro modules from the Microsoft Visual Studio Macros IDE, which you can run by choosing Tools, Macros IDE.

By default the recorded macros are stored in MyMacros.vsmacros. (You can use the Windows Explorer to find the physical file location of MyMacros.vsmacros on your PC.) In Figure 4.1 you can see the expanded RecordingModule in MyMacros. For all intents and purposes, RecordingModule is a VB .NET module within the .vsmacros file, and macros consist of procedures with an individual procedure entry point. Listing 4.1 shows the complete listing, including the temporary macro created after recording the copyright block.

Listing 4.1 The TemporaryMacro created from recording the copyright block.
  1:  Option Strict Off  2:  Option Explicit Off  3:  Imports EnvDTE  4:  Imports System.Diagnostics  5:   6:  Public Module RecordingModule  7:   8:  Sub TemporaryMacro()  9:  DTE.ActiveDocument.Selection.Text = _  10:  "' filename.vb - Raison d'etre"  11:  DTE.ActiveDocument.Selection.NewLine()  12:  DTE.ActiveDocument.Selection.Text = _  13:  "' Copyright (c) 2001. All Rights Reserved."  14:  DTE.ActiveDocument.Selection.NewLine()  15:  DTE.ActiveDocument.Selection.Text = _  16:  "' Software Conceptions, Inc."  17:  DTE.ActiveDocument.Selection.NewLine()  18:  DTE.ActiveDocument.Selection.Text = _  19:  "' Written by Paul Kimmel."  20:  DTE.ActiveDocument.Selection.NewLine()  21:  End Sub  22:  End Module 

The macro is straightforward; when the macro is run, a line of text is added, followed by a new line, until all of the lines of text are added. More importantly, this simple macro introduces some of the key features of the extensibility model, including a key featurethe DTE.

The Imports EnvDTE adds the common environment object model namespace to the macro project. EnvDTE implements the object model for extensibility.

The DTEDevelopment Tools Extensibility or Development Tools Environment, depending on the source of informationobject represents a handle to the IDE. DTE.ActiveDocument represents the document, or module, that has the focus. We will come back to these subjects later. For now let's examine the practical aspects of making the temporary macro more efficient and saving it to prevent the next recorded macro from overwriting this one.

Editing a Temporary Macro

TemporaryMacro is a little verbose. We will shorten it to demonstrate how we can edit recorded macros to extend capabilities.

From Listing 4.1 we know that filename.vb needs to be changed to reflect the actual filename. The description needs to be something specific and the date might not accurately reflect the actual year. Additionally, the temporary macro will be overwritten if a new macro is recorded; to improve the utility of the macro and prevent it from being overwritten, we will tackle these issues one by one.

Refactoring the Macro Code

Macros, like any other code, can benefit from refactoring. For string values you might consider replacing literals with constants. If the strings are contrived with code, you might combine replacing literals with constants and query methods ; adding a query method is a refactoring.

Listing 4.2 demonstrates a basic refactoring that replaces the literals created by the macro with query methods. The actual macro is much simpler, and just as with production code, the query methods could be used in another context.

Listing 4.2 A refactored macro.
  1:  Option Strict Off  2:  Option Explicit Off  3:  Imports EnvDTE  4:  Imports System.Diagnostics  5:   6:  Public Module RecordingModule  7:   8:  Const sDescription As String = _  9:  "' filename.vb = Raison d'etre"  10:  Const sCopyright As String = _  11:  "' Copyright (c) 2001. All Rights Reserved."  12:  Const sOwner As String = _  13:  "' Software Conceptions, Inc."  14:  Const sAuthor As String = _  15:  "' Written by Paul Kimmel."  16:   17:   18:  Private ReadOnly Property Description() As String  19:  Get  20:  Return sDescription  21:  End Get  22:  End Property  23:   24:  Private ReadOnly Property Copyright() As String  25:  Get  26:  Return sCopyright  27:  End Get  28:  End Property  29:   30:  Private ReadOnly Property Owner() As String  31:  Get  32:  Return sOwner  33:  End Get  34:  End Property  35:   36:  Private ReadOnly Property Author() As String  37:  Get  38:  Return sAuthor  39:  End Get  40:  End Property  41:   42:  Private Function GetCopyrightBlock() As String  43:  Return _  44:  Description & vbCrLf & Copyright & vbCrLf & _  45:  Owner & vbCrLf & Author & vbCrLf  46:  End Function  47:   48:  Sub TemporaryMacro()  49:  DTE.ActiveDocument.Selection.StartOfDocument()  50:  DTE.ActiveDocument.Selection.Text = GetCopyrightBlock()  51:  End Sub  52:   53:  End Module 

Because the constants are treated like resource strings, a simple conventionprefixing the resource with an senables the properties to be closely associated by name with the resource strings. The refactored solution assembles the copyright block by concatenating the resource strings returned by the query methods; the end result is a much simpler macro and potentially reusable query methods. To convey meaning, the properties playing the role of query methodsare implemented as read-only properties.

Writing code and macros this way requires a little extra effort but the end result is short, concise , well-named code that is genuinely flexible.

Dynamically Retrieving the ActiveDocument Name

Thus far our demonstration macro simply adds some text to our modules. For the macro to be more effective, it would be helpful if the macro added the correct filename to the copyright block. This will be useful, especially when we print the code for documentation purposes.

Now that the code is refactored, we can focus on one problem at a time. The first problem is retrieving the name of the module we are commenting and adding that information to the Description block.

Conveniently we can modify the literal to contain a string parameter, as follows:

 Const sDescription = "' {0}   Raison d'etre" 

Next we can modify the read-only property method to fill in the ActiveDocument name.

 Private ReadOnly Property Description() As String     Get       Return String.Format(sDescription, DTE.ActiveDocument.Name)     End Get End Property 

DTE.ActiveDocument.Name returns the document's name. The end result is a copyright block with the filename we are actually adding the copyright block to.

Adding a Dynamic Description Block

What about the comment? As it stands, the comment Raison d'etre is not very helpful. It would be useful if the documentor could add a comment, replacing the generic text with something particular to a given program.

Again we could modify the constant string and add a string parameter. At the time the user wants to add the block, we can display an InputBox and elicit a response. The modified string and Description property follow.

 Const sDescription As String = "' {0}  = {1} " Private Function GetReason() As String   Dim Reason As String = "Raison d'etre"   Return _   InputBox("Enter description:", "Description", Reason) End Function Private ReadOnly Property Description() As String   Get     Return _       String.Format(sDescription, DTE.ActiveDocument.Name, GetReason())     End Get End Property 


Notice that a refactored style of programming tends to introduce more methods but those new methods are very short. This style is elected by the author for very specific reasons. To reiterate, these reasons are: Singular methods are easier to debug; reuse is a good thing; therefore methods can be reused, and lines of code cannot.

In the short term , singular methods tend to produce more code. In a system, singular methods tend to be more extensible and reusable and result in significantly smaller and simpler systems.

The modified sDescription string contains two string- replaceable parameters. The modified Description property fills in both parameters. The first comes from the DTE.ActiveDocument object and the second comes as a return result from an InputBox dialog. Notice that instead of adding the code to solicit feedback from the user directly in the property, we used a query method; this eliminates a temporary variable and keeps the property method succinct.

Displaying the Correct Year

We can employ the same technique for updating the filename to update the copyright year. That is, by modifying the constant sCopyright string and replacing it with a param-eter, we can fill in the calendar year within the property method.

 Const sCopyright As String = _     "' Copyright (c) {0} . All Rights Reserved." Private ReadOnly Property Copyright() As String   Get     Return String.Format(sCopyright, Now.Year)   End Get End Property 

As in the previous section, the constant string now contains a replaceable parameter, represented by {0}, and the property method dynamically fills in that parameter upon request.

The benefit of this style of code is that each aspect of it in and of itself is very simple. You can extend, customize, and maintain it with very little effort. Writing like this takes some practice, but you end up with good code the first time and revising it to get great code is much easier.

Our motivation for refactoring macros is the same as that for refactoring consumer code: We want less code, and we want that code to be more maintainable .

Saving a Temporary Macro

Saving macros is easy whether you have refactored code or not. The easiest way to preserve a macro is to save the module it is in, RecordingModule, to have a new module name. It is a good idea to save the macrothe proceduretoo, giving it a better name. Follow these steps to rename the macro and the module.

  1. To rename TemporaryMacro, in the Visual Studio .NET IDE (not the Macros IDE), choose Tools, Macros, Save TemporaryMacro.

  2. Step 1 opens the Macro Explorer and highlights the macro name as if you had selected Rename from the Macro Explorer context menu. Enter a new name for the macro. For our example, rename the macro InsertCopyrightTag.

Now that we have the procedure that is the entry point for our macro, we can save the RecordingModule itself. Follow these steps to rename the RecordModule to Copyright.

  1. Right-click on RecordingModule in the Macro Explorer opened when we renamed the macro.

  2. From the context menu, choose Rename. Rename the RecordingModule to Copyright.

When you start recording a new macro, the IDE will create a new module named RecordingModule and add the TemporaryMacro automatically.

Running the Macro

There are a couple of ways that we can invoke our macro. While it is the temporary macro, we can run it with the menu commands Tools, Macros, Run TemporaryMacro. We can also run the TemporaryMacro (or any macro) by opening the Macro Explorer and selecting Run from the macro's context menu.


Macros can be run from the command line. Open a command window and type devenv /command macroname , where macroname is the qualified name of your macro, and you can run macros from the command line.

Finally, we can open the Command window (Ctrl+Alt+A) by choosing View, Other Windows, Command Window and enter the fully qualified name of the macro. To run the macro from the Command window, enter Macros.MyMacros.Copyright. InsertCopyrightTag.

Clearly, manually entering the Macros. root namespace.module.macroname would quickly become tedious if you use the macro more than a couple of times. If the macro will be used frequently, you might want to add a toolbar button or menu shortcut to invoke the macro. The next two subsections look at how you can incorporate or remove a shortcut to make invoking custom macros easier.

Mapping Macros to a Keyboard Shortcut

Frequently used macros can be mapped to keystrokes or toolbars for convenience. To map the InsertCopyrightTag macroor any macroto a keyboard shortcut, choose Tools, Options, Environment, Keyboard in the IDE (see Figure 4.2). Type copyright into the Show Commands Containing field. Select the InsertCopyrightTag macro (as shown in the figure), and select the Press Shortcut Key(s) text box. Press Ctrl+Alt+C, reflecting the shortcut key combination you want to map to the copyright macro.

Figure 4.2. Use the Options page to map a shortcut to your frequently used macros.



To run a macro without using a keyboard shortcut, open the Macro Explorer and double-click on a macro name.

When you have the macro shortcut keys selected, click the Assign button. Close the Options dialog box and return to the code editor. Press the shortcut keys to test your new macro shortcut.

Mapping Macros to the Toolbar

Macros can also be added as toolbuttons in the IDE. Right-click on the IDE toolbar and select the Customize context menu item. The Commands tab of the Customize dialog box (shown in Figure 4.3) enables you to add, modify, and delete toolbuttons.

Figure 4.3. Use the Commands tab of the Customize dialog box to manage custom toolbutton commands.


Select Macros from the Categories list. By default, if you drag a macro from the Customize dialog to the toolbar, a button with the name of the macro will be added to the toolbar. For example, dragging MyMacros.Copyright.InsertCopyrightTag to the Standard toolbar will insert a toolbutton with the caption MyMacros.Copyright. InsertCopyrightTag to that toolbar.

Modifying the Macro Toolbutton Description

A long caption will take up too much toolbar real estate. To decrease the amount of room used by the custom toolbutton, we can modify the name of the toolbutton.


You can add a keyboard shortcut from the Customize dialog box by clicking the Keyboard button. Clicking the Keyboard button opens the Options dialog box focused to the Keyboard tab.

Because the copyright macro inserts a copyright, we will modify the caption to display the copyright symbol (). To modify the button, leave the Customize dialog box open. Right-click over the toolbutton to display the toolbutton context menuor click Modify Selection in the Customize dialog boxand modify the Name context menu item to contain the literal text. Alternatively, you could choose the Change Button Image menu item and pick an appropriate icon (see Figure 4.4).

Figure 4.4. Pick an icon to associate with your toolbutton.



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