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

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

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

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

Brian Johnson

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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










The IDTExtensibility2 Interface


As you now know, an implementation of IDTExtensibility2 lies at the core of every add-in. Visual Studio .NET calls the methods on this interface whenever it needs to apprise an add-in of important events, such as when another add-in is loaded or unloaded, or when Visual Studio .NET is about to shut down. The communication isn't just one-way, either: through the IDTExtensibility2 interface, the add-in has access to and control over the entire Visual Studio .NET automation object model.


The EnvDTE Namespace


Before examining the individual IDTExtensibility2 methods, we need to take a quick look at the real objective of add-inscontrolling the objects in the EnvDTE namespace. The name EnvDTE stands for Environment Development Tools Extensibility, which pretty much describes its purpose: it defines the Visual Studio .NET automation object model. The Visual Studio .NET documentation includes a chart of the automation object model that displays a hierarchy of over 140 objects defined by the EnvDTE namespace. The add-ins in this book will make use of most of those objects, but a few of the objects are of special interest to add-ins:


DTE

The root object of the automation object model


DTE.AddIn

An object that represents an add-in


DTE.AddIns

A collection of AddIn objects that includes all add-ins registered with the Visual Studio .NET IDE


DTE.Solution.AddIns

A collection of AddIn objects associated with a solution


The next several examples will focus on the DTE, DTE.AddIn, and DTE.AddIns objects, which collectively give you control over your own add-in and others. We'll cover the DTE.Solution.AddIns object in Chapter 8.

Note

The main purpose of an add-in class is to provide an implementation of IDTExtensibility2, but that doesn't have to be its only purpose. An add-in class is a class, after all, and it can define any number of non-IDTExtensibility2-related methods, properties, and events. The automation object model provides access to your add-in class through the AddIn.Object property, which returns the add-in's IDispatch interface. The following macro code shows how you would call a public method named DisplayMessage on the MyAddIn.Connect add-in class:


Dim dispObj As Object = DTE.AddIns.Item("MyAddIn.Connect").Object
dispObj.DisplayMessage("IDispatch a message to you.")



OnConnection


By far the most important of the IDTExtensibility2 methods, OnConnection provides an add-in with the main object reference it needs to communicate directly with the IDE. The OnConnection method has the following prototype:


public void OnConnection(object application,
ext_ConnectMode connectMode,
object addInInst,
ref Array custom);

The application parameter holds a reference to an instance of EnvDTE.DTE, which is the root object of the automation object model. Technically, application holds a reference to an instance of EnvDTE.DTEClass, which implements the EnvDTE.DTE interface, which in turn derives from the EnvDTE._DTE interface. This last interface contains the types you want. To get at the _DTE interface types, you can cast application to DTE or _DTE, according to your taste. (See the upcoming sidebar "Underscoring the Obvious" to find out where all those EnvDTE underscores come from.) Almost every add-in that does something useful has need of the DTE object, so the first statements in OnConnection typically cache the DTE object in a global variable.


Underscoring the Obvious


Staring at the underscores that litter the EnvDTE namespace, you might begin to wonder what the Visual Studio .NET programmers were smoking when they designed it. Most of the type names in the EnvDTE namespace bear little resemblance to type names found elsewhere in the .NET Framework; the name EnvDTE itself violates the .NET Framework's Pascal-casing rule. As it turns out, there's a legitimate reason for EnvDTE's strange names (which implies that the programmers' smoking material probably was legitimate also): that reason is COM.

The original extensibility object model, Design Time Extensibility (DTE), began life as a COM component in previous versions of Visual Studio. Rather than rewrite the component as managed code, the Visual Studio .NET team chose to offer the component's functionality via COM interoperability. The EnvDTE assembly that shows up in the Add Reference dialog box was generated mechanically by running the extensibility component's type library (dte.olb) through the Type Library Importer utility (TlbImp). As it churns through a type library, TlbImp preserves the type library names exactly as it finds themin the case of EnvDTE, the result is a namespace that carries its COM heritage in every underscore.

Of course, the Visual Studio team made the right decision not to rewrite the extensibility component for the earliest versions of Visual Studio .NET, so for now we'll just have to live with the funny names and hope our pinkies don't give out from typing Shift+<Underscore> all day.

The connectMode parameter tells an add-in the circumstance under which it was loaded. This parameter takes on one of the Extensibility.ext_ConnectMode enumeration values shown in Table 6-1.

Table 6-1. The Extensibility.ext_ConnectMode Enumeration

Constant


Value (Int32)


Description


ext_cm_AfterStartup


0x00000000


Loaded after Visual Studio .NET started.


ext_cm_Startup


0x00000001


Loaded when Visual Studio .NET started.


ext_cm_External


0x00000002


Loaded by an external client. (No longer used by Visual Studio .NET.)


ext_cm_CommandLine


0x00000003


Loaded from the command line.


ext_cm_Solution


0x00000004


Loaded with a solution.


ext_cm_UISetup


0x00000005


Loaded for user interface setup.

An add-in can check the connectMode value and alter its behavior accordingly. For example, when an add-in encounters ext_cm_UISetup, it knows that this is the first time it has run, so it can add its custom commands to the IDE menus and toolbars. (The Add-in Wizard generates code that handles the ext_cm_UISetup case in this manner.)

The addInInst parameter passes an add-in a reference to its own AddIn instance, which it can store for later use. (The AddIn instance proves invaluable for discovering the add-in's parent collection.) Finally, each of the IDTExtensibility2 methods includes a custom parameter, which allows add-in hosts to pass in an array of host-specific data. Visual Studio .NET always passes an empty array in custom.



OnStartupComplete


The OnStartupComplete event fires only in add-ins that load when Visual Studio .NET starts. The OnStartupComplete prototype looks like this:


public void OnStartupComplete(ref Array custom);

An add-in that loads at startup can't always rely on OnConnection for its initializationif the add-in arrives too early, it will fail when it tries to access a Visual Studio .NET component that hasn't yet loaded. In such cases, the add-in can use OnStartupComplete to guarantee that Visual Studio .NET is up and running first.



OnAddInsUpdate


The OnAddInsUpdate event fires when an add-in joins or leaves the Visual Studio .NET environment. An add-in can use this event to enforce dependencies on other add-ins. Here's the OnAddInsUpdate prototype:


public void OnAddInsUpdate(ref Array custom);

The lack of useful parameters reveals OnAddInsUpdate's passive-aggressive natureit interrupts your add-in to tell it that the state of some add-in has changed, but it withholds information about which add-in triggered the event and why. If you need to know the add-in responsible for the event, you have to discover its identity on your own. Fortunately, you have the DTE.AddIns collection to aid you in your investigation. This collection holds a list of AddIn objects (one for each registered add-in), and each AddIn object has a Connected property that exposes its connection status. You retrieve a specific add-in from the AddIns collection by passing the AddIns.Item method a ProgID or a 1-based index; if the requested index doesn't exist in the collection, the Item method throws an "invalid index" COMException; otherwise, it returns an AddIn reference. Here's one way to check InsideVSNET.AddIns.LifeCycle's connection status:


public void OnAddInsUpdate(ref Array custom)
{
try
{
AddIn addIn =
this.dte.AddIns.Item("InsideVSNET.AddIns.LifeCycle");
if (addIn.Connected == true)
{
// InsideVSNET.AddIns.LifeCycle is connected
}
else
{
// InsideVSNET.AddIns.LifeCycle isn't connected
}
}
catch (COMException)
{
// InsideVSNET.AddIns.LifeCycle isn't a
// registered add-in
}
}

Of course, whether InsideVSNET.AddIns.LifeCycle caused the event remains a mystery. The LoadUnload add-in, shown in Listing 6-3, does what the previous sample cannot: it deduces which add-in triggers the OnAddInsUpdate event.

Listing 6-3 The LoadUnload source code


LoadUnload.cs
namespace InsideVSNET
{
namespace AddIns
{
using EnvDTE;
using Extensibility;
using InsideVSNET.Utilities;
using Microsoft.Office.Core;
using System;
using System.Collections;
using System.Runtime.InteropServices;
[GuidAttribute("B2FCDEBF-1536-4EA2-9F1A-81878A9C028D"),
ProgId("LoadUnload.Connect")]
public class Connect : Object, IDTExtensibility2, IDTCommandTarget
{
private DTE dte;
private AddIn addInInstance;
private SortedList addInsList = new SortedList();
private AddIns addInsCollection;
private OutputWindowPaneEx output;
private string title = "LoadUnload";
public Connect()
{
}
public void OnConnection(object application,
ext_ConnectMode connectMode,
object addInInst,
ref Array custom)
{
this.dte = (DTE)application;
this.addInInstance = (AddIn)addInInst;
this.addInsCollection = this.dte.AddIns;
foreach (AddIn addIn in this.addInsCollection)
{
this.addInsList[addIn.ProgID] = addIn.Connected;
}
this.output = new OutputWindowPaneEx(this.dte, this.title);

}
public void OnDisconnection(ext_DisconnectMode disconnectMode,
ref Array custom)
{
}
public void OnAddInsUpdate(ref Array custom)
{
this.addInsCollection.Update();
foreach (AddIn addIn in this.addInsCollection)
{
if (this.addInsList.Contains(addIn.ProgID))
{
if (addIn.Connected !=
(bool)this.addInsList[addIn.ProgID])
{
string action = addIn.Connected ?
"loaded" : "unloaded";
this.output.WriteLine(addIn.ProgID +
" was " + action, this.title);
}
}
else
{
string action = addIn.Connected ?
" and loaded" : String.Empty;
this.output.WriteLine(addIn.ProgID +
" was added" + action, this.title);
}
this.addInsList[addIn.ProgID] = addIn.Connected;
}
}
public void OnStartupComplete(ref Array custom)
{
}
public void OnBeginShutdown(ref Array custom)
{
}

}
}
}

LoadUnload maintains a running list of add-ins and their connection statuses in its addInsList variable, which is declared as type SortedList. When OnAddInsUpdate fires, LoadUnload compares the connection statuses of the add-ins in its internal list with the connection statuses of the add-ins in the DTE.AddIns collectionif it finds a discrepancy, it knows which add-in to blame for the event. Here's the first part of the main loop from Listing 6-3:


this.addInsCollection.Update();
foreach (AddIn addIn in this.addInsCollection)
{
if (this.addInsList.Contains(addIn.ProgID))
{
if (addIn.Connected !=
(bool)this.addInsList[addIn.ProgID])
{
string action = addIn.Connected ?
"loaded" : "unloaded";
this.output.WriteLine(addIn.ProgID +
" was " + action, this.title);
}
}


The addInsCollection variable holds a reference to the DTE.AddIns collection, and the call to Update synchs up the collection with the registry so that any newly created add-ins are included. (The Add-in Manager performs the equivalent of Update each time it runs.) After the call to Update, the main loop iterates through the current add-ins in addInsCollection and checks whether each add-in already exists in its internal list. If so, the Connected property of the add-in is compared with the corresponding value stored in the internal list; if they differ, the Connected property determines whether the add-in was just loaded (true) or unloaded (false).

If the current add-in doesn't exist in addInsList, the add-in was registered sometime between the previous OnAddInsUpdate event and this OnAddInsUpdate event. Here's the second part of the main loop, which handles new add-ins:



else
{
string action = addIn.Connected ?
" and loaded" : String.Empty;
this.output.WriteLine(addIn.ProgID +
" was added" + action, this.title);
}
this.addInsList[addIn.ProgID] = addIn.Connected;
}

The last statement either writes the current Connected value to an existing entry or creates a fresh entry for a newly registered add-in.

LoadUnload isn't foolprooffor example, add-ins loaded by commands arrive and leave unannouncedbut it works well enough for demonstration purposes.



OnBeginShutdown


Here's the prototype for OnBeginShutdown:


public void OnBeginShutdown(ref Array custom);

This event fires only when the IDE shuts down while an add-in is running. Although an IDE shutdown might get canceled along the way, OnBeginShutdown doesn't provide a cancellation mechanism, so an add-in should assume that shutdown is inevitable and perform its cleanup routines accordingly. An add-in that manipulates IDE state might use this event to restore the original IDE settings.



OnDisconnection


This event is similar to OnBeginShutdown in that it signals the end of an add-in's life; it differs from OnBeginShutdown in that the IDE isn't necessarily about to shut down. OnDisconnection also provides more information to an add-in than OnBeginShutdown does. OnDisconnection's prototype looks like this:


public void OnDisconnection(ext_DisconnectMode removeMode,
ref Array custom);

The removeMode parameter passes in an IDTExtensibility2.ext_Disconnect­
Mode enumeration value that tells an add-in why it was unloaded. Table 6-2 lists the ext_DisconnectMode values.

Table 6-2. The Extensibility.ext_DisconnectMode Enumeration

Constant


Value (Int32)


Description


ext_dm_HostShutdown


0x00000000


Unloaded when Visual Studio .NET shut down


ext_dm_UserClosed


0x00000001


Unloaded while Visual Studio was running


ext_dm_UISetupComplete


0x00000002


Unloaded after user interface setup


ext_dm_SolutionClosed


0x00000003


Unloaded when solution closed

The ext_DisconnectMode enumeration serves a purpose similar to ext_ConnectMode: it allows an add-in to alter its behavior to suit its current circumstances. For example, an add-in that receives ext_dm_UISetupComplete probably would bypass its cleanup routines because it was loaded for initialization purposes only.


/ 118