One of the great features of the DSL Tools is the possibility of integrating custom entries in the designer context menu. However, it is not yet a quick and easy process to set up. I found how to do it in Sebastian Talamoni’s blog (found via this blog). When you need to add a new menu entry, you have to go through the same tedious process of adding the new command and identifiers to the Commands.vsct file, defining the constants in the CommandSet class and registering the command.
So I thought, there must be a way to do more with less effort. The proposed solution relies on these parts:
- A model which specifies the available menu entries.
- A template which generates the Commands.vsct file content based on the model.
- A template which generates a partial CommandSet class, containing the command definition and registration.
Here’s how to get everything running:
Step 1: Create the DSL project.
Open VS2008, choose File | New Project, select new Domain-Specific Language Designer and click OK.

Select Next on each step, making sure a default DSL project is created.
Step 2: Add the menu entry model.
After the solution is created, go to the DslPackage project. Add a new folder named CustomCode, with a subfolder named CommandSet. Add a new file to this folder named CommandSetConfig.tt. Go to the file properties and remove the “Custom Tool” field value. We do not want to generate code here, this file will only contain our menu entry model. I used the example you can find in Sebastian’s post:
<#
string languageNamespace = "Company.Language1";
string languageName = "Language1";
// List of commands
Collection<Dictionary<string, string>> commands = new Collection<Dictionary<string, string>>();
// Command ImportDbSchema
Dictionary<string, string> command = new Dictionary<string, string>();
command.Add("CanonicalName", "cmdImportDbSchema");
command.Add("ButtonText", "Import from Database Schema");
command.Add("ToolTipText", "Use this option to create the domain from an existing Database Schema");
command.Add("Id", "{EC120F9A-9E7F-469d-8D61-F4E2A97E5725}");
commands.Add(command);
// Command ImportClasses
command = new Dictionary<string, string>();
command.Add("CanonicalName", "cmdImportClasses");
command.Add("ButtonText", "Import from Existing Classes");
command.Add("ToolTipText", "");
command.Add("Id", "{EC120F9A-9E7F-469d-8D61-F4E2A97E5726}");
commands.Add(command);
#>
Step 3: Generate the Commands.vsct file contents.
Add a new file to the CommandSet folder named Commands.tt. Edit the file and add this content:
<#@ template inherits=”Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" debug="true"#>
<#@ output extension=".vsct" #>
<#@ import namespace = "System.Collections.Generic" #>
<#@ import namespace = "System.Collections.ObjectModel" #>
<#@ include file="CommandSetConfig.tt" #>
<?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">
<!– –>
<!– This file contains custom command definitions. –>
<!– –>
<!– NOTE: Each time commands are added or changed, the "version" parameter to the –>
<!– ProvideMenuResource attribute in Shell\Package.tt should be incremented. –>
<!– This causes Visual Studio to re-merge the menu definitions for the package. –>
<!– Otherwise, changes won’t take effect until the next time devenv /setup is run. –>
<!– –>
<Extern href="stdidcmd.h"/>
<Extern href="vsshlids.h"/>
<Extern href="msobtnid.h"/>
<Extern href="virtkeys.h"/>
<Extern href="DSLToolsCmdID.h"/>
<Include href="GeneratedCode\GeneratedVsct.vsct"/>
<Commands package="guidPkg">
<Buttons>
<#
foreach (Dictionary<string, string> element in commands)
{
#>
<Button guid="<#= element["CanonicalName"] #>GUID" id="<#= element["CanonicalName"] #>ID" priority="0×0902" type="Button">
<Parent guid="guidCmdSet" id="grpidContextMain" />
<Strings>
<CanonicalName><#= element["CanonicalName"] #></CanonicalName>
<ButtonText><#= element["ButtonText"] #></ButtonText>
<ToolTipText><#= element["ToolTipText"] #></ToolTipText>
</Strings>
</Button>
<#
}
#>
</Buttons>
</Commands>
<Symbols>
<#
int index = 810;
foreach (Dictionary<string, string> element in commands)
{
#>
<GuidSymbol name="<#= element["CanonicalName"] #>GUID" value="<#= element["Id"] #>">
<IDSymbol name="<#= element["CanonicalName"] #>ID" value="0x<#= index #>" />
</GuidSymbol>
<#
index++;
}
#>
</Symbols>
</CommandTable>
Step 4: Generate the CommandSet partial class.
Now, we will generate a partial class which defines the menu entry constants and registers the commands. Add a new file to the same folder named CommandSet.tt. Edit the file and add this content:
<#@ template inherits=”Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" debug="true"#>
<#@ output extension=".cs" #>
<#@ import namespace = "System.Collections.Generic" #>
<#@ import namespace = "System.Collections.ObjectModel" #>
<#@ include file="CommandSetConfig.tt" #>
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Design;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Shell;
namespace <#= languageNamespace #>
{
/// <summary>
/// Custom code for <#= languageName #>CommandSet.
/// </summary>
internal partial class <#= languageName #>CommandSet : <#= languageName #>CommandSetBase
{
#region Constants
<#
int index = 810;
foreach (Dictionary<string, string> element in commands)
{
string canonicalName = element["CanonicalName"];
string constName = canonicalName.Substring(0, 1).ToUpper() + canonicalName.Substring(1);
#>
/// <summary>
/// <#= constName #> commmand identifier.
/// </summary>
private const int <#= constName #>ID = 0x<#= index #>;
<#
index++;
}
#>
#endregion
#region Members
<#
foreach (Dictionary<string, string> element in commands)
{
string canonicalName = element["CanonicalName"];
#>
/// <summary>
/// Enable related task commmand unique identifier.
/// </summary>
private Guid <#= canonicalName #>GUID = new Guid("<#= element["Id"] #>");
<#
}
#>
#endregion
#region Protected Methods
/// <summary>
/// Provide the menu commands that this command set handles.
/// </summary>
/// <returns>A list of commands.</returns>
protected override IList<MenuCommand> GetMenuCommands()
{
// Execute base
IList<MenuCommand> commands = base.GetMenuCommands();
<#
foreach (Dictionary<string, string> element in commands)
{
string canonicalName = element["CanonicalName"];
string constName = canonicalName.Substring(0, 1).ToUpper() + canonicalName.Substring(1);
#>
// Add the new menu command (Enable related task)
DynamicStatusMenuCommand <#= canonicalName #> =
new DynamicStatusMenuCommand(
new EventHandler(this.OnPopUpMenuDisplayAction),
new EventHandler(this.OnPopUpMenuClick),
new CommandID(this.<#= canonicalName #>GUID, <#= constName #>ID));
commands.Add(<#= canonicalName #>);
<#
}
#>
// Result
return commands;
}
#endregion
}
}
Step 5: Complete the CommandSet partial class.
After saving the file, you should now have a few entries in the Error List. Now you need to complete the generated partial class with the variable part of the menu model. Add the file Language1CommandSet.cs file to the CustomCode folder and add the handlers for the menu visibility and click actions:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Design;
namespace Company.Language1
{
/// <summary>
/// Custom code for Language1CommandSet.
/// </summary>
internal partial class Language1CommandSet
{
#region Event Handlers
/// <summary>
/// Occurs when one of the menu items is being displayed.
/// </summary>
/// <param name=”sender”>Sender of this event.</param>
/// <param name=”e”>Event arguments.</param>
internal void OnPopUpMenuDisplayAction(object sender, EventArgs e)
{
// Control availability of the command
MenuCommand command = sender as MenuCommand;
if (command != null)
{
// Check what command it is
if (command.CommandID.Guid.Equals(this.cmdImportDbSchemaGUID))
{
command.Enabled = true;
command.Visible = true;
}
else if (command.CommandID.Guid.Equals(this.cmdImportClassesGUID))
{
command.Enabled = true;
command.Visible = true;
}
}
}
/// <summary>
/// Occurs when one of the menu items is clicked.
/// </summary>
/// <param name=”sender”>Sender of this event.</param>
/// <param name=”e”>Event arguments.</param>
internal void OnPopUpMenuClick(object sender, EventArgs e)
{
// Get the current menu
MenuCommand command = sender as MenuCommand;
if (command != null)
{
// Check what command it is
if (command.CommandID.Guid.Equals(this.cmdImportDbSchemaGUID))
{
this.ImportDbSchema();
}
else if (command.CommandID.Guid.Equals(this.cmdImportClassesGUID))
{
this.ImportClasses();
}
}
}
#endregion
#region Private Methods
private void ImportDbSchema()
{
// TODO
}
private void ImportClasses()
{
// TODO
}
#endregion
}
}
Your project should now look like this:

Step 6: Test it.
Transform All Templates. Copy the contents of the generated Commands.vsct file into the file with the same name located in the DslPackage project root.
Run the solution. Right-click the Designer surface. The two commands defined in the model are available:

Next steps:
- Simplify and improve the menu entry model configuration. Maybe by creating an EntryDefinition class with a custom constructor.
- Find a way to update the project Commands.vsct directly without having to copy the contents from one file to the other.