Creating an Add-In Command


Now that you know how commands are named, found, and run, it's time to see how you can create your own commands. As we saw earlier, when a command built into Visual Studio is invoked, the add-on program for that command is located because of the GUID assigned to the command, and it is asked to handle the command invocation. Likewise, commands that you create need a target that handles the command invocation. Commands can be dynamically created and removed, but creating them requires that an add-in be associated with the new command so Visual Studio can find and use that add-in as the target. The method to create a command, AddNamedCommand2, can be found on the Commands2 collection object. Here is its signature:

 public EnvDTE.Command AddNamedCommand2(EnvDTE.AddIn AddInInstance,     string Name, string ButtonText, string Tooltip, bool MSOButton,     int Bitmap = 0, ref object[] ContextUIGUIDs,     int vsCommandStatusValue = 3, int CommandStyleFlags = 3,     EnvDTE80.vsCommandControlType ControlType = 2) 

and here are the arguments:

  • AddInInstance The AddIn object that will act as the command invocation target.

  • Name The name of the command. The name can contain only alphanumeric characters and the underscore character.

  • ButtonText The text that is displayed on any user interface elements, such as buttons, for the command when placed on menus or command bars.

  • ToolTip Descriptive text providing users with information about the command.

  • MSOButton True if the bitmap to display on user interface elements for this command should use the predefined command bar button graphics. If False, the graphic for the button is retrieved from the satellite DLL that is associated with the add-in.

  • BitmapIf the MSOButton argument is True, the value passed to the MSOButton parameter is the index of the predefined command bar button graphic. See the HTML page in the CommandUIBmps folder included with the book's sample files for a listing of available images. If MSOButton is False, the MSOButton parameter is the resource identifier of the bitmap picture in the satellite DLL.

  • ContextUIGUIDs Visual Studio defines a list of GUIDs that, as Visual Studio enters and exits a particular state, such as entering and exiting debugging mode, it marks as active and inactive. Some of these GUIDs are listed in the EnvDTE80. ContextGuids class. When a particular GUID becomes active and that GUID is passed to AddNamedCommand2 through this parameter, the command will become visible. For example, suppose you have a command that helps users debug their code. You could pass the context GUID vsContextGuidDebugging, and when the user starts debugging, your command will use the default state passed for the next parameter, vsCommandStatusValue. If you do not have any context GUIDs for your command, an empty array of type System.Object should be passed for this value, and the state passed to vsCommandStatusValue will always be applied.

  • vsCommandStatusValueThis is the default availability state of the button. If the add-in that handles the command invocation has not yet been loaded, rather than forcing the add-in to load to find how the command should be displayed, this argument provides a default availability state. This argument value is used in place of the value returned through the StatusOption argument of the QueryStatus method on the IDTCommandTarget interface, which we'll discuss later in this chapter.

  • CommandStyleFlagsUser interface elements for the new command can show just a bitmap (as the items on the standard toolbar do), just the name of the text (as the items on the menu bar do), or both. This parameter controls the appearance of the user interface element and is a value from the vsCommandStyle enumeration.

  • ControlType When the command is created, no user interface element—such as a menu item, combo box, or most recently used (MRU) list—is created for that command. But if you intend to create a UI element for the command, you need to declare which kind of element will be created. This declaration is done through this parameter.

When called, the AddNamedCommand2 method adds an item to the internal list of commands maintained by Visual Studio. The full name of the command, which you can use in the Command Window or as an argument to the ExecuteCommand method, is constructed by taking the fully qualified name of the class implementing the add-in (in the form of Namespace.Class) and concatenating a period, followed by the value of the Name parameter. So, for example, if the name you provide to the AddNamedCommand2 method is MyCommand and the Namespace.ClassName of the add-in is MyAddin.Connect, the name of the command that's created is MyAddin.Connect.MyCommand.

All commands added with this method also have a GUID and ID pair assigned to them. The GUID that is used for all commands created with AddNamedCommand2 is defined by the constant EnvDTE.Constants.vsAddInCmdGroup; the ID value starts at the index 1 for the first call to AddNamedCommand2, and depending on the value passed to the ControlType parameter, it is incremented by anywhere from 1 to 25 values every time the AddNamedCommand2 method is called.

Handling a Command Invocation

With a newly created command, our code now needs to provide a way for Visual Studio to call back to the add-in to let it know when the command is invoked. Usually, when an add-in or macro wants to be informed when the user has performed an action, an event connection is made. But command handlers work a bit differently: rather than connecting to an event source, your add-in must implement a specific interface. The reason for not using events is simple. When an add-in command is invoked, if the add-in that handles that command hasn't been loaded, the code for the add-in is loaded into memory and run by calling the OnConnection and other appropriate IDTExtensibility2 methods, just as if you were to go into the Add-in Manager dialog box and select the check box for that add-in. Because the add-in is demand-loaded (loaded when the command is run), code within that add-in could not have been run to connect to an event handler.

The interface to handle command invocations, named IDTCommandTarget, is modeled on the IOleCommandTarget interface of the Win32 SDK, but IDTCommandTarget has been changed to be easier to use with languages such as C# or Visual Basic. This is its signature:

 public interface IDTCommandTarget {     public void Exec(string CmdName,         EnvDTE.vsCommandExecOption ExecuteOption, ref object VariantIn,          ref object VariantOut, ref bool Handled);     public void QueryStatus(string CmdName,         EnvDTE.vsCommandStatusTextWanted NeededText,          ref EnvDTE.vsCommandStatus StatusOption,         ref object CommandText); } 

When these two methods are called and which values are passed to them depend on the type of command that you are adding to Visual Studio. The simplest case is when you pass the value vsCommandControlTypeButton for the ControlType parameter for AddNamedCommand2, and we will first discuss these two methods in terms of this command type. When invoked, all commands that your add-in creates are dispatched through this interface, particularly through the Exec method. The Exec method has the following arguments:

  • CmdName The full name of the command. Your add-in should do a case-sensitive compare on this string to determine which command is being asked to run because all commands that the add-in creates are sent to this method for handling.

  • vsCommandExecOption For most situations, the value passed to this parameter is the vsCommandExecOptionDoDefault enumeration value, informing your add-in that it should do the work defined for that command.

  • VariantInAs you'll see later in this chapter, commands can be passed data. If any data is passed to your command, they are passed through this argument.

  • VariantOutThis argument is used to pass data from your add-in to the caller.

  • HandledThis argument allows your add-in to pass back data to Visual Studio, signaling whether your add-in handled the command. If a true value is returned, it is assumed that no further processing for the command is necessary. If this value is set to false on return, Visual Studio continues searching for a handler for the command. The search should fail because no other command handler will accept the same GUID and ID pair for the command your add-in has created.

Command State

A command and its user interface don't always need to be enabled and available to the user. For example, your add-in's command might be available only when a text editor is the currently active window. You can control whether your command is enabled, disabled, or in the latched state (which means a check mark is drawn next to the button if it is a menu item or appears with a box drawn around it if it is on a toolbar). You control this state by using the QueryStatus method of the IDTCommandTarget interface. If your add-in hasn't yet been loaded, the default status, or value passed as the last argument of AddNamedCommand, is used to control the default behavior. However, once you've loaded the add-in–by executing the command or manually through the Add-in Manager dialog box–QueryStatus is called to determine the state. The QueryStatus method has the following arguments:

  • CmdNameThis argument has the same meaning as the CmdName argument passed to the Exec method of the IDTCommandTarget interface.

  • NeededTextThis parameter is always vsCommandStatusTextWantedNone. Your add-in should always verify that this value is passed because the other values are reserved for future versions of Visual Studio.

  • StatusOptionYour add-in should fill in this parameter, which lets Visual Studio know whether the add-in command is supported (vsCommandStatusSupported) or unsupported (vsCommandStatusUnsupported), whether the command is enabled and can be called (vsCommandStatusEnabled), whether the command user interface can't be seen (vsCommandStatusInvisible), or whether the user interface is drawn in the selected state (vsCommandStatusLatched). You can logically OR these values together to create the current status of the command and pass it back through this argument.

  • CommandTextThis value currently isn't used by Visual Studio and shouldn't be modified.

Periodically, such as when the focus changes from one window to another or when a menu is displayed that contains an add-in command, Visual Studio calls QueryStatus for that command to ensure that the user interface is synchronized with the command state. It is important to keep the code that implements QueryStatus as efficient as possible; otherwise, the user interface might become sluggish. Suppose you create a command that queries the currently active file's attributes, and the state of a command depends on those file attributes. If the file is on the local disk, calling a method to retrieve the attributes of a file is a relatively fast operation. But if the file is on a network share, that operation can take awhile to perform–especially if the network is temporarily unavailable. A user who has to wait for a command to update itself because he or she showed the menu containing your command would be much happier if the command were always enabled and he or she would receive an error message when the command was invoked.

MRU Button Commands

If you were to choose File | Recent Files, you will see a list of files that you have recently opened. This list of files is implemented using a most recently used menu item list, or MRU button list, and is created with a command type of vsCommandControlTypeMRUButton. An MRU button list allows you to create many related commands with one call to the AddNamedCommand2 method. When UI for these commands are added to a menu, multiple menu buttons are created and grouped together. When this command type is used, Visual Studio will create 25 separate commands rather than just the one it would create when the command type is vsCommandControlTypeButton. If the name of the command supplied to the AddNamedCommand2 method is, for example, MRUButton, and the full name of the class implementing IDTCommandTarget is MyAddin.Connect, then AddNamedCommand2 will create commands named MyAddin.Connect.MRUButton, MyAddin.Connect.MRUButton_1, MyAddin. Connect.MRUButton_2, and so on to MyAddin.Connect.MRUButton_24. This allows you to create an MRU list of 25 separate items.

For this command type, the QueryStatus method is used not only to retrieve the status of the command but also to retrieve the text of the menu item. If your add-in has only four separate MRU items, for the commands MyAddin.Connect.MRUButton, MyAddin.Connect.MRUButton_1, MyAddin.Connect.MRUButton_2, and MyAddin.Connect.MRUButton_3, your query status method returns as the value for status vsCommandStatus.vsCommandStatusSupported. For these values, the commandStatus will also not be null/Nothing, and it is your opportunity to set the text displayed for the menu item. MRU items can have text that changes frequently because (such as in the list of open files) the user will open, close, and remove files quite often, and you will want to keep that list as up-to-date as possible. Because, for this example, you want to display only four MRU menu items, the QueryStatus method should return vsCommandStatus.vsCommandStatusUnsupported for MyAddin.Connect.MRUButton_4 through MyAddin.Connect.MRUButton_24. This bit of code shows how to implement the QueryStatus method for an MRU button list. It maintains a list of four items, which is used for the text of the four menu items. When executed, the code will look at the command name, and if the name is out of the acceptable range, vsCommandStatusUnsupported is passed back to the caller. If the command name is in the appropriate range, it sets the commandText parameter to a string that will be displayed on the menu item for that MRU item, sets the status parameter to a value indicating the command is available, and then returns.

 string []MRUItemNames = new string[]   {"Item 1", "Item 2", "Item 3", "Item 4" }; void QueryStatus(string commandName,     vsCommandStatusTextWanted neededText,     ref vsCommandStatus status, ref object commandText) { if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)      {         int index = 0;         string rootCommandName = "MyAddin.Connect.MRUButton";         if (commandName.StartsWith(rootCommandName))          {                //Check for the main command of the group              if ("MyAddin.Connect.MRUButton" == commandName)              {                  index = 0;              }              else //Command is of the form MyAddin.Connect.MRUButton_*              {                 index = int.Parse(commandName.Substring(rootCommandName.Length + 1));              }              if (index > 3)                //If the command index is out of the                //range of MRU items supported...              { //then the command is should not show                 status = vsCommandStatus.vsCommandStatusUnsupported;                  return;              }              else              { //then show the command, and set the text of the item                 status = (vsCommandStatus)                   vsCommandStatus.vsCommandStatusSupported |                   vsCommandStatus.vsCommandStatusEnabled;                  commandText = MRUItemNames[index];                  return;              }          }      } } 

Visual Studio may also call the QueryStatus method just to retrieve the text on the menu item and not require the status of the command. In this case, the neededText parameter will be set to the value vsCommandStatusTextWantedName. You can change the aforementioned QueryStatus to also check for this value, and because both status and commandText are set (setting one value when it is not needed does not cause any side effects–Visual Studio just ignores it) in this code, you will handle both cases:

 void QueryStatus(string commandName,   vsCommandStatusTextWanted neededText,   ref vsCommandStatus status, ref object commandText) { if ((neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone) ||     (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedName))      {         //TODO: Code to handle the QueryStatus      } } 

The Exec method for this control type is very much like the Exec method for a vsCommandTypeButton command type, except that you will have multiple, similarly named items being passed to the Exec method. Code to find which item was executed will look similar to that in the QueryStatus method, except, rather than returning the text and status of the command, you will perform the appropriate action for that command:

 void Exec(string commandName,       vsCommandExecOption executeOption,       ref object varIn, ref object varOut, ref bool handled) {     string rootCommandName = "MyAddin.Connect.MRUButton";     handled = false;     if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)     {         if (commandName == rootCommandName)         {             //Do the operation for MRUItemNames[0]             handled = true;             return;         }         else if (commandName.StartsWith(rootCommandName))         {             int index = int.Parse(commandName.Substring(rootCommandName.Length + 1));             //Do the operation for MRUItemNames[index]             handled = true;             return;         }     } } 

Drop-Down Combo Boxes and MRU Combo Boxes

These command types are used to implement a combo box on a command bar. The difference between the two command types is in how the user interacts with them. An MRU combo box lets the user enter new text and also click the arrow to the right of the drop-down combo box to select items that were entered in the past. This is similar to the find combo box on the standard command bar. A drop-down combo box is more restrictive; it allows the user to select only items that you choose from within your add-in code, and the user cannot enter new items. This type of combo box is similar to the Solution Configuration drop-down.

The items in a drop-down combo box are filled in through a call to the Exec method. When the command type is a vsCommandControlTypeDropDownCombo, the AddNamedCommand2 method will create two separate commands. One command is used to retrieve the text that is shown within the main portion of the combo box, indicating the currently selected item. This command is also used to notify your code that the user has executed the command, meaning that the when the user selects an item from within the combo box, the Exec method is called with the text of the item that was selected. The second command that is created is used to retrieve the text of the items that are shown within the drop-down portion of the combo box, listing the possible items that the user can select. When the AddNamedCommand2 method is called, the fully qualified name of the class implementing the add-in in the form of Namespace.ClassName is combined with the name of the command you supply. So if the class name is MyAddin.Connect and the command name is DropDownCombo, the resulting name of the first command that is created is MyAddin. Connect.DropDownCombo. The second command has this same name, except the text "_1" is appended, making the command name MyAddin.Connect.DropDownCombo_1. When the method Exec is called with a command name of MyAddin.Connect.DropDownCombo, you will need to check the values of the parameters varIn and varOut to determine if the list of items to fill in the drop-down portion is being asked for, or if a selection was made within the drop-down. If the value of varIn is not null/Nothing, and if the value it contains is a string, a selection was made within the drop-down. However, if the varOut value is not null/Nothing, your add-in is being queried for the currently selected item text to show in the drop-down, and you should set it to a string. Here is an example of the code implementing this first command:

 void Exec(string commandName,   vsCommandExecOption executeOption,   ref object varIn, ref object varOut, ref bool handled) {     if (commandName == "MyAddin.Connect.DropDownCombo")     {         if ((varIn != null) && (varIn is string))         {             //The command was executed, retrive the selected text.             string selectedText = varIn as string;             //Perform some operation on the selected text.         }         else         {             //The selected text is being asked for, return that here.             varOut = "Item 1";         }             handled = true;             return;     } } 

If the Exec method is called with the command name of the second command, the varOut value will not be null/Nothing, and you need to pass back an array containing strings of items to show. This code will fill in the drop-down portion with four items:

 if (commandName == "MyAddin.Connect.DropDownCombo_1") {     //Set the list of items the user can select.     varOut = new string[] { "Item 1", "Item 2", "Item 3", "Item 4" };     handled = true;     return; } 

The items in an MRU combo box are not filled in by calling the add-in; rather, the items are filled in by the user. The only code that you need to write to handle this command type, which is vsCommandControlTypeMRUCombo, is the QueryStatus code to indicate whether the command is enabled and the Exec code to handle selection within the drop-down. For the Exec method, the varIn parameter contains the text of the selected item when a selection is made. When Visual Studio closes, it will automatically save all the items that the user entered into the MRU combo box, and then it will restore them the next time Visual Studio is started.

Located within the samples that accompany this book, the add-in sample named CommandTypes demonstrates how to use each of these command types.

Programmatically Determining Command State

At times, you might need to programmatically determine whether a command is enabled and can be invoked, such as when you want to invoke a command by using DTE. ExecuteCommand. All commands, whether a macro command, one created by an add-in, or one built into Visual Studio, support a QueryStatus method. When you invoke the DTE. ExecuteCommand but the command isn't enabled because the QueryStatus method returned a value indicating that it isn't currently available, you'll get an exception if you're using a language supported by the .NET Framework.

To check whether a command is enabled and thus avoid this error condition, you can use the Command.IsAvailable property. For example, to make sure that the Build.BuildSolution command can be called before you invoke it, you can use the following code:

 Sub CheckAvailability()     If (DTE.Commands.Item("Build.BuildSolution").IsAvailable = True) Then         DTE.ExecuteCommand("Build.BuildSolution")     End If End Sub 

How an Add-In Command Handler Is Found

When a user invokes your command, Visual Studio needs to know which add-in handles that command so it can call the methods of the IDTCommandTarget interface. It first inspects the command name; as noted earlier, the first part of the full command name is the Namespace. ClassName of the add-in, and the remainder is the value passed for the Name parameter of the AddNamedCommand method. To locate the add-in, Visual Studio extracts the Namespace. ClassName from the command name and then checks the add-in corresponding to that Namespace.ClassName to see whether it's loaded. If it isn't, it is told to load. Visual Studio looks for the IDTCommandTarget interface (which must be implemented on the same object that implements IDTExtensibility2) on the add-in object instance, and then it calls the Exec method, passing the name of the command as the first parameter.

If, during this process, the add-in can't be found, the user is presented with the message box shown in Figure 7-1.

image from book
Figure 7-1: The message box displayed by Visual Studio when a command's add-in doesn't load

If the user clicks the Yes button, the command is removed using the Command. Delete method, and any user interface elements for that command are removed. If the add-in is loaded but the IDTCommandTarget interface can't be found on the add-in object, the command is treated as if the QueryStatus method had returned the vsCommandStatusUnsupported flag.




Working with Microsoft Visual Studio 2005
Working with Microsoft Visual Studio 2005
ISBN: 0735623155
EAN: 2147483647
Year: 2006
Pages: 100

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