Commands

Commands are user-invokable actions in Visual Studio. They appear in menus, toolbars, context menus, and can be executed via the Command Window.

Command Basics

With Community Toolkit

The simplest way to create a command:

[Command(PackageIds.MyCommand)]
internal sealed class MyCommand : BaseCommand<MyCommand>
{
    protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
    {
        await VS.MessageBox.ShowAsync("Hello, World!");
    }
}

Register commands in your package:

protected override async Task InitializeAsync(...)
{
    await this.RegisterCommandsAsync();
}

Traditional Approach

Without the Community Toolkit:

public sealed class MyPackage : AsyncPackage
{
    protected override async Task InitializeAsync(...)
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync();

        var commandService = await GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;

        var cmdId = new CommandID(PackageGuids.guidMyPackageCmdSet, PackageIds.MyCommand);
        var menuItem = new MenuCommand(Execute, cmdId);
        commandService.AddCommand(menuItem);
    }

    private void Execute(object sender, EventArgs e)
    {
        ThreadHelper.ThrowIfNotOnUIThread();
        // Command logic
    }
}

Command Table (.vsct)

The Visual Studio Command Table defines command placement and appearance:

<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable">
  <Extern href="stdidcmd.h"/>
  <Extern href="vsshlids.h"/>
  <Include href="KnownImageIds.vsct"/>

  <Commands package="guidMyPackage">
    <Groups>
      <Group guid="guidMyPackageCmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
      </Group>
    </Groups>

    <Buttons>
      <Button guid="guidMyPackageCmdSet" id="MyCommand" priority="0x0100" type="Button">
        <Parent guid="guidMyPackageCmdSet" id="MyMenuGroup"/>
        <Icon guid="ImageCatalogGuid" id="StatusInformation"/>
        <CommandFlag>IconIsMoniker</CommandFlag>
        <Strings>
          <ButtonText>My Command</ButtonText>
        </Strings>
      </Button>
    </Buttons>
  </Commands>

  <Symbols>
    <GuidSymbol name="guidMyPackage" value="{your-package-guid}"/>
    <GuidSymbol name="guidMyPackageCmdSet" value="{your-cmdset-guid}">
      <IDSymbol name="MyMenuGroup" value="0x1020"/>
      <IDSymbol name="MyCommand" value="0x0100"/>
    </GuidSymbol>
  </Symbols>
</CommandTable>

Command Placement

Place commands in main menus:

<Group guid="guidMyPackageCmdSet" id="MyMenuGroup" priority="0x0600">
  <!-- Tools menu -->
  <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
</Group>

Common menu IDs:

MenuID
FileIDM_VS_MENU_FILE
EditIDM_VS_MENU_EDIT
ViewIDM_VS_MENU_VIEW
ProjectIDM_VS_MENU_PROJECT
BuildIDM_VS_MENU_BUILD
DebugIDM_VS_MENU_DEBUG
ToolsIDM_VS_MENU_TOOLS
ExtensionsIDM_VS_MENU_EXTENSIONS

Context Menu Commands

Add to editor context menu:

<Group guid="guidMyPackageCmdSet" id="EditorContextGroup" priority="0x0600">
  <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>
</Group>

Common context menus:

ContextID
Code EditorIDM_VS_CTXT_CODEWIN
Solution Explorer (Solution)IDM_VS_CTXT_SOLNNODE
Solution Explorer (Project)IDM_VS_CTXT_PROJNODE
Solution Explorer (Item)IDM_VS_CTXT_ITEMNODE
Solution Explorer (Folder)IDM_VS_CTXT_FOLDERNODE

Toolbar Commands

Create a custom toolbar:

<Menus>
  <Menu guid="guidMyPackageCmdSet" id="MyToolbar" type="Toolbar">
    <Strings>
      <ButtonText>My Toolbar</ButtonText>
    </Strings>
  </Menu>
</Menus>

<Groups>
  <Group guid="guidMyPackageCmdSet" id="MyToolbarGroup" priority="0x0000">
    <Parent guid="guidMyPackageCmdSet" id="MyToolbar"/>
  </Group>
</Groups>

Command State

Enabling/Disabling Commands

With Community Toolkit:

[Command(PackageIds.MyCommand)]
internal sealed class MyCommand : BaseCommand<MyCommand>
{
    protected override void BeforeQueryStatus(EventArgs e)
    {
        // Only enable when a document is open
        Command.Enabled = VS.Documents.GetActiveDocumentViewAsync().Result?.TextView != null;
    }

    protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
    {
        // Command logic
    }
}

Traditional approach:

var menuItem = new OleMenuCommand(Execute, cmdId);
menuItem.BeforeQueryStatus += (s, e) =>
{
    ThreadHelper.ThrowIfNotOnUIThread();
    var cmd = (OleMenuCommand)s;
    cmd.Enabled = /* your condition */;
};

Visibility

Control when commands appear:

protected override void BeforeQueryStatus(EventArgs e)
{
    Command.Visible = /* show/hide condition */;
    Command.Enabled = /* enable/disable condition */;
}

In VSCT, use visibility constraints:

<VisibilityConstraints>
  <VisibilityItem guid="guidMyPackageCmdSet" id="MyCommand"
                  context="UICONTEXT_SolutionExists"/>
</VisibilityConstraints>

Command Flags

Modify command behavior with flags:

<Button ...>
  <CommandFlag>DynamicVisibility</CommandFlag>
  <CommandFlag>DefaultInvisible</CommandFlag>
  <CommandFlag>TextChanges</CommandFlag>
  ...
</Button>
FlagDescription
DynamicVisibilityVisibility can change at runtime
DefaultInvisibleHidden by default
DefaultDisabledDisabled by default
TextChangesButton text can change
IconAndTextShow both icon and text
FixMenuControllerFor menu controller behavior

Keyboard Shortcuts

In VSCT

<KeyBindings>
  <KeyBinding guid="guidMyPackageCmdSet" id="MyCommand"
              editor="guidVSStd97"
              key1="K" mod1="Control"
              key2="M" mod2="Control"/>
</KeyBindings>

This creates a Ctrl+K, Ctrl+M chord binding.

Note

Users can customize shortcuts in Tools > Options > Keyboard. Your default bindings may be overridden.

Dynamic Commands

Changing Text

[Command(PackageIds.MyCommand)]
internal sealed class MyCommand : BaseCommand<MyCommand>
{
    private int _count = 0;

    protected override void BeforeQueryStatus(EventArgs e)
    {
        Command.Text = $"Click Count: {_count}";
    }

    protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
    {
        _count++;
    }
}

Requires the TextChanges command flag in VSCT.

Checked State

protected override void BeforeQueryStatus(EventArgs e)
{
    Command.Checked = _isFeatureEnabled;
}

Command Parameters

Commands can receive parameters:

protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
{
    // Get input from command window: >MyExtension.MyCommand "parameter"
    var input = e.InValue as string;

    if (!string.IsNullOrEmpty(input))
    {
        await VS.MessageBox.ShowAsync($"Parameter: {input}");
    }
}

Complete Example

[Command(PackageIds.FormatDocumentCommand)]
internal sealed class FormatDocumentCommand : BaseCommand<FormatDocumentCommand>
{
    protected override void BeforeQueryStatus(EventArgs e)
    {
        // Only show for text files
        var doc = VS.Documents.GetActiveDocumentViewAsync().Result;
        Command.Visible = doc?.FilePath?.EndsWith(".txt") == true;
        Command.Enabled = doc?.TextView != null;
    }

    protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
    {
        var docView = await VS.Documents.GetActiveDocumentViewAsync();
        if (docView?.TextView == null) return;

        // Get the text buffer
        var textBuffer = docView.TextView.TextBuffer;

        using (var edit = textBuffer.CreateEdit())
        {
            var text = edit.Snapshot.GetText();
            var formatted = FormatText(text);
            edit.Replace(0, text.Length, formatted);
            edit.Apply();
        }

        await VS.StatusBar.ShowMessageAsync("Document formatted!");
    }

    private string FormatText(string text)
    {
        // Your formatting logic
        return text.Trim();
    }
}

Next Steps

Learn about Tool Windows to create dockable UI panels.