Dotneteers.net
All for .net, .net for all!

LVN! Sidebar #7 - Showing a toolbar at Visual Studio startup

After publishing LearnVSXNow! Part #41 – Toolbar Layout and Persistence I got a letter from Laurie:

I am working on an isolated shell app, and already have a working tool window pane and toolbar (and am using VSXtra). I'm trying to change the visibility of the toolbar so it is visible by default, as soon as the IDE launches, just like the Standard toolbar. Right now, after installing and launching the IDE, the user has to right click on the toolbars and turn on the new toolbar.  I've looked through your posts on this, as well as other sources, but I haven't seen any documentation that says how to turn a toolbar on by default. Can you help?

To be honest, I have never tried to create a toolbar and set it visible initially. First I thought that maybe I can influence the toolbar’s initial visibility through a CommandFlag attribute in the command table (.vsct) file, but I have not found such an attribute.

My second workaround was to create a package that automatically loads at Visual Studio startup time and that package sets the toolbar visible. This second solution worked, so I share the code—and experience—with you.

I have created a simple package with one menu command using the VSPackage wizard and then changed it so that the simple command has been moved to a toolbar. The structure of the .vsct file is the following:

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

  <Extern href="vsshlids.h"/>

  <Extern href="msobtnid.h"/>

  <Commands package="guidShowInitialToolbarPkg">

    <Menus>

      <Menu guid="guidShowInitialToolbarCmdSet" id="MyToolbar" priority="0x100"

        type="Toolbar">

        <Parent guid="guidShowInitialToolbarCmdSet" id="MyToolbar" />

        <CommandFlag>DefaultDocked</CommandFlag>

        <Strings>

          <ButtonText>Sample Startup Toolbar</ButtonText>

        </Strings>

      </Menu>

    </Menus>

 

    <Groups>

      <Group guid="guidShowInitialToolbarCmdSet" id="MyMenuGroup"

        priority="0x0600">

        <Parent guid="guidShowInitialToolbarCmdSet" id="MyToolbar"/>

      </Group>

    </Groups>

   

    <Buttons>

      <Button guid="guidShowInitialToolbarCmdSet" id="cmdidMyCommand"

        priority="0x0100" type="Button">

        <Parent guid="guidShowInitialToolbarCmdSet" id="MyMenuGroup" />

        <Icon guid="guidOfficeIcon" id="msotcidClock" />

        <Strings>

          <CommandName>cmdidMyCommand</CommandName>

          <ButtonText>My Command name</ButtonText>

        </Strings>

      </Button>

    </Buttons>

  

  </Commands>

 

  <Symbols>

    <GuidSymbol name="guidShowInitialToolbarPkg"

      value="{3f005b1f-cdea-4287-8545-6606a2965136}" />

    <GuidSymbol name="guidShowInitialToolbarCmdSet"

      value="{91489787-3361-4715-bb3a-76dbd86353dc}">

      <IDSymbol name="MyMenuGroup" value="0x1020" />

      <IDSymbol name="MyToolbar" value="0x1021" />

      <IDSymbol name="cmdidMyCommand" value="0x0100" />

    </GuidSymbol>

  </Symbols>

 

</CommandTable>

I added the DefaultDocked command flag to the toolbar (it has the name “Sample Startup Toolbar”) so that it would be docked at the top of the VS window. In the next step I updated the package class:

using System;

using System.Diagnostics;

using System.Globalization;

using System.Runtime.InteropServices;

using System.ComponentModel.Design;

using EnvDTE;

using EnvDTE80;

using Microsoft.VisualStudio;

using Microsoft.VisualStudio.CommandBars;

using Microsoft.VisualStudio.Shell.Interop;

using Microsoft.VisualStudio.Shell;

 

namespace DeepDiver.ShowInitialToolbar

{

  [PackageRegistration(UseManagedResourcesOnly = true)]

  [DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")]

  [InstalledProductRegistration(false, "#110", "#112", "1.0",

    IconResourceID = 400)]

  [ProvideLoadKey("Standard", "1.0", "ShowInitialToolbar", "DeepDiver", 1)]

  [ProvideMenuResource(1000, 1)]

  [Guid(GuidList.guidShowInitialToolbarPkgString)]

  [ProvideAutoLoad("ADFC4E64-0397-11D1-9F4E-00A0C911004F")]

  public sealed class ShowInitialToolbarPackage : Package, IVsShellPropertyEvents

  {

    private uint _EventSinkCookie;

    protected override void Initialize()

    {

      base.Initialize();

 

      // --- Create command bindings

      var mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;

      if (null != mcs)

      {

        var menuCommandID = new CommandID(GuidList.guidShowInitialToolbarCmdSet,

          (int)PkgCmdIDList.cmdidMyCommand);

        var menuItem = new MenuCommand(MenuItemCallback, menuCommandID);

        mcs.AddCommand(menuItem);

      }

 

      // -- Set an event listener for shell property changes

      var shellService = GetService(typeof(SVsShell)) as IVsShell;

      if (shellService != null)

      {

        ErrorHandler.ThrowOnFailure(shellService.

          AdviseShellPropertyChanges(this, out _EventSinkCookie));

      }

    }

 

    private void MenuItemCallback(object sender, EventArgs e)

    {

      // --- Show a Message Box to prove we were here

      IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));

      Guid clsid = Guid.Empty;

      int result;

      ErrorHandler.ThrowOnFailure(uiShell.ShowMessageBox(

                 0,

                 ref clsid,

                 "ShowInitialToolbar",

                 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));

    }

 

    public int OnShellPropertyChange(int propid, object propValue)

    {

      // --- We handle the event if zombie state changes from true to false

      if ((int)__VSSPROPID.VSSPROPID_Zombie == propid)

      {

        if ((bool)propValue == false)

        {

          // --- Show the commandbar

          var dte = GetService(typeof(DTE)) as DTE2;

          var cbs = ((CommandBars)dte.CommandBars);

          CommandBar cb = cbs["Sample Startup Toolbar"];

          cb.Visible = true;

 

          // --- Unsubscribe from events

          var shellService = GetService(typeof(SVsShell)) as IVsShell;

          if (shellService != null)

          {

            ErrorHandler.ThrowOnFailure(shellService.

              UnadviseShellPropertyChanges(_EventSinkCookie));

          }

          _EventSinkCookie = 0;

        }

      }

      return VSConstants.S_OK;

    }

  }

}

The package is decorated with the ProvideAutoLoad attribute using the NoSolution UI context GUID in order to load automatically. The command bar is set visible through the automation objects (DTE) and could be set visible in the Initialize method. However, there is a design “feature” in package initialization: while the Initialize method runs for an automatically loaded package, the Shell initialization may have not been completed. Asking for a DTE object with the GetService method could result (and not just could, but does) in a null service instance. To solve this situation, we subscribe for the property changes of the Shell and watch the event when the VSSPROPID_Zombie property value changes from true to false, indicating that the Shell gets fully functional.

This is done in the OnShellPropertyChange method. The highlighted lines are the ones which display the toolbar.

I am not sure this is the best (or much easier) solution for this issue. If you have other working ideas, please, share it with me.

 


Posted Feb 03 2009, 03:18 PM by inovak
Filed under: ,

Add a Comment

(required)  
(optional)
(required)  
Remember Me?