Inside Microsoft® Visual Studio® .NET 2003 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Inside Microsoft® Visual Studio® .NET 2003 [Electronic resources] - نسخه متنی

Brian Johnson

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید










Working with Macros


The macros you build will use the automation object model to access and automate the different parts of the IDE. In this section, we'll demonstrate how you can use macros to automate some simple tasks and we'll talk a bit about the automation object model as it applies to documents and windows in the IDE. We'll also discuss events and provide some simple examples to help you get going right away. Much of the material we'll cover here is discussed in detail in Part II of the book.


Manipulating Documents and Text


Some of the most useful tasks you can perform with macros involve working with text in documents. You might want to search for text, change a selection in some way, or just insert text into a document. The Document object in the DTE provides a good deal of functionality that makes it easy to manipulate text in code documents.

Macros are often run on the document with the current focus. To get the currently active document in the IDE, you use the DTE.ActiveDocument property, which returns a Document object. (Recall that a Visual Studio .NET document is an editor or a designer window that opens to the center of the IDE.) If the document is an editor, it has an associated TextDocument object.

The TextDocument object has three properties of interest for programmers who want to manipulate text inside the object. The StartPoint property returns a TextPoint object that points to the beginning of the document. The EndPoint property returns an object that points to the end of the document. And finally, the Selection property returns a TextSelection object, which offers a number of properties and methods you can use on selected text.

The TextPoint object provides location information for the editing functionality inside a document. You create a TextPoint in a document whenever you want to insert or manipulate text in the document or when you want to get some information about a particular document. TextPoint objects aren't dependent on text selection, and you can use multiple TextPoint objects in a single document.

Let's look at a couple of examples that use the objects we've mentioned. You should become familiar with this code because much of the macro automation code you'll write will depend on it.

First, let's get the ActiveDocument, create a couple of EditPoint objects, and add some text to the ActiveDocument using that information:


Sub CommentWholeDoc()
Dim td As TextDocument = ActiveDocument.Object
Dim sp As TextPoint
Dim ep As TextPoint
sp = td.StartPoint.CreateEditPoint()
ep = td.EndPoint.CreateEditPoint()
sp.Insert("/* ")
ep.Insert(" */")
End Sub

Running this sample on a Visual C# or a Visual C++ code document will comment out the entire document, unless the source already contains comments. The macro isn't very practical, but it does show you how to put those parts together. You can use IntelliSense to make your way through the objects created to experiment with some of the other functionality.

Let's take a look at a second, more useful, example that inserts text into a document based on a selection. The following example creates an HTML comment in a document. This functionality doesn't exist in Visual Studio .NET 2003, so you might find this simple macro useful enough to add to your own toolbox. Here we'll declare ts as a TextSelection object and assign it the current selection using DTE.ActiveDocument.Selection:


Sub HTMLComment()
Dim ts As TextSelection = DTE.ActiveDocument.Selection
ts.Insert("<!-- ", vsInsertFlags.vsInsertFlagsInsertAtStart)
ts.Insert(" -->", vsInsertFlags.vsInsertFlagsInsertAtEnd)
End Sub

This macro uses the TextSelection Insert method to insert text around the Selection object. The Insert method takes two arguments. The first argument is the string that you want to insert into the selection. The second argument is a vsInsertFlags constant that defines where the insertion is to take place. The first Insert call in the example uses vsInsertFlagsAtStart. The second uses vsInsertFlagsAtEnd. Table 4-3 lists these constants.

Table 4-3.
vsInsertFlags Constants

Constant


Description


vsInsertFlagsCollapseToStart


Collapses the insertion point from the end of the selection to the current TextPoint


vsInsertFlagsCollapseToEnd


Collapses the insertion point from beginning of the selection to the current TextPoint


vsInsertFlagsContainNewText


Replaces the current selection


vsInsertFlagsInsertAtStart


Inserts the text before the start point of the selection


vsInsertFlagsInsertAtEnd


Inserts text just after the end point of the selection

With a Selection, a TextPoint, and the methods available through the DTE, you should have a good basis for the types of operations you can perform with macros on source code.


Moving Windows


Windows in Visual Studio .NET are controlled through the Window object, which is part of the DTE.Windows collection. The Window object provides functionality based on the window type. Specifically, the CommandWindow, OutputWindow, TaskList, TextWindow, and ToolBox derive from the Window object.

Of the window objects, OutputWindow is among the most practical for macro writing. You can use it to display and hold messages in much the same way you would use printf or Console.Write in a console application, or in the same way that you use MsgBox or MessageBox.Show in a Windows-based application.

To use the OutputWindow object to display messages, you must create a new method that takes a string argument. You can then call the method with the argument in same way you use the MsgBox method to display a message. The following example is a method named MsgWin. It takes only a string as an argument. You can use this method in place of MsgBox when you want to quickly see a bit of text information.


Sub MsgWin(ByVal msg As String)
Dim win As Window = DTE.Windows.Item(Constants.vsWindowKindOutput)
Dim cwin As Window =
DTE.Windows.Item(Constants.vsWindowKindCommandWindow)
Dim ow As OutputWindow = win.Object
Dim owp As OutputWindowPane
Dim cwp As CommandWindow = cwin.Object
Dim i As Integer
Dim exists As Boolean = False
' Check to see if we're running in the Command Window. If so,
' we'll send our output there. If not, we'll send it to a Command
' window.
If (DTE.ActiveWindow Is cwin) Then
cwp.OutputString(msg + vbCrLf)
Else
' Determine if the output pane name exits. If it does, we need
' to send our message there, or we end up with multiple windows of
' the same name.
For i = 1 To ow.OutputWindowPanes.Count
If ow.OutputWindowPanes().Item(i).Name() = "MsgWin Output" Then
exists = True
Exit For
End If
Next
' If our output pane exits, we'll use that to output the string,
' otherwise, we'll add it to the list.
If exists Then
owp = ow.OutputWindowPanes().Item(i)
Else
owp = ow.OutputWindowPanes.Add("MsgWin Output")
End If
' Here we set the Output window to visible, activate the pane,
' and send the string to the pane.
win.Visible = True
owp.Activate()
owp.OutputString(msg + vbCrLf)
End If
End Sub

MsgWin uses a pretty cool feature that's found in the samples that ship with Visual Studio .NET. The method determines whether the calling method was invoked from the Command Window. If it was, the output is directed right back to the user in the Command Window. If it's called from a macro that was run from a menu, shortcut, or button, MsgWin sends the output to an Output window named MsgWin Output.

Tip

The Samples macros project that ships with Visual Studio .NET contains a lot of really good, functional macro code that you can use in the macros you write.

To use the MsgWin macro, you must call it from another method. For this example, we've created a method that lists all the currently open windows in the IDE:


Sub MsgWinTest()
Dim wins As Windows = DTE.Windows()
Dim i As Integer
For i = 1 To wins.Count
MsgWin(wins.Item(i).Caption.ToString())
Next
End Sub

Figure 4-6 shows what the Visual Studio .NET IDE looks like after it has been invoked from the MsgWinText macro in the IDE.


Figure 4-6. The MsgBox Output window in the IDE



You can do a lot of things with this basic MsgWin macro to improve it. It would be pretty trivial to overload the MsgWin method to allow for such actions as clearing the output pane or adding a heading to the list. For example, to create an overload for the MsgWin function that clears the output pane, you can make the method look something like this:


Sub MsgWin(ByVal msg As String, ByVal clr As Boolean)

' If clr is True then we'll clear the output pane.
If clr = True Then
owp.Clear()
End If
' Here we set the Output window to visible, activate the pane,
' and send the string to the pane.
win.Visible = True
owp.Activate()
owp.OutputString(msg + vbCrLf)
End If
End Sub

Of course, this overload won't do you much good if you call the macro the way we did in MsgBoxTest, but as you can see it's easy enough to do what you want with the macro.

Another way to add this kind of functionality to your macros is to create an assembly in the language of your choice and then reference that assembly from within your macro project. We did this with the CommandWindowPaneEx object.


Using Assemblies in Your Macros


A couple of things become apparent when you start to use macros a lot. The first is that you can use macros as a place to test the functionality of .NET assemblies. For example, if you want to test some bit of functionality in the framework, all you need to do is reference the appropriate assembly and then call the methods from within a macro. With a little practice, you'll find that the Macros IDE can work as a little laboratory that lets you try out functionality without having to mess around with rebuilding your projects.

The second thing you'll notice is that you have to write all this cool stuff in Visual Basic, and if that's not your preferred language, you might be spending a lot of time performing tasks you already know how to accomplish quickly in another language. As we mentioned earlier, there is a way to write macro functionality in languages other than Visual Basicby building your functionality into an assembly and then referencing that assembly from within your macro project.

We wrote a base set of utility functions for the book that you can take advantage of in your own macros and add-ins. In the Utilities folder of the companion content, you'll find the Utilities solution. This solution contains the OutputWindowPaneEx object. Build the solution and copy the InsideVSNET.Utilities.dll file from the bin\debug folder for the project into the C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\Public­Assemblies folder.

Once you've copied that file into your Public Assemblies folder, you can add a reference to the assembly from your macro project in the Macros IDE by right-clicking on the References folder in the Project Explorer window and choosing Add Reference, or by selecting the References folder and typing Project.AddReference into the Macros IDE Command Window. On the .NET tab of the Add Reference dialog box, you should see the new InsideVSNET.Utilities assembly. Select it in the list, click Select, and then click OK. You'll see the new assembly in the list of references if you expand the References folder.

After you add the reference to your project, it's helpful to add the appropriate Imports statement to your module. In this case, you'll add the following to the top of the module:


Imports InsideVSNET.Utilities

Once you add the reference, IntelliSense kicks in automatically as you create an OutputWindowPaneEx object and use it. The really cool thing about this object is that it lets you specify whether to send your output to the Output window in the Visual Studio .NET IDE or to the Macros IDE. In this example, we specified the Macros IDE, passing DTE.MacrosIDE when we created the object. We also changed the test a bit by enumerating the open windows in the Macros IDE rather than the Visual Studio .NET IDE, as we did earlier.


Sub OutputWindowPaneExTest()
Dim owp As New OutputWindowPaneEx(DTE.MacrosIDE)
Dim wins As Windows = DTE.MacrosIDE.Windows()
Dim i As Integer
owp.Activate()
For i = 1 To wins.Count
owp.WriteLine(wins.Item(i).Caption.ToString())
Next
End Sub

In addition to letting you perform the tasks that you're able to perform with the OutputWindowPane object, OutputWindowPaneEx lets you do a number of things with text that you want to send to an Output window. As we mentioned, you can specify the IDE to which you want to send your output. The Write method has three overloads, letting you specify an object, a string, or a formatting string/parameter array. Using these overloads, you can specify formatting options, much like using the System.Console.Write and System.Console.WriteLine methods in the .NET Framework.


Macro Events


One of the most powerful features of macros in the IDE is an event model that lets you fire macros based on events that take place in the IDE. You can use events to fire macros that create logs, reset tests, or manipulate different parts of the IDE in the ways we've already talked about in this chapter. In this short section, we'll show you how to create event handlers for different events in the IDE. Using this information and the detailed information about the different parts of the automation API discussed throughout the rest of the book, you should have a good idea how to take advantage of events in your own projects.

The easiest way to get to the event handlers for a macros project is through the Project Explorer window in the Macros IDE. Expand a project, and you'll see an EnvironmentEvents module listed. Open that file, and you'll see a block of code that's been generated automatically by the IDE. Here's the important part of the block. (The attributes have been removed to make this fit the page.)


Public WithEvents DTEEvents As EnvDTE.DTEEvents
Public WithEvents DocumentEvents As EnvDTE.DocumentEvents
Public WithEvents WindowEvents As EnvDTE.WindowEvents
Public WithEvents TaskListEvents As EnvDTE.TaskListEvents
Public WithEvents FindEvents As EnvDTE.FindEvents
Public WithEvents OutputWindowEvents As EnvDTE.OutputWindowEvents
Public WithEvents SelectionEvents As EnvDTE.SelectionEvents
Public WithEvents BuildEvents As EnvDTE.BuildEvents
Public WithEvents SolutionEvents As EnvDTE.SolutionEvents
Public WithEvents SolutionItemsEvents As EnvDTE.ProjectItemsEvents
Public WithEvents MiscFilesEvents As EnvDTE.ProjectItemsEvents
Public WithEvents DebuggerEvents As EnvDTE.DebuggerEvents

As you can see from this listing, there are a lot of event types you can take advantage of in the IDE. In fact, you can use all the DTE events, though they're not included by default. You can add these other events to this list to get to the events that you're interested in. To create a new event handler, you select the event type you want to handle from the Class Name list at the top of the code window. You can see how this looks in Figure 4-7.


Figure 4-7. Selecting the event type you want to handle from the Class Name list



After you select an event type, the Method Name list in the upper-right portion of the code pane will list the events you can handle, as shown in Figure 4-8.


Figure 4-8. Selecting the event you want to handle from the Method Name list



Select the event you want from the list, and your event handler will be generated automatically. From this generated event handler, you can call a method that you've created in the project, or you can add your event handling functionality directly to the event handler code. In this example, we'll call the MsgWin function that we worked through earlier to display a message that indicates that the build has completed.


Private Sub BuildEvents_OnBuildDone(ByVal Scope As EnvDTE.vsBuildScope, _
ByVal Action As EnvDTE.vsBuildAction) _
Handles BuildEvents.OnBuildDone
MsgWin("Build is done!")
End Sub

As you can imagine, these events open up all sorts of possibilities for automation and customization in the IDE. One thing you should keep in mind when working with events is that all the code in a single macro project shares the same event module. This means that if you want to create different event handlers for the same event, you'll need to create the other event handlers in other projects.


Event Security


As you can imagine, executing event code in a powerful macros facility such as the one in Visual Studio .NET has some potential security implications. The first time you load a macro project that contains event-handling code, you see a dialog box that looks like this:





You should be sure you know where your macros come from when you load macro projects. If you're not sure of the event-handling code in the project, click Disable Event Handling Code in the Warning dialog box and review the code in the module before you use it.


/ 118