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

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

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

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

Brian Johnson

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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










The Add-in Wizard


You learned in Chapter 4 that macros provide a convenient way to automate tasks within Visual Studio .NET, and we encourage you to write macros as a first resort when customizing the IDE. But for some purposes, such as writing commercial software, you might find that macros are a poor choice in terms of performance and protection of intellectual property. In such cases, the appropriate vehicle is an add-in, which is a compiled DLL (providing increased protection) that runs within the IDE (providing increased performance). And the fastest way to get started as an add-in programmer is through the Add-in Wizard, which gathers your requirements in six easy steps and creates an add-in project tailored to your needs.


Running the Add-in Wizard


When you choose File | New | Project, Visual Studio .NET offers its selection of project types in the New Project dialog box. By expanding the Other Projects node and selecting Extensibility Projects, you'll find the Visual Studio .NET Add-in template shown in Figure 5-1; double-clicking its icon launches the Add-in Wizard.


Figure 5-1. The Visual Studio .NET Add-in template



The six pages of the Add-in Wizard collect your choices about the final form of your add-in. The wizard gives you control over the following areas:

Programming language

The Add-in Wizard generates the add-in source code in one of three programming languagesC#, Visual Basic .NET, or Visual C++ (using the Active Template Library [ATL]). You're not restricted to these languages when you write add-ins by hand, however; any language that supports the creation of COM objects will suffice.

Application host

Add-ins can run in the Visual Studio .NET IDE, the Macros IDE, or both. With few exceptions, the rules that apply to an add-in running in the Visual Studio .NET IDE also apply to an add-in running in the Macros IDE. (We'll point out differences between the two hosts when appropriate.)

Name and description

These settings let you associate a meaningful name and description with your add-in. The wizard stores these values in the registry so that any interested client can find them.

Menu command

The Add-in Wizard can generate code that creates a new menu item for your add-in, giving users a convenient way to load your add-in and execute a command.

Command-line build support

You can mark your add-in as being safe for use with unattended builds. Such an add-in promises that it won't display user interface elements that require user intervention (such as modal dialog boxes).

Load at startup

Add-ins can request that they be loaded automatically when Visual Studio .NET starts up.

Access privileges

You can make an add-in available to all users on a machine or just the user who installs the add-in.

About box information

You can provide support information for your add-in that Visual Studio .NET will display in its About dialog box.


When the Add-in Wizard finishes, it generates two projects: an add-in project that builds the add-in DLL and a setup project that builds a Windows Installer (MSI) file that you can use to distribute your add-in.


The Add-in Project


Add-ins are DLLs, so the Add-in Wizard creates a Class Library project for your add-in. This project contains a source file named Connect, which defines the add-in class, also named Connect. The Connect class implements the IDTExtensibility2 interface, which serves as the main conduit for add-in/IDE communication. (Connect also implements IDTCommandTarget if you select the user interface option in the Add-in Wizard.) Table 5-1 lists the five methods of the IDTExtensibility2 interface.

Table 5-1.
IDTExtensibility2 Interface

Method


Description


OnConnection


Called when the add-in is loaded.


OnStartupComplete


Called when Visual Studio .NET finishes loading.


OnAddInsUpdate


Called whenever an add-in is loaded or unloaded from Visual Studio .NET.


OnBeginShutdown


Called when Visual Studio .NET is closed.


OnDisconnection


Called when the add-in is unloaded.

The Connect.cs file in Listing 5-1 shows the code (minus some comments) that the Add-in Wizard generates for a typical C# add-in with a menu command. We'll walk through the source code, pointing out any interesting features along the way.

Listing 5-1 The add-in source code generated by the Add-in Wizard


Connect.cs
namespace MyAddin1
{
using System;
using Microsoft.Office.Core;
using Extensibility;
using System.Runtime.InteropServices;
using EnvDTE;
/// <summary>
/// The object for implementing an add-in.
/// </summary>
/// <seealso class='IDTExtensibility2' />
[GuidAttribute("BA857E49-7873-45D2-8335-FCCD4123739E"),
ProgId("MyAddin1.Connect")]
public class Connect : Object, Extensibility.IDTExtensibility2,
IDTCommandTarget
{
/// <summary>
/// Implements the constructor for the add-in object.
/// Place your initialization code within this method.
/// </summary>
public Connect()
{
}
/// <summary>
/// Implements the OnConnection method of the
/// IDTExtensibility2 interface.
/// Receives notification that the add-in is being loaded.
/// </summary>
/// <param term='application'>
/// Root object of the host application.
/// </param>
/// <param term='connectMode'>
/// Describes how the add-in is being loaded.
/// </param>
/// <param term='addInInst'>
/// Object representing this add-in.
/// </param>
public void OnConnection(object application,
Extensibility.ext_ConnectMode connectMode,
object addInInst, ref System.Array custom)
{
applicationObject = (_DTE)application;
addInInstance = (AddIn)addInInst;
if (connectMode ==
Extensibility.ext_ConnectMode.ext_cm_UISetup)
{
object []contextGUIDS = new object[] { };
Commands commands = applicationObject.Commands;
_CommandBars commandBars =
applicationObject.CommandBars;
try
{
Command command = commands.AddNamedCommand(
addInInstance, "MyAddin1", "MyAddin1",
"Executes the command for MyAddin1", true, 59,
ref contextGUIDS,
(int)vsCommandStatus.vsCommandStatusSupported +
(int)vsCommandStatus.vsCommandStatusEnabled);
CommandBar commandBar =
(CommandBar)commandBars["Tools"];
CommandBarControl commandBarControl =
command.AddControl(commandBar, 1);
}
catch(System.Exception /*e*/)
{
}
}
}
/// <summary>
/// Implements the OnDisconnection method of the
/// IDTExtensibility2 interface.
/// Receives notification that the add-in is being unloaded.
/// </summary>
/// <param term='disconnectMode'>
/// Describes how the add-in is being unloaded.
/// </param>
/// <param term='custom'>
/// Array of parameters that are host-application specific.
/// </param>
public void OnDisconnection(
Extensibility.ext_DisconnectMode disconnectMode,
ref System.Array custom)
{
}
/// <summary>
/// Implements the OnAddInsUpdate method of the
/// IDTExtensibility2 interface.
/// Receives notification that the collection of add-ins
/// has changed.
/// </summary>
/// <param term='custom'>
/// Array of parameters that are host-application specific.
/// </param>
public void OnAddInsUpdate(ref System.Array custom)
{
}
/// <summary>
/// Implements the OnStartupComplete method of the
/// IDTExtensibility2 interface.
/// Receives notification that the host application has
/// completed loading.
/// </summary>
/// <param term='custom'>
/// Array of parameters that are host-application specific.
/// </param>
public void OnStartupComplete(ref System.Array custom)
{
}
/// <summary>
/// Implements the OnBeginShutdown method of the
/// IDTExtensibility2 interface.
/// Receives notification that the host application is
/// being unloaded.
/// </summary>
/// <param term='custom'>
/// Array of parameters that are host-application specific.
/// </param>
public void OnBeginShutdown(ref System.Array custom)
{
}
/// <summary>
/// Implements the QueryStatus method of the
/// IDTCommandTarget interface.
/// This is called when the command's availability is updated.
/// </summary>
/// <param term='commandName'>
/// The name of the command to determine state for.
/// </param>
/// <param term='neededText'>
/// Text that is needed for the command.
/// </param>
/// <param term='status'>
/// The state of the command in the user interface.
/// </param>
/// <param term='commandText'>
/// Text requested by the neededText parameter.
/// </param>
public void QueryStatus(string commandName,
EnvDTE.vsCommandStatusTextWanted neededText,
ref EnvDTE.vsCommandStatus status, ref object commandText)
{
if (neededText ==
EnvDTE.vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
{
if (commandName == "MyAddin1.Connect.MyAddin1")
{
status =
(vsCommandStatus)vsCommandStatus.vsCommandStatusSupported |
vsCommandStatus.vsCommandStatusEnabled;
}
}
}
/// <summary>
/// Implements the Exec method of the IDTCommandTarget interface.
/// This is called when the command is invoked.
/// </summary>
/// <param term='commandName'>
/// The name of the command to execute.
/// </param>
/// <param term='executeOption'>
/// Describes how the command should be run.
/// </param>
/// <param term='varIn'>
/// Parameters passed from the caller to the command handler.
/// </param>
/// <param term='varOut'>
/// Parameters passed from the command handler to the caller.
/// </param>
/// <param term='handled'>
/// Informs the caller whether the command was handled.
/// </param>
public void Exec(string commandName,
EnvDTE.vsCommandExecOption executeOption, ref object varIn,
ref object varOut, ref bool handled)
{
handled = false;
if (executeOption ==
EnvDTE.vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if (commandName == "MyAddin1.Connect.MyAddin1")
{
handled = true;
return;
}
}
}
private _DTE applicationObject;
private AddIn addInInstance;
}
}

At the top of the listing, you'll see that the Add-in Wizard generates a set of using statements for the programmer's convenience. (A quick look through Connect.cs reveals that the Add-in Wizard eschews the using statements in favor of fully-qualified typesa practice that proves invaluable when you're trying to figure out where all the weird add-in types come from.) The two most important namespaces in the using statements are EnvDTE and Extensibility; the former defines the types used by IDTExtensibility2, and the latter defines the types in the automation object model. (A close third in the namespace contest is Microsoft.Office.Core, which defines types for manipulating command bars in the IDE.)

The first method in the listing, OnConnection, wins the prize for "most important add-in method." Visual Studio .NET calls this method when it loads the add-in, and it passes the add-in a reference to the root object of the automation object model through the application parameter. The code generated by the Add-in Wizard casts the application parameter to the EnvDTE._DTE type and stores the result in a private variable named applicationObject. All further interaction between the add-in and the automation object model takes place through the applicationObject variable.

Visual Studio .NET also passes the add-in a reference to its corresponding AddIn object through the addInInst parameter; the add-in stores this reference in a private variable named addInInstance.

The rest of the code in OnConnection creates an add-in menu command on the Tools menu. (This code is absent if you forgo the user interface option in the Add-in Wizard.) The menu-creation code executes conditionally, depending on the following if statement:


if (connectMode == Extensibility.ext_ConnectMode.ext_cm_UISetup)

The connectMode parameter holds a value that describes how the add-in was loaded. For add-ins that create a menu command, Visual Studio .NET passes in the Extensibility.ext_ConnectMode. ext_cm_UISetup value the first time the add-in loads after being installed, which signals to the add-in that now is as good a time as any to add its commands to the IDE.

The Add-in Wizard doesn't generate any code in the bodies of the other four IDTExtensibility2 methods: OnStartupComplete, OnAddinsUpdate, OnBeginShutdown, and OnDisconnection. The two IDTCommandTarget methods, QueryStatus and Exec, have some boilerplate code that helps manage the add-in's menu command and menu command clicks, respectively. To handle menu command clicks, you add code to the Exec method in the second if statement, which begins with


if (commandName == "MyAddin1.Connect.MyAddin1")

There isn't much code in Connect.cs, even if you've selected every option in the Add-in Wizard, but the code that's there creates a fully-functional add-in that you can build on.


Installing and Loading the Add-in


The easiest (and best) way of installing your new add-in is to build the add-in setup project and install the MSI file that it creates. The MSI file is a completely self-contained package that can be deployed on any Windows machine, with one caveat: the target machine must have the .NET Framework installed. (If you need to, you can distribute the .NET Framework along with your add-in. Chapter 13 explains how.) This single-package distributable makes installing the add-in as simple as double-clicking the MSI file's icon in Windows Explorer. If you like, you also can install the add-in from within Visual Studio .NET. After you build the setup project, choose the Install command from the project's shortcut menu, as shown in Figure 5-2. After you launch the setup, an installer wizard steps you through the setup process.


Figure 5-2. Installing the add-in from within Visual Studio .NET



The way you load your add-in into Visual Studio .NET can vary, depending in part on the options you selected in the Add-in Wizard. If you chose to have your add-in load on startup, Visual Studio .NET will load the add-in automatically each time it runs. If you chose to have a user interface item for your add-in, the next time Visual Studio .NET runs, you'll be able to load the add-in by choosing its command from the Tools menu, as shown in Figure 5-3.


Figure 5-3. A default add-in menu command



If you didn't choose either of these options, you can load the add-in by choosing Tools | Add-in Manager, which launches the Add-in Manager (shown in Figure 5-4). The Add-in Manager gives you control over all the registered add-ins, allowing you to load them, unload them, and mark them to load on startup and during command-line builds.


Figure 5-4. The Add-in Manager dialog box




Debugging the Add-in


An add-in is just a DLL, so debugging an add-in project is no different from debugging any other Class Library project. Because a DLL can't run on its own, it needs a host application; for an add-in, that host is Visual Studio .NET (devenv.exe) or the Macros IDE (vsaenv.exe). The Add-in Wizard sets the debugging properties of the add-in project so that Visual Studio .NET is the host. You can examine and modify the project's debugging properties by right-clicking the add-in project in Solution Explorer, choosing Properties from the shortcut menu, and selecting Configuration Properties | Debugging in the Property Pages dialog box (shown in Figure 5-5). For most purposes, however, the default settings work just fine.


Figure 5-5. The add-in project's debugging properties



In a typical debugging session, you open the add-in project in Visual Studio .NET, set breakpoints in the add-in source code, and then start the debugger by choosing Start from the Debug menu (or pressing F5). The debugger, in turn, launches a second instance of Visual Studio .NET and attaches itself to this new process. You load the add-in to be debugged in the second instance of Visual Studio .NET, and when the add-in code hits a breakpoint, execution passes to the debugger running in the first instance of Visual Studio .NET. From there you can step through the code, examine the contents of variables and registers, and perform other sundry debugging tasks.

Debugging add-ins in the Macros IDE is almost as easy as debugging add-ins in Visual Studio .NET. The one catch is that you can't just open the Macros IDE from an instance of Visual Studio .NET and then attach the debugger from that instance to the Macros IDE. Why not? Because the two processes will deadlock if Visual Studio .NET fires a macro event while execution is stopped in the debugger. Instead, the recommended way to debug add-ins in the Macros IDE is similar to the way you debug add-ins in Visual Studio .NET: open the add-in project in Visual Studio .NET, start a second instance of Visual Studio .NET, open the Macros IDE from the second instance of Visual Studio .NET, attach the debugger from the first instance of Visual Studio .NET to the Macros IDE process, load the add-in in the Macros IDE, and then debug as normal. (And don't forget to breathe.)

Finally, you shouldn't feel obligated to run the Visual Studio .NET debugger if you have a favorite debugger you'd rather use. Any debugger that can handle delay-load DLLs will do (such as the Microsoft CLR Debugger that comes with the .NET Framework SDK, shown in Figure 5-6).


Figure 5-6. Using the CLR Debugger to debug an add-in




/ 118