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

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

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

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

Brian Johnson

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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










The Automation Object Model


One look at the automation object model chart and you'll agree that the Visual Studio .NET designers are serious about letting developers customize their programming environment. With over 140 different objects, the automation object model gives you unprecedented control over the IDE, from the solution level all the way down to a single parameter in a function.


Automation Objects


Just as its name suggests, the automation object model exposes its functionality through objectsa programming paradigm familiar to most programmers today. Each object in the model encapsulates some small part of Visual Studio .NET and offers programmatic access to it through a set of methods, properties, and events. The objects are arranged in a hierarchy, with the methods and properties of one object allowing access to its child objects below and to its parent object above.

The DTE object sits atop this automation object model hierarchy and serves as the entrance to its furthest recesses. Add-ins get their DTE reference from the application parameter of their OnConnection event; macros use the DTE reference in the predefined global variable named DTE. From this DTE reference, you can reach all the other objects in the automation object model. If you're wondering what objects you might want to reach, the following short list should get your imagination working:

Solution, Project, and ProjectItem objects give you programmatic control over all aspects of project management.

Document and TextDocument objects let you manipulate documents in the IDE.

TextSelection and TextPoint objects let you edit text as viewed in an editor window; EditPoint objects let you make changes directly to the text buffer.

FileCodeModel, CodeNamespace, CodeInterface, CodeVariable, CodeFunction, and similar objects let you manipulate code constructs at a level independent of the underlying programming language.

CommandWindow, TaskList, OutputWindow, ToolBox, and other similar objects give you control of specific tool windows within the IDE.

Command objects let you access environment commands.

Debugger, Process, Program, Thread, StackFrame, and other related objects give you control over the Visual Studio .NET debugger.


Of course, the objects in the previous list are only the beginning. The remainder of the book covers these objects and more in exhaustive detail, teaching you everything you need to know to turn your copy of Visual Studio .NET into the coolest IDE on your block.


Object Model Guidelines


Whenever possible, the automation object model design follows a few simple guidelines, shown in the following list. (Keep these rules in mind while you program, and you won't waste time agonizing over why AddIns(0) throws a COMException or why Activate won't display your @#&%*! output window.)

All objects use Automation types only.

All objects have a DTE property that returns the DTE object.

Collections have names that are the plural form of the objects they contain. For example, Documents is the collection of Document objects.

Collections are accessed through properties that have the same name as the collection.

Collections have an Item methodnot a propertyfor accessing their contained items.

The Item method for a collection takes an object when you're using a managed language; it takes a VARIANT when you're using an unmanaged language.

The Item method on a collection takes a numerical index or the name of a collection item.

Numerical indexes into a collection are 1-based.

Collections have a Count method that returns a System.Int32 when you're using a managed language or a long when you're using an unmanaged language.

Collection items have a Collection property that returns the owning collection; all other objects have a Parent property that returns its parent object.

You add an item to a collection by using the Add method on the collection; you remove an item from a collection by using the Remove or Delete method on the item. (Delete applies to user interface items, such as ToolBoxTab objects; Remove applies to nonuser interface items, such as AddIn objects.)

The DTE.Events object provides access to all event interfaces.

A general object can return one or more specific objects through its Object property, which takes the case-insensitive name of the specific object.

Creating a window doesn't automatically make that window visible; instead, you make a window visible by setting its Visible property to True.

Properties that return a complete path to a file on disk are called FullName.

The Activate method sets the focus on a visible user interface item; if the item isn't visible, Activate won't necessarily make its container visible.



Automation Events


If automation objects tell you what you can do within Visual Studio .NET, automation events tell you when you can do it. Each of the functional groups within the automation object model defines events that allow you to listen in on Visual Studio .NET's activities and take action based on what you hear. Together with automation objects, automation events allow you to achieve hands-free control over every important aspect of Visual Studio .NET.

Connecting to Automation Events


Connecting to automation events is pretty simple, although the syntax for doing so differs dramatically between the languages supported by Visual Studio .NET. We'll begin our events overview with C# because the C# syntax reveals everything we care to know about the mechanics of events.

The DTE.Events property is the first stop on the way to automation events. This property returns an EnvDTE.Events interface, which defines the read-only properties shown in Table 5-2. Each of the Events properties returns an event interface with the same name as the property; each event interface, in turn, defines a set of events to which you can subscribe.

Table 5-2.
EnvDTE.Events Properties

Property


Events


BuildEvents


OnBuildBegin

OnBuildDone

OnBuildProjConfigBegin

OnBuildProjConfigDone


CommandEvents
*


AfterExecute

BeforeExecute


DebuggerEvents


OnContextChanged

OnEnterBreakMode

OnEnterDesignMode

OnEnterRunMode

OnExceptionNotHandled

OnExceptionThrown


DocumentEvents
*


DocumentClosing

DocumentOpened

DocumentOpening

DocumentSaved


DTEEvents


ModeChanged

OnBeginShutdown

OnMacrosRuntimeReset

OnStartupComplete


FindEvents


FindDone


MiscFilesEvents (returns Project­ItemsEvents)


ItemAdded

ItemRemoved

ItemRenamed


OutputWindowEvents
*


PaneAdded

PaneClearing

PaneUpdated


SelectionEvents


OnChange


SolutionEvents


AfterClosing

BeforeClosing

Opened

ProjectAdded

ProjectRemoved

ProjectRenamed

QueryCloseSolution

Renamed


SolutionItemsEvents (returns
ProjectItemsEvents)


ItemAdded

ItemRemoved

ItemRenamed


TaskListEvents
*


TaskAdded

TaskModified

TaskNavigated

TaskRemoved


TextEditorEvents
*


LineChanged


WindowEvents
*


WindowActivated

WindowClosing

WindowCreated

WindowMoved

* C# won't allow you to reference these properties using property syntax because their get accessors take parameters. Use explicit calls to the get accessors instead.

Before you can subscribe to one of the events in Table 5-2, you need to define the function that will handle the event, and to do that you need the event's signature. The Visual Studio .NET documentation contains all the signature information for the automation events; once you have that information, you can define an event handler function whose prototype matches that of the corresponding event.

Next you need the delegate that's defined for the event. In the .NET Framework event model, a delegate is a class that wraps a callback function (such as an event handler) and provides type-safe access to it. The delegates for the automation events have names that follow this pattern: _disp<event interface>_<event name>EventHandler. To add your event handler to the event's list of subscribers, you create a new instance of the event's delegate, passing the event handler to the delegate's constructor, and then you assign the delegate to the event using the += syntax. If you've never done this before, it's easier than it sounds. Let's look at an example.

Suppose the software company you work for logs its daily builds and you would like to inject custom information into those logs, such as the name and department of the person performing the build. One way to do that would be to write an add-in that intercepts the BuildEvents.OnBuildBegin eventwhich fires just before a build takes placeand write the custom information into the log from the event handler. A quick peek at the documentation reveals the event's signature:


void OnBuildBegin(vsBuildScope scope, vsBuildAction action);

When this event fires, Visual Studio .NET passes along information about the impending build through the scope and action parameters: the scope parameter tells you the extent of the build that is about to begin (solution, batch, or project), and the action parameter tells you the kind of build that is about to begin (build, rebuild all, clean, or deploy). The event handler that you create must have the same signature as this event; you can name the handler anything you like, but the event's name is as good as any other:


public class Connect : Object, Extensibility.IDTExtensibility2
{

private void OnBuildBegin(vsBuildScope scope, vsBuildAction action)
{
// Log the builder's information

}
}

Now that the event handler is in place, it's time to wire it up to the event. From Table 5-2, you know that build events belong to the BuildEvents interface, so the delegate you need for the OnBeginBuild event has the name _dispBuildEvents_OnBeginBuildEventHandler. The following code wires up the event handler:


public class Connect : Object, Extensibility.IDTExtensibility2
{

public void OnConnection(object application,
Extensibility.ext_ConnectMode connectMode,
object addInInst,
ref System.Array custom)
{
applicationObject = (_DTE)application;
addInInstance = (AddIn)addInInst;
buildEvents = applicationObject.Events.BuildEvents;
buildEvents.OnBuildBegin +=
new _dispBuildEvents_OnBuildBeginEventHandler(
this.OnBuildBegin);

}

public void OnDisconnection(
Extensibility.ext_DisconnectMode disconnectMode,
ref System.Array custom)
{
buildEvents.OnBuildBegin -=
new _dispBuildEvents_OnBuildBeginEventHandler(
this.OnBuildBegin);

}

private void OnBuildBegin(vsBuildScope scope, vsBuildAction action)
{
// Log the builder's information

}

private _DTE applicationObject;
private AddIn addInInstance;
private EnvDTE.BuildEvents buildEvents;

}

The pattern is simple: OnConnection hooks up the OnBuildBegin event handler to the event when the add-in loads, and OnDisconnection unhooks the same event handler when the add-in unloads.

Important

If you don't unsubscribe from all events before the add-in unloads, you're in for a good old-fashioned memory leak. Under normal circumstances, the add-in becomes fair game for the garbage collector when Visual Studio .NET unloads it. But when the add-in doesn't unsubscribe from its events, it never gets garbage collected because the events still hold references to it. And like the ghosts in the movie The Sixth Sense who "don't even know they're dead," the add-in will behave as though nothing is wrong and will continue to process events long after it has passed on from the realm of Visual Studio .NET.

Macro Event Handlers


Connecting to events from add-ins is easy, and connecting to events from macros is easier still. Every new macro project begins life with a module named EnvironmentEvents. This module defines an event variable for each of the event interfaces listed in Table 5-2, and these event variables are initialized automatically by the Macros IDE. When the EnvironmentEvents module is open in an editor window, you can add a new event handler by selecting the event variable from the Class Name drop-down list and then selecting the event you want to connect to from the Method Name drop-down list (as shown in Figure 5-7). The Macros IDE generates an empty event handlerall you have to do is add the code.


Figure 5-7. Adding a macro event handler



To unsubscribe from an event, you just delete its event handler from the EnvironmentEvents module. Be careful not to modify or delete the event variables located within the region marked "Automatically generated code, do not modify," because doing so might prevent the macro project from building.

Defining Your Own Event Variables


Although the EnvironmentEvents module is a convenient place to create and manage your macro event handlers, you might have reason to define some of your event handlers in other modules (if only to keep EnvironmentEvents from growing too large). To create an event handler in an arbitrary module, you first need to declare an event variable in that module. The following declaration creates the MyWindowEvents variable for handling window events:


<System.ContextStaticAttribute()> Public WithEvents _
MyWindowEvents As EnvDTE.WindowEvents

If you open the module containing this declaration in an editor window, you can generate an empty event handler just as you would in the EnvironmentEvents module by selecting the MyWindowEvents variable from the Class Name drop-down list and then selecting the event you want to handle from the Method Name drop-down list. Unlike the EnvironmentEvents module, however, the new event handler won't receive events automatically. That's because the Macros IDE initializes the event variables in EnvironmentEvents for youwhen you create your own event variables, you have to initialize them yourself.

To initialize an event variable, you assign the appropriate DTE.Events property to it:


MyWindowEvents = DTE.Events.WindowEvents

You can put the assignment in a regular macro and run the macro by hand, which works well enough for some situations, but your handler will miss all events up to the time you run the macro. If the event handler has to intercept events right from startup, you need to automate the assignment somehow. The DTEEvents interface defines the two events you'll need for this purpose: OnStartupComplete, which fires when Visual Studio .NET finishes loading, and OnMacrosRuntimeReset, which fires when a macro project is reloaded into memory. By creating handlers in EnvironmentEvents for both of these events and performing the assignment within each handler, you achieve automatic initialization of your event variable:


Public Module EnvironmentEvents

Public Sub DTEEvents_OnMacrosRuntimeReset() _
Handles DTEEvents.OnMacrosRuntimeReset
MyModule.MyWindowEvents = DTE.Events.WindowEvents
End Sub
Public Sub DTEEvents_OnStartupComplete() _
Handles DTEEvents.OnStartupComplete
MyModule.MyWindowEvents = DTE.Events.WindowEvents
End Sub

End Module

Filtered Events


In general, you should use events sparingly because too many event handlers will degrade the performance of Visual Studio .NET. By creating a filtered event, however, you can have your event and eat it, too. Essentially, a filtered event lets you handle the events of some objects and ignore the events of others. For example, instead of receiving WindowClosing events for every window, you can choose to receive WindowClosing events for the Task List window only. The DTE.Events properties listed in Table 5-3 allow you to create filtered events.

Table 5-3. Properties for Filtered Events

DTE.Events Property


Filtered By


CommandEvents


Command name


DocumentEvents


Document object


OutputWindowEvents


Output window pane name


TaskListEvents


Task List category


TextEditorEvents


TextDocument object


WindowEvents


Window object

Suppose you want to handle events from a specific window in the IDE. From Table 5-3, you see that the WindowEvents property accepts a Window object as its filter. You retrieve the Window object you want from the EnvDTE.Windows collection by passing its Item method the associated EnvDTE.Constants.vsWindowKindxxx constant. Pass this Window object to the WindowEvents property and you'll receive an event interface for that particular window. The following code shows how to narrow down window events to the Macro Explorer window only:


' Event variable declaration
<System.ContextStaticAttribute()> Public WithEvents _
MacroExplorerWindowEvents As EnvDTE.WindowEvents
Sub InitializeMacroExplorerFilter()
Dim macroExplorerWindow As EnvDTE.Window
macroExplorerWindow = _
DTE.Windows.Item(EnvDTE.Constants.vsWindowKindMacroExplorer)
MacroExplorerWindowEvents = _
DTE.Events.WindowEvents(macroExplorerWindow)
End Sub
Sub MacroExplorerWindowEvents_WindowActivated( _
ByVal GotFocus As EnvDTE.Window, _
ByVal LostFocus As EnvDTE.Window) _
Handles MacroExplorerWindowEvents.WindowActivated
' Description: Tracks each time the Macro Explorer window
' is activated

End Sub


Lab: Unfiltered and Filtered Events


Want to see the difference between unfiltered and filtered events? Then open the Output window and try the following experiment, which uses macros in the FilteredEvents module:

Run the InitializeUnfilteredEvents macro, which initializes DocumentEvents and TextEditorEvents event variables without filters. FilteredEvents defines event handlers that process DocumentSaved and LineChanged events for these two event variables.

Create two new text files.

Type a line of text, and press Enter in both of the files you created. The Output window displays a "LineChanged fired" message for each new line.

Save both of the files. As you save each file, the Output window displays a "DocumentSaved fired" message.


Because the DocumentEvents and TextEditorEvents event variables were initialized without filters, the corresponding event handlers receive events for all documents. Now, leave open the two files you created and try a similar experiment using filtered events:

Run the InitializeFilteredEvents macro. This macro creates a file named New File and initializes the DocumentSaved and LineChanged event variables so that they target events for this file only.

In the New File file, enter a line of text and press Enter. The Output window displays a "LineChanged fired" message.

Switch to each of the text files you created, and repeat the previous step. Notice that the Output window doesn't display "LineChanged fired" for either of these files.

Save both of the text files. Again, notice that the Output window doesn't display "DocumentSaved fired" messages.

Save New File. This time, you'll see "DocumentSaved fired" in the Output window.


Late-Bound Events


Programming languages integrated into Visual Studio .NET can offer late-bound events that monitor the status of their own project typesyou can think of them as filtered events for projects. These project-specific events are shown in Table 5-4.

Table 5-4. Late-Bound Events

Language


Event Interface


Visual C#


CSharpBuildManagerEvents

CSharpProjectsEvents

CSharpProjectItemsEvents

CSharpReferencesEvents


Visual Basic


VBBuildManagerEvents

VBImportsEvents

VBProjectsEvents

VBProjectItemsEvents

VBReferencesEvents


Visual C++


CodeModelEvents

VCProjectEngineEventsObject

The event interface names are the same as the interfaces they derive from, but with a language-specific prefix: VBProjectsEvents, CSharpProjectItemsEvents, and so on. (The Visual C++ event interfaces are the exceptions because they derive from a different code base.) You retrieve the event interface you want by passing its name to the DTE.Events.GetObject method. For example, the following code initializes an event variable with the interface that handles all the C# project events:


<System.ContextStaticAttribute()> Public WithEvents _
CSharpProjectsEvents As EnvDTE.ProjectsEvents
Sub InitializeCSharpProjectsEvents()
CSharpProjectsEvents = DTE.Events.GetObject("CSharpProjectsEvents")
End Sub

After the previous code executes, any event handlers you created for the CSharpProjectsEvents variable will receive C# project events only.


/ 118