To demonstrate how to add functions to our package we are going to create a package with a simple menu function (command). We start with a new Visual Studio Integration Package as in the case of an empty package, this time we name it SimpleCommand. When the wizard starts we select C# and let the wizard generate a new key for the project assembly. On the Basic VSPackage Information page we provide the following parameters:

On the next wizard page we check the Menu Command box to create a simple menu command within our package:

When moving to the next page, the wizards ask for the display name of the new menu command and the name of identifier representing the command. Please specify them as indicated on the picture:

On the last page of the wizard we can ask for Integration Test Project and Unit Test Project. Please uncheck both options and click on Finish. In a few seconds the wizard creates the source files of the project.
Build and run the SimpleCommand project. When the Visual Studio Experimental Hive starts, you can discover the menu command of our package on the Tools menu:

Click on My First Command! Our newly created package executes the command and displays a message box proving the package works.
What is inside?
Closing the VS Experimental Hive and having a look at the source files, we can discover two new source files the EmptyPackage project does not have: PkgCmdID.cs and SimpleCommand.vsct. The first embeds the identifier for “My First Command”:
namespace MyCompany.SimpleToolWindow
{
static class PkgCmdIDList
{
public const uint cmdidMyFirstCommand = 0x100;
};
}
The identifier has the name we provided when filling the VS package wizard pages. The value of 0x100 has been generated by the wizard.
The second file, SimpleCommand.vsct seems more “exciting” as it contains XML content that recalls us something for UI declaration. The original file contains many comments but I omit those for the sake of readability:
<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/
CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<Extern href="stdidcmd.h" mce_href="stdidcmd.h"/>
<Extern href="vsshlids.h" mce_href="vsshlids.h"/>
<Extern href="msobtnid.h" mce_href="msobtnid.h"/>
<Commands package="guidSimpleCommandPkg">
<Groups>
<Group guid="guidSimpleCommandCmdSet" id="MyMenuGroup" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
</Group>
</Groups>
<Buttons>
<Button guid="guidSimpleCommandCmdSet" id="cmdidMyFirstCommand"
priority="0x0100" type="Button">
<Parent guid="guidSimpleCommandCmdSet" id="MyMenuGroup" />
<Icon guid="guidImages" id="bmpPic1" />
<Strings>
<CommandName>cmdidMyFirstCommand</CommandName>
<ButtonText>My First Command</ButtonText>
</Strings>
</Button>
</Buttons>
<Bitmaps>
<Bitmap guid="guidImages" href="Resources\Images_32bit.bmp" mce_href="Resources\Images_32bit.bmp"
usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"/>
</Bitmaps>
</Commands>
<Symbols>
<GuidSymbol name="guidSimpleCommandPkg"
value="{2291da24-92e5-4ea4-bdb7-72a9b5ac7d59}" />
<GuidSymbol name="guidSimpleCommandCmdSet"
value="{a982b107-4ad4-437e-b2bc-cdf2708aa376}">
<IDSymbol name="MyMenuGroup" value="0x1020" />
<IDSymbol name="cmdidMyFirstCommand" value="0x0100" />
</GuidSymbol>
<GuidSymbol name="guidImages"
value="{5c3faf04-8190-48c4-a6e9-71f04f1848e5}" >
<IDSymbol name="bmpPic1" value="1" />
<IDSymbol name="bmpPic2" value="2" />
<IDSymbol name="bmpPicSearch" value="3" />
<IDSymbol name="bmpPicX" value="4" />
<IDSymbol name="bmpPicArrows" value="5" />
</GuidSymbol>
</Symbols>
</CommandTable>
This .vsct file is a new XML format in the Visual Studio 2008 SDK. The .vsct extension (as I guess) stands for Visual Studio Command Table, it defines resources used by Visual Studio to provide user interface for the commands of our package. When you see the properties of the file, you can see Build Action is set to VSCTCompile. During the build of the package the .vsct file is compiled into a binary resource and added to the VSPackage.resx resource with the ID of decimal 1000. When regpkg.exe registers our package, the resource representation of the .vsct file is registered for Visual Studio. When VS starts, it do not have to load our package, it simply uses the registered resource to update its user interface (displaying menu and toolbar items)
I think the time has not come to take a deep dive into the details of the .vsct file, but there are some important details I want to tell you. The .vsct tells a lot about how VS is architected, how it solves the coupling of functions (commands) and user interface elements.
The commands (action to execute) are separated from the user interface element triggering the command. The same command can be assigned to different menus and toolbars; they will use the same action.
Commands used together can be grouped and simply merged into existing menus by using the command group representation. It is much easier then coupling commands with hosting menus one-by-one.
Elements are identified symbols rather than using explicit values. This makes the coupling less error-prone: the value of symbols must be defined only once, and the VSCT compiler can check for mistyping.
The full enumeration is much longer! I mentioned only these aspects since I think these are the most essential ones to establish a picture about why .vsct looks like as above.
How does it work?
Right now we see how the menu item of our “My First Command” is represented and we can imagine how Visual Studio represents it. But we have to answer the most important question: How does Visual Studio call the action to be executed when we select the menu item representing the command? The answer is within the SimpleCommandPackage.cs file, so we are going to examine a few details.
It the article about the EmptyPackage we discovered that the package class has attribute decorations used by the regpkg.exe to register our package. The SimpleCommandPackage type has a new decoration, namely the ProvideMenuResource attribute:
...
[ProvideMenuResource(1000, 1)]
...
public sealed class SimpleCommandPackage : Package
{
...
}
The ProvideMenuResource attribute has two parameters. The first is the so-called resourceID. This value should be set to 1000 as the VSCT compiler uses the value of 1000 by default when adding the binary representation of the .vsct file to the VSPackage resource. The second parameter is called versionID and it plays important role in the caching mechanism of resources. Instead going into details right now, please keep in mind that ProvideMenuResource attribute allows the package to register its menu resources.
To be able to execute a command triggered by the user, we need two steps:
We need to write a command handler that represents the activity to execute when the command is triggered.
We must tell Visual Studio to use our command handler
The command handler should display a message box. The handler itself is a simple private method with the well-known event handler method parameters. However, its body is more than a simple Messagebox.Show method call.
private void MenuItemCallback(object sender, EventArgs e)
{
IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
Guid clsid = Guid.Empty;
int result;
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(
uiShell.ShowMessageBox(
0,
ref clsid,
"SimpleCommand",
string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()",
this.ToString()),
string.Empty,
0,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
OLEMSGICON.OLEMSGICON_INFO,
0, // false
out result)
);
}
To access functions Visual Studio provides for add-ins and packages, we must use explicitly defined services. In our case, we use the IVsUIShell service that provides a few dozen methods related to UI centric windowing tasks. To obtain such a service object instance we must call the GetService method of the Package class with the SVsUIShell type.
Through this service instance we can call the ShowMessageBox method to display a Visual Studio message box. This time I do not go into the explanation of ShowMessageBox’s arguments, please believe it displays a message with an OK button.
Visual Studio uses COM technology behind the scenes. All implemented services can be accessed through COM interoperability classes. As you know, COM does not provide a runtime exception handling mechanism; it simply signs exceptions through the return value of a method call. When calling a COM method we manually should check the result code for exceptions and raise one if happens. The ThrowOnFailure method of the Microsoft.VisualStudio.ErrorHandler class does this work for us.
So, MenuItemCallback method executes our command. Somehow we must tell Visual Studio to use this method when the user clicks on the “My First Command” item. The “magic” is done in the Initialize method that is called when our package is loaded and sited into Visual Studio:
protected override void Initialize()
{
Trace.WriteLine (string.Format(CultureInfo.CurrentCulture,
"Entering Initialize() of: {0}", this.ToString()));
base.Initialize();
OleMenuCommandService mcs =
GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if ( null != mcs )
{
CommandID menuCommandID = new CommandID(GuidList.guidSimpleCommandCmdSet,
(int)PkgCmdIDList.cmdidMyFirstCommand);
MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
mcs.AddCommand( menuItem );
}
}
Since we override the base Initialize method, before doing any activity we call the base implementation. A command can be executed from many locations: from the main menu, from a toolbar or from a number of context menus. All of those menu items are bound to the appropriate command with the corresponding Command ID. Here, we bound the Command ID with the handler method.
We obtain an instance of IMenuCommandService using the GetService method. For this call GetService provides an OleMenuCommandService instance that has about twenty methods and a few properties. We use the AddCommand method to bind a command handler (in this case MenuItemCallback) with a Command ID (represented by menuCommandId).
So, when the user clicks the “My First Command” menu item, it is bound to the command identified by GuidList.guidSimpleCommandCmdSet and PkgCmdIDList.cmdidMyFirstCommand. This Command ID is associated with the MenuItemCallback method that displays the message box.
Where we are?
We added a simple menu command to our package to display a message box. To add this command we did the following things:
Created a .vsct file to describe the resources (menu items, commands and related symbols. This file was compiled by the VSCT compiler into a binary resource that was merged into our VSPackage resources. A new decoration, ProvideMenuResource attribute was added to our package to register this new resource.
Implemented a method displaying the message box. This method used the SVsUIShell service to interact with Visual Studio in order to display the message.
In the package initialization we added code that bound the command with the handler method. This used the OleMenuCommandService to interact with the IDE to associate the command with its handler code.
Next time we add our own user interface to the package in form of a tool window.
Posted
Jan 06 2008, 06:24 PM
by
inovak