VSX Arcana

Covering the many topics of Visual Studio Extensibility

How to change solution icon in the Visual Studio Isolated Shell

This question posted to the VS Extensibility Forum recently caught my attention. Initially, I initially suspected this would have a simple answer, but it turns out the shell pulls this icon directly from a resource in msenv.dll.

After examining the underlying IVsHierarchy implementation of the root solution node, it looked like the icon could be changed by setting the VSHPROPID_IconHandle property on the solution node. The next question to solve was “when” to set this. You cannot set the property from the shell stub, and package assemblies are typically loaded on demand.

So the best solution is to force one of your isolated shell packages to load at startup, by applying a couple of ProvideAutoLoad attributes one of your VSPackage objects. Specifically:

    [ProvideAutoLoad(UIContextGuids.SolutionExists)]     
[ProvideAutoLoad(UIContextGuids.NoSolution)]

Note, this is generally frowned upon in most instances. To keep startup time and memory footprint to a minimum, packages should be loaded on demand. Especially when targeting the integrated shell.  But this is an isolated shell scenario, and it’s certain you’ll have to have at least one package loaded.

Now that we have the package configured to load at startup, we could just set the property from the package’s overridden Initialize method. But that would be a mistake. Various shell services are also implemented with packages, and there is no guarantee to the order in which packages are loaded. So we need to wait until all prescribed packages are loaded.

To do that, we need to wait until the VSSPROPID_ShellInitialized property is set. And this can be done by implementing IVsShellPropertyEvents and calling IVsShell.AdviseShellPropertyChanges.

Below, is a slightly modified AboutBoxPackage (initially generated with the isolated shell project wizard), that I created as a quick proof of concept to verify that the solution icon can actually be changed for an isolated shell application.

 

Code Snippet
using System;

using System.Diagnostics;

using System.Drawing;

using System.Globalization;

using System.Reflection;

using System.Runtime.InteropServices;

using System.ComponentModel.Design;

using Microsoft.Win32;

using Microsoft.VisualStudio;

using Microsoft.VisualStudio.Shell.Interop;

using Microsoft.VisualStudio.OLE.Interop;

using Microsoft.VisualStudio.Shell;



namespace MyShellony.AboutBoxPackage

{

    [PackageRegistration(UseManagedResourcesOnly = true)]

    [ProvideMenuResource("Menus.ctmenu", 1)]

    [Guid(GuidList.guidAboutBoxPackagePkgString)]

    // ensure package is loaded on startup

    [ProvideAutoLoad(UIContextGuids.SolutionExists)]

    [ProvideAutoLoad(UIContextGuids.NoSolution)]

    public sealed class AboutBoxPackage : Package, IVsShellPropertyEvents

    {

        private static Icon solutionIcon;

        private uint propChangeCookie;



        public AboutBoxPackage()

        {

            solutionIcon = new Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream("MyShellony.AboutBoxPackage.Resources.SolutionIcon.ico"));

        }



        protected override void Initialize()

        {

            base.Initialize();



            // watch for VSSPROPID property changes

            IVsShell vsShell = (IVsShell)GetService(typeof(SVsShell));

            vsShell.AdviseShellPropertyChanges(this, out propChangeCookie);



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

            if (null != mcs)

            {

                CommandID menuCommandID = new CommandID(GuidList.guidAboutBoxPackageCmdSet, (int)PkgCmdIDList.cmdidHelpAbout);

                MenuCommand menuItem = new MenuCommand(ShowAboutBox, menuCommandID);

                mcs.AddCommand(menuItem);

            }

        }



        // when shell is fully initialized, set the icon on the solution explorer's root node.

        public int OnShellPropertyChange(int propid, object var)

        {

            if (propid == (int)__VSSPROPID4.VSSPROPID_ShellInitialized && Convert.ToBoolean(var) == true)

            {



                // set VSHPROPID_IconHandle for Solution Node

                IVsHierarchy solHierarchy = (IVsHierarchy)GetService(typeof(SVsSolution));

                int hr = solHierarchy.SetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_IconHandle, solutionIcon.Handle);



                // stop listening for shell property changes

                IVsShell vsShell = (IVsShell)GetService(typeof(SVsShell));

                hr = vsShell.UnadviseShellPropertyChanges(propChangeCookie);

                propChangeCookie = 0;

            }

            return VSConstants.S_OK;

        }



        private void ShowAboutBox(object sender, EventArgs e)

        {

            AboutBox aboutBox = new AboutBox();

            aboutBox.ShowModal();

        }

    }

}