Writing Visual Studio Add-ins


Up to this point, we have confined our Visual Studio automation discussions to macros. As you have seen, macros are an ideal way to control a variety of items in the IDE. Within a macro, you have access to the entire automation object model, macros are easy to write, and they come complete with their own development environment. Even with all of these positives, however, there are limitations to what a macro can do:

  • A macro can't be used to create and display custom tool windows.

  • A macro is incapable of exposing any sort of user interface beyond simple dialog boxes and message boxes.

  • A macro can't implement a property page hosted in the Options dialog box.

  • A macro can't dynamically enable or disable menu and toolbar items in the IDE.

  • A macro can't be compiled and distributed as a binary.

Visual Studio add-ins can do all of the preceding and more. Put simply, add-ins present deeper IDE integration options to developers. So, what exactly is an add-in? An add-in is a compiled DLL containing a COM object that implements a specific interface, IDTExtensibility2, which provides the add-in with a direct connection to the IDE. As we have mentioned previously, you can write add-ins in your managed language of choice. Because we have spent so much time working with Visual Basic syntax in the macro world in this chapter, all of the add-in examples will be done in C#.

Probably the simplest way to get started with add-ins is to run the Add-in Wizard. As with the macro recorder, the wizard will give you a starting point for implementing your own add-ins, and by examining the code that the Add-in Wizard creates, you can learn a great deal about the makeup of an add-in.

Managing Add-ins

Visual Studio add-ins are controlled with the Visual Studio Add-in Manager. It allows you to do two things: load and unload any registered add-in and specify how an add-in can be loaded. To access the Add-in Manager, select Tools, Add-in Manager (see Figure 11.13).

Figure 11.13. Managing add-ins.


This dialog box will always display a list of any available add-ins on the local machine. Checking or unchecking the box next to an add-in's name will cause the add-in to immediately load or unload. The Startup check box determines whether the add-in will load automatically when Visual Studio is started. The Command Line check box performs the same action if Visual Studio is started via the command line (such as when you are launching Visual Studio as part of an automated build scenario).

Add-in Automation Objects

To programmatically manage add-ins, you use the DTE.AddIns collection, which contains an AddIn instance for every currently registered add-in (whether or not it is loaded).

You can directly reference add-ins from the DTE.AddIns collection by using their name like this:

AddIn addIn = this.DTE.AddIns.Item("MyFirstAddIn")


With a valid add-in object, you can use its properties to determine whether it is loaded, query its name, or retrieve the add-in's ProgID:

bool isLoaded = addIn.Connected; string name = addIn.Name; string id = addIn.ProgId;


Note

We use the term registered to denote an add-in that has been installed on the local machine and registered with Visual Studio. In versions prior to Visual Studio 2005, this meant that a Registry entry was created for the add-in. This concept has now been replaced with XML files: Visual Studio looks for XML files with an .addin extension to determine the list of add-ins available to be loaded (an add-in is "loaded" when it has been connected to, and loaded within, an application's host process). These .addin files are created for you automatically by the Add-in Wizard, but they can be easily created or edited by hand as well. To get a feeling for the information and structure of these files, look in the Visual Studio 2005\Addins folder under your local My Documents directory. Each registered add-in will appear here; you can poke through an add-in file by loading it into Visual Studio, Notepad, or any other text editor.


So, how do you go about creating your own add-in? The easiest way is to start with the Add-in Wizard.

Running the Add-in Wizard

The Add-in Wizard is launched whenever you try to create a new project of the type Visual Studio Add-in. From the File, New Project dialog box, select the Extensibility node in the project types tree (Visual C#, Other Project Types, Extensibility). From here, you can see two project templates: Visual Studio Add-in and Shared Add-in (see Figure 11.14).

Figure 11.14. Selecting the Visual Studio add-in project type.


We'll touch on the differences between these two project types in a bit; for now, we are interested in the Visual Studio Add-in template.

Clicking OK will start the Add-in Wizard.

Selecting a Language

After an initial welcome page, you can select the language you want to use for the add-in (see Figure 11.15).

The list of languages available will depend on two things:

  • The languages installed as part of your Visual Studio package

  • The type of add-in (shared or Visual Studio)

Figure 11.15. Picking your add-in language.


Visual Studio add-ins support Visual C#, Visual Basic, Visual J#, and both managed and unmanaged Visual C++.

Picking an Application Host

After selecting a language, you are presented with a question about "application hosts." This screen, shown in Figure 11.15, is really just asking where you want the add-in to run.

Figure 11.16. Selecting the application host.


Because you have indicated that this is a Visual Studio Add-in and not a shared add-in, your host options essentially are the Visual Studio IDE or the Macros IDE.

Note

This is a good time to discuss the differences between a Visual Studio add-in and a shared add-in. A shared add-in is the moniker given for add-ins hosted inside a Microsoft Office application, such as Microsoft Word or Microsoft Excel. A Visual Studio add-in can only be hosted within the Visual Studio or Macros IDE. If you run through the Add-in Wizard for a shared add-in, you will find that the page which asks you to select an application host (or hosts) will be populated with a list of the installed Microsoft Office applications; you won't be able to select Visual Studio as an application host for a shared add-in.


Describing the Add-in

The name and description you enter on page 3 of the wizard (see Figure 11.17) are visible in the Add-in Manager when the add-in is selected. This information is intended to give users an idea as to the add-in's functionality and purpose.

Figure 11.17. Giving the add-in a name and description.


Setting Add-in Options

The next wizard page, shown in Figure 11.18, allows you to specify various add-in options. You can indicate whether you want the add-in to appear in the Tools menu, when you want the add-in to load, and whether the add-in could potentially display a modal dialog box during its operation.

Figure 11.18. Setting add-in options.


Setting About Box Information

The second-to-last wizard page captures the text that Visual Studio will display in its About Box dialog box (see Figure 11.19).

Figure 11.19. Entering text for the Visual Studio About Box dialog box.


This is the place to include such details as where users can contact the author of the add-in, support and licensing information, copyright and version information, and so on.

Finishing the Wizard

The last page of the wizard contains a summary of the options that you have selected. After you click the Finish button, the wizard will start creating the code for your add-in based on all the selections you have made in the wizard.

Because add-ins are DLLs, the Add-in Wizard will create the add-in source as part of a class library project in the IDE. The primary code file that is created implements a class called Connect. This class inherits from all of the necessary COM interfaces to make the add-in work in the context of the IDE.

Listing 11.5 shows the Connect class as it was generated by the Add-in Wizard.

Listing 11.5. Code Generated by the Add-in Wizard

[View full width]

using System; using Extensibility; using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio.CommandBars; using System.Resources; using System.Reflection; using System.Globalization; namespace MyFirstAddin {     /// <summary>The object for implementing an Add-in.</summary>     /// <seealso class='IDTExtensibility2' />     public class Connect : 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>         /// <seealso class='IDTExtensibility2' />         public void OnConnection(object application,                ext_ConnectMode connectMode, object addInInst,                ref Array custom)         {             _applicationObject = (DTE2)application;             _addInInstance = (AddIn)addInInst;             if(connectMode == ext_ConnectMode.ext_cm_UISetup)             {                 object []contextGUIDS = new object[] { };                 Commands2 commands =                               (Commands2)_applicationObject.Commands;                 string toolsMenuName;                     try                     {                         //If you would like to move the                                         //command to a different menu,                                         //change the word "Tools" to the                         //  English version of the menu.                                         //This code will take the culture,                                         //append on the name of the menu                         //  then add the command to that menu.                                         //You can find a list of all the top-level                                         // menus in the file CommandBar.resx.                         ResourceManager resourceManager =                           new ResourceManager("MyFirstAddin.CommandBar",                           Assembly.GetExecutingAssembly());                         CultureInfo cultureInfo = new                            System.Globalization.CultureInfo                            (_applicationObject.LocaleID);                         string resourceName =                           String.Concat(cultureInfo.TwoLetterISOLanguageName,                           "Tools");                         toolsMenuName = resourceManager.GetString(resourceName);                     }                     catch                     {                         //We tried to find a localized version of                                         // the word Tools, but one was not found.                         //  Default to the en-US word, which may                                         //work for the current culture.                         toolsMenuName = "Tools";                     }                     //Place the command on the tools menu.                     //Find the MenuBar command bar, which is the                                 //top-level command bar holding all the main                                 //menu items:                     Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar =                       ((Microsoft.VisualStudio.CommandBars.CommandBars)                        _applicationObject.CommandBars)["MenuBar"];                     //Find the Tools command bar on the MenuBar command bar:                     CommandBarControl toolsControl =                       menuBarCommandBar.Controls[toolsMenuName];                     CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;                //This try/catch block can be duplicated if you wish to                //add multiple commands to be handled by your Add-in,                //just make sure you also update the QueryStatus/Exec                //method to include the new command names.                try                {                    //Add a command to the Commands collection:                    Command command = commands.AddNamedCommand2(_addInInstance,                      "MyFirstAddin", "MyFirstAddin",                      "Executes the command for MyFirstAddin", true, 59,                      ref contextGUIDS,                      (int)vsCommandStatus.vsCommandStatusSupported                        +(int)vsCommandStatus.vsCommandStatusEnabled,                      (int)vsCommandStyle.vsCommandStylePictAndText,                      vsCommandControlType.vsCommandControlTypeButton);                    //Add a control for the command to the tools menu:                    if((command != null) && (toolsPopup != null))                    {                        command.AddControl(toolsPopup.CommandBar, 1);                    }                }                catch(System.ArgumentException)                {                    //If we are here, then the exception is probably because a                    //command with that name already exists. If so there is no                    //If so there is no need to recreate the command                    // and we can safely ignore the exception.                }            }        }        /// <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>        /// <seealso class='IDTExtensibility2' />        public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)        {        }        /// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2        /// interface. Receives notification when the collection of Add-ins has        ///changed.</summary>        /// <param term='custom'>Array of parameters that are host application        /// specific.</param>        /// <seealso class='IDTExtensibility2' />        public void OnAddInsUpdate(ref 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>        /// <seealso class='IDTExtensibility2' />        public void OnStartupComplete(ref 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>        /// <seealso class='IDTExtensibility2' />        public void OnBeginShutdown(ref 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>        /// <seealso class='Exec' />        public void QueryStatus(string commandName, vsCommandStatusTextWanted            neededText, ref vsCommandStatus status, ref object commandText)        {            if(neededText ==             vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)            {                if(commandName == "MyFirstAddin.Connect.MyFirstAddin")                {                    status = (vsCommandStatus)vsCommandStatus.  vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled;                    return;                }            }        }        /// <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 if the command was handled        /// or not.</param>        /// <seealso class='Exec' />        public void Exec(string commandName, vsCommandExecOption executeOption,            ref object varIn, ref object varOut, ref bool handled)        {            handled = false;            if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)            {                if(commandName == "MyFirstAddin.Connect.MyFirstAddin")                {                    handled = true;                    return;                }            }        }        private DTE2 _applicationObject;        private AddIn _addInInstance;    } }

At this stage, the add-in doesn't actually do anything. You still have to implement the custom logic for the add-in. What the wizard has done, however, is implement much (if not all) of the tedious plumbing required to wire the add-in into the IDE, expose it on the Tools menu, and intercept the appropriate extensibility events to make the add-in work.

Now that you have a baseline of code to work with, you're ready to examine the source to understand the overall structure and layout of an add-in.

The Structure of an Add-in

The first thing to notice is that the Connect class inherits from two different interfaces: IDTCommandTarget and IDTExtensibility2.

public class Connect : IDTExtensibility2, IDTCommandTarget


The IDTCommandTarget interface provides the functionality necessary to expose the add-in via a command bar. The code to inherit from this interface was added by the wizard because the Yes, Create a Tools Menu Item box was checked on page 4.

The IDTExtensibility2 interface provides the eventing glue for add-ins. It is responsible for all of the events that constitute the life span of an add-in.

The Life Cycle of an Add-in

Add-ins progress through a sequence of events every time they are loaded or unloaded in their application host. Each of these events is represented by a method defined on the IDTExtensibility2 interface. These methods are documented in Table 11.1.

Table 11.1. IDTExtensibility2 Methods

Method

Description

OnAddInsUpdate

Called whenever an add-in is either loaded or unloaded

OnBeginShutdown

Called if Visual Studio is shut down while an add-in is loaded

OnConnection

Called when an add-in is loaded

OnDisconnection

Called when an add-in is unloaded

OnStartupComplete

Called when the add-in loads if this add-in is set to load when Visual Studio starts


The diagrams in Figure 11.20 and Figure 11.21 show how these methods (which really represent events) fall onto the normal load and unload path for an add-in.

Figure 11.20. Load sequence of events.


Figure 11.21. Unload sequence of events.


If you look back at the template code for the add-in, you can see that each one of these IDTExtensibility2 methods has been implemented. The OnDisconnection, OnAddInsUpdate, OnStartupComplete, and OnBeginShutdown methods are empty; the wizard has merely implemented the method signature. The OnConnection method, however, already has a fair bit of code to it before you even lift a hand to modify or add to the wizard-generated code.

Now you're ready to investigate what happens in each of the IDTExtensibility2 methods.

OnAddInsUpdate

The OnAddInsUpdate method is called when any add-in is loaded or unloaded from Visual Studio; because of this, the OnAddInsUpdate method is primarily useful for enforcing or dealing with dependencies between add-ins. If your add-in depends on or otherwise uses the functionality contained in another add-in, this is the ideal injection point for containing the logic that deals with that relationship.

Here is the OnAddInsUpdate method as implemented by the Add-in Wizard:

/// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 /// interface. Receives notification when the collection of Add-ins has /// changed.</summary> /// <param term='custom'>Array of parameters that are host application /// specific.</param> /// <seealso class='IDTExtensibility2' /> public void OnAddInsUpdate(ref Array custom) { }


Tip

Because you don't know which add-in has triggered the OnAddInsUpdate method, you would need to iterate through the DTE.AddIns collection and query each add-in's Connected property to determine its current state.


OnBeginShutdown

OnBeginShutdown is called for every running add-in when Visual Studio begins its shutdown sequence. If an IDE requires any clean-up code (including perhaps resetting IDE settings that have been changed during the add-in's life), you would place that code within this method.

A user may elect to cancel Visual Studio's shutdown process. OnBeginShutdown will fire regardless of whether the Visual Studio shutdown process was successful. This forces you, as an add-in author, to always assume that Visual Studio has, in fact, terminated and therefore act accordingly in your code.

Here is the OnBeginShutdown method:

/// <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> /// <seealso class='IDTExtensibility2' /> public void OnBeginShutdown(ref Array custom) { }


OnConnection

OnConnection indicates that an add-in has been loaded. It accepts four parameters:

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


The first parameter, application, is the most important; it provides a reference to the DTE object representing the IDE. You know from the preceding chapter that the DTE object is the key to accessing the entire automation object model. With macros, the DTE object is held as a global variable. For add-ins, the OnConnection method is the sole provider of this object, thus providing the crucial link between the add-in and its host IDE.

The second parameter, connectMode, is an ext_ConnectMode enumeration. It indicates exactly how the add-in was loaded (see Table 11.2 for a list of the possible ext_ConnectMode values).

Table 11.2. ext_ConnectMode Members

Member

Description

ext_cm_AfterStartup

The add-in was loaded after Visual Studio was started.

ext_cm_CommandLine

The add-in was loaded from the command line.

ext_cm_External

n/a (Visual Studio 2005 does not use this value.)

ext_cm_Solution

The add-in was loaded with a Visual Studio solution.

ext_cm_Startup

The add-in was loaded when Visual Studio started.

ext_cm_UISetup

The add-in was loaded for UI setup (this represents the initial load of an add-in).


The addInInst parameter is actually a reference to the add-in itself. And last, the custom parameter is an empty Array object. This array is passed by reference and can be used to pass parameters into and out of the add-in.

The Add-in Wizard has taken the first two parameters, explicitly cast them to their underlying types, and assigned them into two class fields for later reference:

_applicationObject = (DTE2)application; _addInInstance = (AddIn)addInInst;


The next block of code examines the ext_ConnectMode value. If this is the first time that the add-in was loaded (for example, ext_ConnectMode is equal to ext_cm_UISetup), then the code does two things: It creates a Tools menu entry for the add-in, and it creates a custom, named command to launch the add-in (this named command is called when you select the add-in from the Tools menu).

if(connectMode == ext_ConnectMode.ext_cm_UISetup) {        object []contextGUIDS = new object[] { };        Commands2 commands = (Commands2)_applicationObject.Commands;     string toolsMenuName;     try     {         //If you would like to move the command to a different menu, change the         // word "Tools" to the English version of the menu.         //  This code will take the culture, append on the name of the menu         //  then add the command to that menu. You can find a list of all         // the top-level menus in the file         //  CommandBar.resx.         ResourceManager resourceManager = new _                     ResourceManager("MyFirstAddin.CommandBar", _                     Assembly.GetExecutingAssembly());         CultureInfo cultureInfo = new _                     System.Globalization.CultureInfo(_applicationObject.LocaleID);         string resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");         toolsMenuName = resourceManager.GetString(resourceName);     }     catch     {         //We tried to find a localized version of the word Tools, but one         //was not found.         //  Default to the en-US word, which may work for the current culture.         toolsMenuName = "Tools";     }     //Place the command on the tools menu.     //Find the MenuBar command bar, which is the top-level command bar holding     //all the main menu items:     Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = _       ((Microsoft.VisualStudio.CommandBars.CommandBars)__       applicationObject.CommandBars)["MenuBar"];     //Find the Tools command bar on the MenuBar command bar:     CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];     CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;     //This try/catch block can be duplicated if you wish to add multiple commands     //to be handled by your Add-in,     // just make sure you also update the QueryStatus/Exec method to include     // the new command names.     try     {        //Add a command to the Commands collection:        Command command = commands.AddNamedCommand2(_addInInstance, _                     "MyFirstAddin", "MyFirstAddin", _                     "Executes the command for MyFirstAddin", true, 59, _                     ref contextGUIDS,                     (int)vsCommandStatus.vsCommandStatusSupported                      +(int)vsCommandStatus.vsCommandStatusEnabled,                      (int)vsCommandStyle.vsCommandStylePictAndText,                       vsCommandControlType.vsCommandControlTypeButton);        //Add a control for the command to the tools menu:        if((command != null) && (toolsPopup != null))        {            command.AddControl(toolsPopup.CommandBar, 1);        }    }    catch(System.ArgumentException)    {        //If we are here, then the exception is probably because a command with        //that name already exists. If so there is no need to recreate the        //command and we can        //  safely ignore the exception.    }


Tip

You can see that the Add-in Wizard is quite liberal with its code comments; when you set out to write your own add-in, it is often useful to read the auto-generated comments and use copy/paste methods to duplicate functionality that the wizard has generated for you.


OnDisconnection

OnDisconnection fires when the add-in is unloaded from Visual Studio. This is the opposite action from that signaled by the OnConnection method. As with OnConnection, an enumerationext_DisconnectModeis provided to this method that indicates the circumstances surrounding the unload action. For a list of the possible ext_DisconnectMode values, see Table 11.3.

Table 11.3. ext_DisconnectMode Members

Member

Description

ext_dm_HostShutdown

The add-in was unloaded because Visual Studio was shut down.

ext_dm_SolutionClosed

The add-in was unloaded because the solution was closed.

ext_dm_UISetupComplete

The add-in was unloaded after UI setup was complete.

ext_dm_UserClosed

The add-in was manually or programmatically unloaded. (This is used only if Visual Studio is still running; otherwise, ext_dm_HostShutdown will be used.)


Here is the OnDisconnection method:

[View full width]

/// <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> /// <seealso class='IDTExtensibility2' /> public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom) { }


OnStartupComplete

If an add-in is set to load automatically during Visual Studio startup, the OnStartupComplete method will fire after that add-in has been loaded.

Here is the OnStartupComplete method:

/// <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> /// <seealso class='IDTExtensibility2' /> public void OnStartupComplete(ref Array custom) { }


Reacting to Commands

Add-ins can react to commands issued within the IDE. If you recall from the discussion on commands in the preceding chapter, and in the previous section on macros, this is done through the concept of "named commands." A named command is really nothing more than an action that has a name attached to it. You already know that Visual Studio comes with its own extensive set of commands that cover a wide variety of actions in the IDE. Using the Commands/Commands2 collection, you can create your own named commands by using the AddNamedCommand2 method.

To repeat the dissection of the OnConnection method, the wizard has created a body of code responsible for creating a new named command, adding it to the Tools menu, and then reacting to the command. The IDTCommandTarget.Exec method is the hook used to react to an issued command. Here is its prototype:

void Exec (     [InAttribute] string CmdName,     [InAttribute] vsCommandExecOption ExecuteOption,     [InAttribute] ref Object VariantIn,     [InAttribute] out Object VariantOut,     [InAttribute] out bool Handled )


To handle a command issued to an add-in, you write code in the Exec method that reacts to the passed-in command.

CmdName is a string containing the name of the command; this is the token used to uniquely identify a command, and thus is the parameter you will examine in the body of the Exec method to determine if and how you will react to the command.

ExecuteOption is a vsCommandExecOption enumeration that provides information about the options associated with the command (see Table 11.4).

Table 11.4. vsCommandExecOption Members

Member

Description

vsCommandExecOptionDoDefault

Performs the default behavior

vsCommandExecOptionDoPromptUser

Obtains user input and then executes the command

vsCommandExecOptionPromptUser

Executes the command without user input

vsCommandExecOptionShowHelp

Shows help for the command (does not execute it)


The VariantIn parameter is used to pass any arguments needed for the incoming command, and VariantOut is used as a way to pass information back out of the add-in to the caller.

Lastly, Handled is a Boolean that indicates to the host application whether the add-in handled the command. As a general rule, if your add-in processed the command, it will set this to TRue. Otherwise, it will set it to false, which is a signal to Visual Studio that it needs to continue to look for a command invocation target that will handle the command.

The code to handle the Tool menu command looks like this:

       /// <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 if the command was handled        /// or not.</param>        /// <seealso class='Exec' />        public void Exec(string commandName, vsCommandExecOption executeOption,            ref object varIn, ref object varOut, ref bool handled)        {            handled = false;            if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)            {                if(commandName == "MyFirstAddin.Connect.MyFirstAddin")                {                    handled = true;                    return;                }             }        }


A Sample Add-in: Color Palette

To cap this discussion of add-ins, let's look at the process of developing a functioning add-in from start to finish. The add-in will be a color picker. It will allow users to click on an area of a color palette, and the add-in will then emit code to create an instance of a Color structure that matches the selected color from the palette. Here is a summary list of requirements for the add-in:

  • In a tool window, it will display a visual color palette representing all of the possible colors.

  • As the mouse pointer is moved over the palette, the control will display the Red, Green, and Blue values for the point directly underneath the mouse pointer.

  • If a user clicks on the palette, it will take the current RGB values and emit C# or VB code into the currently active document window to create a new color structure that encapsulates the given color.

  • The selection of language (for example, C# or VB) will be a configurable property of the control.

Getting Started

To start the development process, you will create a new solution and a Visual Studio Add-in Project called PaletteControlAddIn. The Add-in Wizard will create a code base for you inside a Connect class just as you saw earlier in this chapter. The Connect class is the place where all of the IDE and automation object modelspecific code will go.

In addition to the core add-in plumbing, you will also need to create a User Control class that encapsulates the user interface and the processing logic for the add-in.

Creating the User Control

First, you can work on getting a user control in place that has the functionality you are looking for. After you have a workable control, you can worry about wiring that control into Visual Studio using the Connect class created by the Add-in Wizard.

Add a user control (called PaletteControl) to the add-in project by selecting Project, Add User Control. After the control is added, you'll immediately add a picture box to the design surface. The picture box will display the palette of colors, stored as a simple bitmap in a resource file. With the palette in place, you now need a few label controls to display RGB values per your requirements. And finally, in the finest tradition of gold-plating, you'll also add an additional picture box that repeats the current color selection and a label that shows the code that you would generate to implement that color in a Color structure.

Figure 11.22 provides a glimpse of the user control after situating these controls on the designer.

Figure 11.22. The PaletteControl user control.


Handling Movement over the Palette

With the UI in place, you can now concentrate on the code. First, you can add an event handler to deal with mouse movements over the top of the palette picture box. With the MouseMove event handler, you can update your label controls and the secondary picture box instantly as the pointer roves over the palette bitmap:

public PaletteControl() {     InitializeComponent();     this.pictureBox1.MouseMove += new MouseEventHandler(pictureBox1_MouseMove);     this.pictureBox1.Cursor = System.Windows.Forms.Cursors.Cross; } void pictureBox1_MouseMove(object sender, MouseEventArgs e) {     // Get the color under the current pointer position     Color color = GetPointColor(e.X, e.Y);     // Update the RGB labels and the 2nd pic box     // using the retrieved color     DisplayColor(color);     // Generate our VB or C# code for the Color     // structure     SetCode(color); }


Looking at the Code Generation Properties

The PaletteControl class will expose two properties: Code is a string property that holds the Color structure code generated by clicking on the palette, and GenerateVB is a Boolean that specifies whether the control should generate Visual Basic code (GenerateVB = true) or C# code (GenerateVB = false). Here are the field and property declarations for these two properties:

string _code = ""; public string Code {     get { return _code; } } bool _generateVB = false; public string GenerateVB {    get { return _generateVB; } }


Implementing the Helper Routines

Whenever the mouse pointer moves over the picture box region, you need to capture the color components of the point directly below the cursor (GetPointColor), update the label controls and the secondary picture box control to reflect that color (DisplayColor), and then generate the code to implement a matching color structure (SetCode). Here are the implementations of these routines:

/// <summary> /// Returns a Color structure representing the color of /// the pixel at the indicated x and y coordinates. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <returns>A Color structure</returns> private Color GetPointColor(int x, int y) {     // Get the bitmap from the palette picture box     Bitmap bmp = (Bitmap)pictureBox1.Image;     // Use GetPixel to retrieve a color     // structure for the current pointer position     Color color = bmp.GetPixel(x, y);     // Return the color structure     return color; } /// <summary> /// Displays the RGB values for the given color. Also sets /// the background color of the secondary picture box. /// </summary> /// <param name="color">The Color to display</param> private void DisplayColor(Color color) {     // pull out the RGB values from the     // color structure     string R = color.R.ToString();     string G = color.G.ToString();     string B = color.B.ToString();     // set our secondary picture box     // to display the current color     this.pictureBox2.BackColor = color;     // display RGB values in the label     // controls     this.labelR.Text = R;     this.labelG.Text = G;     this.labelB.Text = B; } /// <summary> /// Generates a string representing the C# or VB code necessary to /// create a Color structure instance that matches the passed in /// Color structure. This string is then assigned to this /// user control's _code field. /// </summary> /// <param name="color">The color to represent in code.</param> /// <param name="isVB">Boolean flag indicating the language /// to use: false indicates C#, true indicates VB</param> private void SetCode(Color color, bool isVB) {     // Read in add-in settings from registry     SetPropFromReg();     string code = "";     if (isVB)     {         code = "Dim color As Color = ";     }     else     {         code = "Color color = ";     }     code = code + "Color.FromArgb(" + color.R.ToString() + ", " +         color.G.ToString() + ", " +         color.B.ToString() + ");";    _code = code;     this.labelCode.Text = _code; } /// <summary> /// Reads a registry entry and sets the language output fields /// appropriately. /// </summary> private void SetPropFromReg() {    RegistryKey regKey =      Registry.CurrentUser.OpenSubKey(@"Software\Contoso\Addins\ColorPalette");    string codeVal = (string)regKey.GetValue("Language", "CSharp");    if (codeVal == "CSharp")    {        _generateVB = false;    }    else    {       _generateVB = true;    } }


Signaling a Color Selection

Because you will need some way for the control to indicate that a user has selected a color (for example, has clicked on the palette), you will also define an event on the user control class that will be raised whenever a click is registered in the palette picture box:

public event EventHandler ColorSelected; protected virtual void OnColorSelected(EventArgs e) {    if (ColorSelected != null)        ColorSelected(this, e); } private void pictureBox1_Click(object sender, EventArgs e) {    OnColorSelected(new EventArgs()); }


Tip

To isolate and test the user control, you may want to add a Windows forms project to the solution and host the control on a Windows form for testing. Just drop the control onto the form and run the forms project.


With the user control in place, you are ready to proceed to the second stage of the add-in's development: wiring the user control into the IDE.

Finishing the Connect Class

The Connect class already has the basic add-in code; now it's time to revisit that code and add the custom code to drive the user control. You'll want the add-in to integrate seamlessly into the development environment, so you can use a tool window to display the user control that you previously created.

Harking back to the discussions of the automation object model, you know that the Windows2 collection has a CreateToolWindow2 method, which allows you to create your own custom tool windows.

Note

Prior versions of Visual Studio required you to create a shim control (using C++) that would host a control for display in a tool window. The tool window, in turn, would then host the shim. With Visual Studio 2005 (and its improved Windows2.CreateToolWindow2 method), this is no longer necessary. Now you can directly host a managed user control in a tool window.


Here is the method prototype:

Window CreateToolWindow2 (     AddIn Addin,     string Assembly,     string Class,     string Caption,     string GuidPosition,     [InAttribute] out Object ControlObject )


Displaying the Tool Window and User Control

Because you want the tool window to be created and displayed after the add-in has loaded, this CreateToolWindow2 method call will be placed in the Connect.OnConnection method. First, you set up a local object to point to the DTE.ToolWindowscollection:

// The DTE.ToolWindows collection Windows2 toolWindows= (Windows2)_applicationObject.Windows;


Then you need an object to hold the reference to the tool window that you will create:

// Object to refer to the newly created tool window Window2 toolWindow;


And finally, you need to create the parameters to feed to the CreateToolWindow2 method:

// Placeholder object; will eventually refer to the user control // hosted by the user control object paletteObject = null; // This section specifies the path and class name for the palette // control to be hosted in the new tool window; we also need to // specify its caption and a unique GUID. Assembly asm = System.Reflection.Assembly.GetExecutingAssembly(); string assemblyPath = asm.Location; string className = "PaletteControlAddIn.PaletteControl"; string guid = new Guid().ToString(); string caption = "Palette Color Picker";


With that in place, you are only a few lines of code away from creating and displaying the tool window:

// Create the new tool window with the hosted user control toolWindow = (Window2)toolWindows.CreateToolWindow2(_addInInstance, assemblyPath,    className, caption, guid, ref paletteObject); // If tool window was created successfully, make it visible if (toolWindow != null) {     toolWindow.Visible = true; }


Capturing User Control Events

The add-in is missing one last piece: You need to react whenever the user clicks on the palette by grabbing the generated code (available from the PaletteControl.Code property) and inserting it into the currently active document. There are two tasks at hand. First, you need to write an event handler to deal with the click event raised by the PaletteControl object. But to do that, you need a reference to the user control. This is the purpose of the paletteObject object that you pass in as the last parameter to the CreateToolWindow2 method. Because this is passed in by reference, it will hold a valid instance of the PaletteControl after the method call completes and returns. You can then cast this object to the specific PaletteControl type, assign it to a field within the Connect class, and attach an event handler to the PaletteControl.ColorSelected event:

// retrieve a reference back to our user control object _paletteControl = (PaletteControl)paletteObject; // wire up event handler for the PaletteControl.ColorSelected event _paletteControl.ColorSelected +=     new System.EventHandler(paletteControl1_ColorSelected);


Tip

Getting a reference to the user control can be a bit tricky. If the user control is not a part of the same project as your add-in class, CreateToolWindow2 will return only a null value instead of a valid reference to the user control. If you want to develop your user control outside the add-in project, you have to make sure that the user control is fully attributed to be visible to calling COM components. See the topic "Exposing .NET Framework Components to COM" in MSDN for details on how this is accomplished.


Inserting the Generated Code

You react to the ColorSelected event by grabbing the content of the PaletteControl.Code property and writing it into the currently active document. Again, you will use your automation object model knowledge gained from the preceding chapter to make this happen. The DTE.ActiveDocument class will hold a reference to the currently active document. By using an edit point, you can easily write text directly into the text document:

TextDocument currDoc = _applicationObject.ActiveDocument.Object; EditPoint2 ep = currDoc.Selection.ActivePoint.CreateEditPoint(); ep.Insert(_paletteControl.Code); ep.InsertNewLine();


Exposing Add-in Settings

The final step is to make the add-in's language choice a configurable option. Users should be able to indicate whether they want the add-in to emit C# or Visual Basic code. To do this, you need to have a user interface in the form of an Options page (that will display in the Options dialog box), and you need a place to persist the option selections.

Creating the Option Page UI

Add-ins can reference an Options page that will appear in the Tools Options dialog box. Again, as you did with the custom tool window, you will build a user control to implement the logic and the user interface for the Options page.

You start by creating a new user control to the existing add-in project. For this example, call this class PaletteControlOptionPage. Adding a label control and two radio button controls will enable you to indicate the language preference for the palette add-in. Figure 11.23 shows the design surface of the Options page.

Figure 11.23. The user control's design surface.


The user control for the Options page needs to inherit from IDTToolsOptionsPage:

public partial class PaletteControlOptionPage : UserControl, IDTToolsOptionsPage {    public PaletteControlOptionPage()    {         InitializeComponent();    } }


The IDTToolsOptionsPage interface defines five methods, outlined in Table 11.5.

Table 11.5. IDTToolsOptionsPage Members

Member

Description

GetProperties

Returns a properties object in response to calling DTE.Properties for this specific Options page

OnAfterCreated

Fires after the Tools Options page is created for the first time

OnCancel

Fires if the user clicks the Cancel button on the Tools Options dialog box

OnHelp

Fires if the user clicks on the Help button on the Tools Options dialog box

OnOK

Fires if the user clicks on the OK button on the Tools Options dialog box


These methods are called as the Options page progresses through its normal sequence of states, as you can see in Figure 11.24.

Figure 11.24. Tools Options page action sequence.


By placing code within these methods, you can read in and store any configuration changes that a user makes through the Options page. In this case, you can keep things simple: Read in a value from a Registry entry as part of the OnAfterCreated method and update that same entry as part of the OnOK method:

public void OnAfterCreated(DTE DTEObject) {     // read our current value from registry     // TODO: we should really include contingency code here for creating     // the key if it doesn't already exist, dealing with unexpected values,     // exceptions, etc.     RegistryKey regKey =        Registry.CurrentUser.OpenSubKey(@"Software\Contoso\Addins\ColorPalette");     string codeVal = (string)regKey.GetValue("Language", "CSharp");     if (codeVal == "CSharp")     {         this.radioButtonCSharp.Checked = true;         this.radioButtonVB.Checked = false;     }     else     {         this.radioButtonCSharp.Checked = true;         this.radioButtonVB.Checked = false;     } } public void OnOK() {     string codeValue = "CSharp";    // our default value     if (this.radioButtonVB.Checked)     {         codeValue = "VB";     }     // update the registry with the new setting     RegistryKey regKey =        Registry.CurrentUser.OpenSubKey(@"Software\Contoso\Addins\ColorPalette");     regKey.SetValue("Language", codeVal); }


Note

It is up to you to decide where and how you persist your add-in's settings. The Registry is one logical place; you could also elect to store your settings in an XML file that is deployed along with your binaries.


Registering the Options Page

The registration mechanism for an Options page is the same as that for an add-in: The .addin file is used. By adding a few lines of XML, you can indicate to Visual Studio that an Options page exists with the custom add-in. You can do this easily by editing the .addin file right in Visual Studio (because it is automatically created as part of the project).

To include the necessary XML registration information, edit the .addin file and place the following XML before the closing </extensibility> tag:

[View full width]

<ToolsOptionsPage> <Category Name="Color Palette"> <SubCategory Name="Code Generation"> <Assembly>C:\Documents and Settings\lpowers\My Documents\Visual Studio 2005\Projects\ PaletteControlAddIn\PaletteControlAddIn\bin\ PaletteControlAddIn.dll</Assembly> <FullClassName>PaletteControlAddIn.PaletteControlOptionPage </FullClassName> </SubCategory> </Category> </ToolsOptionsPage>


You use the Category tag to specify the name of the option category displayed in the Tools Options dialog box. The SubCategory tag specifies the subnode under that category. The Assembly tag provides a path to the add-in's DLL file, and the FullClassName tag contains the full name for the add-in class.

With this final step complete, the add-in is fully functional. You can compile the project and then immediately load the add-in using the Add-in Manager. Figure 11.25 shows the add-in in action, and a complete code listing for the Connect, PaletteControl, and PaletteControlOptionPage classes (in that order) is provided in Listing 11.6.

Figure 11.25. The color palette add-in.


Listing 11.6. The Connect, PaletteControl, and PaletteControlOptionPage Classes

[View full width]

using Extensibility; using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio.CommandBars; using System.Resources; using System.Reflection; using System.Globalization; using System.Windows.Forms; namespace PaletteControlAddIn {     /// <summary>The object for implementing an Add-in.</summary>     /// <seealso class='IDTExtensibility2' />     public class Connect : IDTExtensibility2, IDTCommandTarget     {         #region Fields         private DTE2 _applicationObject;         private AddIn _addInInstance;         private PaletteControl _paletteControl;         #endregion         #region Events and Event Handlers         private void paletteControl1_ColorSelected(object sender, EventArgs e)         {             try             {                 TextDocument currDoc =                   (TextDocument)_applicationObject.ActiveDocument.Object("");                 EditPoint2 ep = (EditPoint2)                   currDoc.Selection.ActivePoint.CreateEditPoint();                 ep.Insert(_paletteControl.Code);                 ep.InsertNewLine(1);             }             catch (Exception ex)             {                 MessageBox.Show("Exception caught: " + ex.ToString());            }        }        #endregion        /// <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>        /// <seealso class='IDTExtensibility2' />        public void OnConnection(object application, ext_ConnectMode connectMode,          object addInInst, ref Array custom)        {             _applicationObject = (DTE2)application;             _addInInstance = (AddIn)addInInst;             #region Tool menu command and control setup             if (connectMode == ext_ConnectMode.ext_cm_UISetup)             {                object []contextGUIDS = new object[] { };                Commands2 commands = (Commands2)_applicationObject.Commands;                string toolsMenuName;                try                {                    //If you would like to move the command to a different menu,                    // change the word "Tools" to the English version of the menu.                    // This code will take the culture, append on the name of                    // the menu then add the command to that menu. You can find                    // a list of all the top-level menus in the file                    //  CommandBar.resx.                    ResourceManager resourceManager = new                      ResourceManager("PaletteControlAddIn.CommandBar",                      Assembly.GetExecutingAssembly());                    CultureInfo cultureInfo = new System.Globalization.CultureInfo(_applicationObject.LocaleID);                    string resourceName =                      String.Concat(cultureInfo.TwoLetterISOLanguageName,                      "Tools");                    toolsMenuName = resourceManager.GetString(resourceName);                }                catch                {                    //We tried to find a localized version of the word Tools,                    //but one was not found.                    //Default to the en-US word, which may work for the current                    //culture.                    toolsMenuName = "Tools";                }                //Place the command on the tools menu.                //Find the MenuBar command bar, which is the top-level command                //bar holding all the main menu items:                Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar =                  ((Microsoft.VisualStudio.CommandBars.CommandBars)                  _applicationObject.CommandBars)["MenuBar"];                                //Find the Tools command bar on the MenuBar command bar:                CommandBarControl toolsControl =                  menuBarCommandBar.Controls[toolsMenuName];                CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;                                //This try/catch block can be duplicated if you wish to add                //multiple commands to be handled by your Add-in,                // just make sure you also update the QueryStatus/Exec                // method to include the new command names.                try                {                    //Add a command to the Commands collection:                    Command command = commands.AddNamedCommand2(_addInInstance,                      "PaletteControlAddIn", "PaletteControlAddIn", "Executes the command  for PaletteControlAddIn", true, 59, ref contextGUIDS,                      (int)vsCommandStatus.vsCommandStatusSupported                        +(int)vsCommandStatus.vsCommandStatusEnabled,                      (int)vsCommandStyle.vsCommandStylePictAndText,                      vsCommandControlType.vsCommandControlTypeButton);                    //Add a control for the command to the tools menu:                    if((command != null) && (toolsPopup != null))                    {                       command.AddControl(toolsPopup.CommandBar, 1);                    }                }                catch(System.ArgumentException)                {                    //If we are here, then the exception is probably because a                    //command with that name already exists.                    //If so there is no need to recreate the command                    //and we can safely ignore the exception.                }            }            #endregion            #region Create Tool Window            // The DTE.ToolWindows collection            Windows2 windows = (Windows2)_applicationObject.Windows;            // Object to refer to the newly created tool window            Window2 toolWindow;            // Placeholder object; will eventually refer to the user control            // hosted by the user control            object paletteObject = null;            // This section specifies the path and class name for the palette            // control to be hosted in the new tool window; we also need to            // specify its caption and a unique GUID.             Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();             string assemblyPath = asm.Location;             string className = "PaletteControlAddIn.PaletteControl";             string guid = Guid.NewGuid().ToString();             string caption = "Palette Color Picker";            try            {                // Create the new tool window and insert the user control in it.                toolWindow = (Window2)windows.CreateToolWindow2(_addInInstance, _                   assemblyPath, className, caption, guid, ref paletteObject);                // If tool window was created successfully, make it visible                if (toolWindow != null)                {                    toolWindow.Visible = true;                }                // retrieve a reference back to our user control object                _paletteControl = (PaletteControl)paletteObject;                // wire up event handler for the PaletteControl.ColorSelected event                _paletteControl.ColorSelected += new _                    System.EventHandler(paletteControl1_ColorSelected);            }            catch (Exception ex)            {                MessageBox.Show("Exception caught: " + ex.ToString());            }            #endregion        }        /// <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>        /// <seealso class='IDTExtensibility2' />        public void OnDisconnection(ext_DisconnectMode disconnectMode,            ref Array custom)        {        }        /// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2              /// interface. Receives notification when the collection of Add-ins              /// has changed.</summary>        /// <param term='custom'>Array of parameters that are host application              ///specific.</param>        /// <seealso class='IDTExtensibility2' />        public void OnAddInsUpdate(ref 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>        /// <seealso class='IDTExtensibility2' />        public void OnStartupComplete(ref 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>        /// <seealso class='IDTExtensibility2' />        public void OnBeginShutdown(ref 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>        /// <seealso class='Exec' />        public void QueryStatus(string commandName, vsCommandStatusTextWanted _                 neededText, ref vsCommandStatus status, ref object commandText)        {            if(neededText == _                        vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)            {                if(commandName == _                                "PaletteControlAddIn.Connect.PaletteControlAddIn")                {                    status = _                 (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported| _                 vsCommandStatus.vsCommandStatusEnabled;                     return;                }            }        }        /// <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 if the command was handled or              /// not.</param>        /// <seealso class='Exec' />        public void Exec(string commandName, vsCommandExecOption executeOption, _                 ref object varIn, ref object varOut, ref bool handled)        {            handled = false;            if(executeOption == _                        vsCommandExecOption.vsCommandExecOptionDoDefault)            {                if(commandName == _                                "PaletteControlAddIn.Connect.PaletteControlAddIn")                {                    handled = true;                    return;                }            }        }    } } using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using Microsoft.Win32; namespace PaletteControlAddIn {    public partial class PaletteControl : UserControl    {        #region fields/properties        string _code = "";        public string Code        {            get { return _code; }        }        bool _generateVB = false;        public bool GenerateVB        {            get { return _generateVB; }        }        #endregion        #region Events and Event Handlers        public event EventHandler ColorSelected;        protected virtual void OnColorSelected(EventArgs e)        {            if (ColorSelected != null)                ColorSelected(this, e);        }        void pictureBox1_MouseMove(object sender, MouseEventArgs e)        {            // Get the color under the current pointer position            Color color = GetPointColor(e.X, e.Y);            // Update the RGB labels and the 2nd pic box            // using the retrieved color            DisplayColor(color);            // Generate our VB or C# code for the Color            // structure            SetCode(color);        }        private void pictureBox1_Click(object sender, EventArgs e)        {            OnColorSelected(new EventArgs());        }        #endregion        #region Ctor(s)        public PaletteControl()        {            InitializeComponent();            this.pictureBox1.MouseMove +=                new MouseEventHandler(pictureBox1_MouseMove);            this.pictureBox1.Click +=                new System.EventHandler(this.pictureBox1_Click);            this.pictureBox1.Cursor = System.Windows.Forms.Cursors.Cross;        }        #endregion        #region Internal Routines        /// <summary>        /// Returns a Color structure representing the color of        /// the pixel at the indicated x and y coordinates.        /// </summary>        /// <param name="x"></param>        /// <param name="y"></param>        /// <returns>A Color structure</returns>        private Color GetPointColor(int x, int y)        {            // Get the bitmap from the palette picture box            Bitmap bmp = (Bitmap)pictureBox1.Image;            // Use GetPixel to retrieve a color            // structure for the current pointer position            Color color = bmp.GetPixel(x, y);            // Return the color structure            return color;        }        /// <summary>        /// Displays the RGB values for the given color. Also sets        /// the background color of the secondary picture box.        /// </summary>        /// <param name="color">The Color to display</param>        private void DisplayColor(Color color)        {            // pull out the RGB values from the            // color structure            string R = color.R.ToString();            string G = color.G.ToString();            string B = color.B.ToString();            // set our secondary picture box            // to display the current color            this.pictureBox2.BackColor = color;            // display RGB values in the label            // controls            this.labelR.Text = R;            this.labelG.Text = G;            this.labelB.Text = B;        }        /// <summary>        /// Generates a string representing the C# or VB code necessary to        /// create a Color structure instance that matches the passed in        /// Color structure. This string is then assigned to this        /// user control's _code field.        /// </summary>        /// <param name="color">The color to represent in code.</param>        /// <param name="isVB">Boolean flag indicating the language        /// to use: false indicates C#, true indicates VB</param>        private void SetCode(Color color)        {            SetPropFromReg();            string code = "";            if (_generateVB)            {                code = "Dim color As Color = ";            }            else            {                code = "Color color = ";            }            code = code + "Color.FromArgb(" + color.R.ToString() + ", " +                color.G.ToString() + ", " +                color.B.ToString() + ");";            _code = code;            this.labelCode.Text = _code;        }        /// <summary>        /// Reads a registry entry and sets the language output fields        /// appropriately.        /// </summary>        private void SetPropFromReg()        {            RegistryKey regKey = _              Registry.CurrentUser.OpenSubKey(@"Software\Contoso\Addins\ ColorPalette");            string codeVal = (string)regKey.GetValue("Language", "CSharp");            if (codeVal == "CSharp")            {                _generateVB = false;            }            else            {                _generateVB = true;            }        }        #endregion    } } using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using Extensibility; using EnvDTE; using EnvDTE80; using Microsoft.Win32; namespace PaletteControlAddIn {     public partial class PaletteControlOptionPage : UserControl,         IDTToolsOptionsPage    {         public PaletteControlOptionPage()         {             InitializeComponent();         }         #region IDTToolsOptionsPage Members         public void GetProperties(ref object PropertiesObject)         {             throw new Exception("The method or operation is not implemented.");         }         public void OnAfterCreated(DTE DTEObject)         {             // read our current value from registry             // TODO: we should really include contingency code here for creating             // the key if it doesn't already exist, dealing with unexpected values,             // exceptions, etc.             RegistryKey regKey = _                Registry.CurrentUser.OpenSubKey(@"Software\Contoso\Addins\ ColorPalette");             string codeVal = (string)regKey.GetValue("Language", "CSharp");             if (codeVal == "CSharp")             {                 this.radioButtonCSharp.Checked = true;                this.radioButtonVB.Checked = false;            }            else            {                this.radioButtonCSharp.Checked = true;                this.radioButtonVB.Checked = false;            }        }        public void OnCancel()        {        }        public void OnHelp()        {            throw new Exception("The method or operation is not implemented.");        }        public void OnOK()        {            string codeVal = "CSharp";    // our default value            if (this.radioButtonVB.Checked)            {                codeVal = "VB";            }            // update the registry with the new setting            RegistryKey regKey = _               Registry.CurrentUser.OpenSubKey(@"Software\Contoso\Addins\ ColorPalette");            regKey.SetValue("Language", codeVal);        }        #endregion    } }




Microsoft Visual Studio 2005 Unleashed
Microsoft Visual Studio 2005 Unleashed
ISBN: 0672328194
EAN: 2147483647
Year: 2006
Pages: 195

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net