How to add a keyboard shortcut on a Visual Studio extension?

Cesar 20 Reputation points
2024-11-05T09:21:24.5366667+00:00

I'm trying to learn how to create a keyboard shortcut on a Visual Studio 2022 extension.

I would like to get the shortcut visible and editable at Tools > Options > Environment > Keyboard

Steps i tried:

  • Addded: [ProvideMenuResource("Menus.ctmenu", 1)]
  • Created the package and the shortcut button GUIDs at public static class PackageIds
  • Created the .vsct file and set the same GUID used on the button.
  • Modified the .vsct include on the .csproj to VSCTCompile
  <ItemGroup>
    <VSCTCompile Include="VSIXPackage.vsct">
      <ResourceName>Menus.ctmenu</ResourceName>
      <SubType>Designer</SubType>
    </VSCTCompile>
  </ItemGroup>

It does compile and install, when i hit Ctrl + W i can see the cursor changing to the loading icon for a short period of time, but the Execute() functio never gets called.

What i'm missing?

VSIXPackage.cs

using Microsoft;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.ComponentModel.Design;
using System.Windows;
using Task = System.Threading.Tasks.Task;

namespace VSIX;

[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideMenuResource("Menus.ctmenu", 1)]
[Guid(VSIXPackage.PackageGuidString)]
[ProvideAutoLoad(UIContextGuids.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
public sealed class VSIXPackage : AsyncPackage
{
    public const string PackageGuidString = "f9a8aea3-f579-4816-9cb5-4ae3a5d68ef7";

    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

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

        var menuCommandID = new CommandID(PackageIds.guidVSIXPackageCmdSet, PackageIds.MyCommandId);
        var menuItem = new OleMenuCommand((s, e) => Execute(), menuCommandID);
        commandService.AddCommand(menuItem);
    }

    public static void Execute()
    {
        MessageBox.Show("Hello World!");
    }

    public static class PackageIds
    {
        public const string CmdSetGuid = "d858b0de-b9fa-49e8-a7ae-e080d04a78be"; // Match the GUID in .vsct
        public const int MyCommandId = 0x0100;
        public static readonly Guid guidVSIXPackageCmdSet = new Guid(CmdSetGuid);
    }
}

VSIXPackage.vsct

<?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"/>

	<Commands package="guidVSIXPackagePkg">

		<Groups>
			<Group guid="guidVSIXPackageCmdSet" id="ButtonGroup" priority="0x0600">
				<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS" />
			</Group>
		</Groups>

		<Buttons>
			<Button guid="guidVSIXPackageCmdSet" id="MyCommandId" priority="0x0100" type="Button">
				<Parent guid="guidVSIXPackageCmdSet" id="ButtonGroup" />
				<Strings>
					<CommandName>MyCommandId</CommandName>
					<ButtonText>MyShortcut</ButtonText>
				</Strings>
			</Button>
		</Buttons>

	</Commands>

	<KeyBindings>
		<KeyBinding guid="guidVSIXPackageCmdSet" id="MyCommandId" editor="guidVSStd97" mod1="Control" key1="W" />
	</KeyBindings>

	<Symbols>
		<!-- Define a unique GUID for your command set -->
		<GuidSymbol name="guidVSIXPackagePkg" value="{f9a8aea3-f579-4816-9cb5-4ae3a5d68ef7}" />
		<GuidSymbol name="guidVSIXPackageCmdSet" value="{d858b0de-b9fa-49e8-a7ae-e080d04a78be}">
			<IDSymbol name="ButtonGroup" value="0x1020" />
			<IDSymbol name="MyCommandId" value="0x0100"/>
		</GuidSymbol>
	</Symbols>
</CommandTable>

VSIX.csproj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <MinimumVisualStudioVersion>17.0</MinimumVisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
  </PropertyGroup>
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
    <ProjectGuid>{C28A9253-3A5D-41ED-A3F9-FAA709A70BD9}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>VSIX</RootNamespace>
    <AssemblyName>VSIX</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <LangVersion>12</LangVersion>
    <GeneratePkgDefFile>true</GeneratePkgDefFile>
    <UseCodebase>true</UseCodebase>
    <IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer>
    <IncludeDebugSymbolsInVSIXContainer>false</IncludeDebugSymbolsInVSIXContainer>
    <IncludeDebugSymbolsInLocalVSIXDeployment>false</IncludeDebugSymbolsInLocalVSIXDeployment>
    <CopyBuildOutputToOutputDirectory>true</CopyBuildOutputToOutputDirectory>
    <CopyOutputSymbolsToOutputDirectory>true</CopyOutputSymbolsToOutputDirectory>
    <StartAction>Program</StartAction>
    <StartProgram Condition="'$(DevEnvDir)' != ''">$(DevEnvDir)devenv.exe</StartProgram>
    <StartArguments>/rootsuffix Exp</StartArguments>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="VSIXPackage.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="source.extension.vsixmanifest">
      <SubType>Designer</SubType>
    </None>
  </ItemGroup>
  <ItemGroup>
    <Reference Include="PresentationFramework" />
    <Reference Include="System" />
    <Reference Include="System.Design" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.0.32112.339" ExcludeAssets="runtime" />
    <PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.12.2069" />
    <PackageReference Include="System.Drawing.Common">
      <Version>5.0.2</Version>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <VSCTCompile Include="VSIXPackage.vsct">
      <ResourceName>Menus.ctmenu</ResourceName>
      <SubType>Designer</SubType>
	</VSCTCompile>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

The full extension project can be found here on GitHub.

Visual Studio
Visual Studio
A family of Microsoft suites of integrated development tools for building applications for Windows, the web and mobile devices.
5,306 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,152 questions
Visual Studio Extensions
Visual Studio Extensions
Visual Studio: A family of Microsoft suites of integrated development tools for building applications for Windows, the web and mobile devices.Extensions: A program or program module that adds functionality to or extends the effectiveness of a program.
235 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Matt Lacey 791 Reputation points MVP
    2024-11-05T12:27:06.9633333+00:00

    Docs for creating a shortcut are at https://learn.microsoft.com/en-us/visualstudio/extensibility/binding-keyboard-shortcuts-to-menu-items?view=vs-2022 but it looks like you've got this.

    When a shortcut seems to be set up ok but doesn't seem to do anything, in my experience, it's often because there are multiple commands set to use the same shortcut. However, VS does nothing to indicate that this may be the case.

    See also: https://developercommunity.visualstudio.com/t/Detecting-and-reporting-conflicting-hotk/10576876

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.