Search code examples
c#visual-studio-extensionsvsix

How to add menu command in Debug > Windows menu showing only in breakpoint?


I'm creating a VSIX that contains a Tool Window. I would like to move the menu item that opens it into Debug > Windows, instead of View > Other Windows. I would also like to show it only when the target project is in debug mode (for example like the Call Stack menu item).

I managed to show the menu item in the Debug menu, but not in Debug > Windows. In my .vsct I've used:

<!-- ... -->
<Extern href="VSDbgCmd.h"/>
<Extern href="VsDebugGuids.h"/>
<!-- ... -->
<Parent guid="guidVSDebugGroup" id="IDM_DEBUG_WINDOWS"/>
<!-- ... -->

Regarding showing it only in debug mode, I don't have any idea and I couldn't find anything useful.

So my question is: how do I make my menu item behave like Call Stack menu item (in Debug > Window and shown only in debug mode)?


Solution

  • While there is a UIContext for "debugging", there doesn't appear to be one for when the debugger actually enteres break mode. (Hint, I used https://marketplace.visualstudio.com/items?itemName=PaulHarrington.ComponentDiagnostics extension to monitor the active UI Contexts)

    So you'll need to add DynamicVisibility and DefaultInvisible command flags to your button:

      <Extern href="stdidcmd.h"/>
      <Extern href="vsshlids.h"/>
      <Extern href="VSDbgCmd.h"/>
      <Extern href="VsDebugGuids.h"/>
      <Commands package="guidIaxToolWindowPackage">
        <Buttons>
          <Button guid="guidIaxToolWindowPackageCmdSet" id="IaxToolWindowCommandId" priority="0x0002" type="Button">
            <Parent guid="guidVSDebugGroup" id="IDG_DEBUG_WINDOWS_GENERAL"/>
            <Icon guid="guidImages" id="bmpPic1" />
            <CommandFlag>DynamicVisibility</CommandFlag>
            <CommandFlag>DefaultInvisible</CommandFlag>
            <Strings>
              <ButtonText>Show IaxToolWindow</ButtonText>
            </Strings>
          </Button>
        </Buttons>
    

    Then ensure your package is loaded whenever the debugger attaches to or launches a process, by decorating your AsyncPackage class with the ProvideAutoLoad attribute below:

    namespace IaxToolwindow
    {
        // use Debugging UI Context to ensure package get loaded when debugging starts
        [ProvideAutoLoad(VSConstants.UICONTEXT.Debugging_string, PackageAutoLoadFlags.BackgroundLoad)]
        [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
        [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About
        [ProvideMenuResource("Menus.ctmenu", 1)]
        [ProvideToolWindow(typeof(IaxToolWindow))]
        [Guid(IaxToolWindowPackage.PackageGuidString)]
        public sealed class IaxToolWindowPackage : AsyncPackage
        {
            public const string PackageGuidString = "256ca1a6-6b6d-4329-a7f9-15ee5bbf8114";
    

    Then add a BeforeQueryStatus command handler for your menu command, to make the menu item visible when IVSDebugger.GetMode returns DBGMODE_BREAK. For example:

    private IaxToolWindowCommand(AsyncPackage package, OleMenuCommandService commandService)
    {
        this.package = package ?? throw new ArgumentNullException(nameof(package));
        commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
    
        var menuCommandID = new CommandID(CommandSet, CommandId);
        var menuItem = new OleMenuCommand(this.Execute, menuCommandID);
        menuItem.BeforeQueryStatus += IAxToolWindowCommand_BeforeQueryStatus;
        commandService.AddCommand(menuItem);
    }
    
    private void IAxToolWindowCommand_BeforeQueryStatus(object sender, EventArgs e)
    {
        ThreadHelper.JoinableTaskFactory.Run(async delegate 
        {
            await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
            OleMenuCommand menuItem = (OleMenuCommand)sender;
            IVsDebugger vsDebugger = await ServiceProvider.GetServiceAsync(typeof(IVsDebugger)) as IVsDebugger;
            Assumes.Present(vsDebugger);
            DBGMODE[] dbgmode = new DBGMODE[1];
            int hr = vsDebugger.GetMode(dbgmode);
            // show menu item when debugger is in break mode
            menuItem.Visible = (dbgmode[0] == DBGMODE.DBGMODE_Break);
        });
    }
    

    This was built off a wizard generated AsyncToolwindow project. But note I changed up the MenuCommand to OleMenuCommand, so we can add and use a BeforeQueryStatus handler to control the visibility of the menu item.

    The ProvideAutoLoad is used to ensure the package is loaded whenever a debug session is started, so it's started in time to make the menu item visible (when the debugger is in break mode).

    If there had been a UIContext guid activated when the debugger entered break mode, we could have just added a VisbilityConstraint to the .vsct. But given the absence of a UIContext specific to break mode, you'll need to do something similar to the above, to make the menu item visible only when in break mode.

    Sincerely,