Logo
HOWTO: Get the icons of files in the Solution Explorer.

Author: Carlos J. Quintero (Microsoft MVP) Applies to: Microsoft Visual Studio 2005
Date: February 2014   Microsoft Visual Studio 2008
      Microsoft Visual Studio 2010
      Microsoft Visual Studio 2012
      Microsoft Visual Studio 2013
Introduction

This article provides the sample code of a package that gets the icons of the solution, projects, files and folders, showing them in a treeview inside a toolwindow, like the Solution Explorer. It gets the icons using the IVsHierarchy interface.

If you are building an add-in instead of a package, you can get the IVsHierarchy interface as explained in the article HOWTO: Get the IVsHierarchy and Item Id of EnvDTE.Project and EnvDTE.ProjectItem. To know how to navigate the solution, project and files using that interface, see the article HOWTO: Navigate the files of a solution using the IVsHierarchy interface from an add-in.

More Information

The icon of an item of the Solution Explorer can be provided through three ways:

  • An icon in a image list provided by the project. In this case you have an image list handle and an icon index.
  • An icon provided by the project. In this case you have an icon handle.
  • An icon provided by the Windows Shell. In this case you have an icon handle.

The approach is then:

  1. Try to get the image list of a project. If successful, you get the icon from the image list.
  2. If fails, try to get the icon directly from the project.
  3. If fails, try to get the icon from the Windows Shell.

To build the sample follow these steps:

  • Install the Visual Studio SDK. This sample uses Visual Studio 2005, but you can adapt it for higher versions.
  • Create a new project selecting "Other Project Types" > "Extensibility" > "Visual Studio Integration Package" template. Name the project "SolutionExplorer".
  • In the pages of the package wizard:
    • Select the C# language.
    • Select the "Menu Command" and "Tool Window" options. Do not select the "Custom Editor" option.
    • Leave the other settings with the default values.
  • In the VsPkg.cs file, replace the ShowToolWindow method but this one, which after showing the toolwindow it gets the control inside and call its Fill method:
Language: C#   Copy Code Copy Code (IE only)
private void ShowToolWindow(object sender, EventArgs e)
{
    IVsSolution solution;
    MyControl myControl;

    ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), 0, true);
    if ((null == window) || (null == window.Frame))
    {
        throw new COMException(Resources.CanNotCreateWindow);
    }
    IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
    Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());

    myControl = (MyControl)window.Window;

    solution = (IVsSolution)base.GetService(typeof(IVsSolution));

    myControl.Fill(solution);
}
  • In the designer of the MyControl.cs file, remove the button and add a Windows Forms TreeView named TreeViewExplorer, set its Dock property to Fill, and a ImageList named ImageListImages.
  • Set event handlers for the AfterCollapse and AfterExpand events of the treeview.
  • Replace the code of the MyControl.cs file by this one:
Language: C#   Copy Code Copy Code (IE only)
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell.Interop;

namespace Company.SolutionExplorer
{
   public partial class MyControl : UserControl
   {
      private const int MAX_PATH = 260;
      private const int IMAGE_NONE = -1;
      private const uint VSITEMID_NIL = 0xFFFFFFFF;
      private const uint VSITEMID_ROOT = 0xFFFFFFFE;

      [StructLayout(LayoutKind.Sequential)]
      private struct SHFILEINFO
      {
         internal IntPtr hIcon;
         internal IntPtr iIcon;
         internal uint dwAttributes;
         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
         internal string szDisplayName;
         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
         internal string szTypeName;
      };

      [DllImport("shell32.dll", EntryPoint = "SHGetFileInfoW", ExactSpelling = true, CharSet = CharSet.Unicode)]
      private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, 
         uint cbSizeFileInfo, uint uFlags);

      [DllImport("comctl32.dll", CharSet = CharSet.None, ExactSpelling = false)]
      private static extern IntPtr ImageList_GetIcon(IntPtr imageListHandle, int iconIndex, int flags);

      [DllImport("user32.dll")]
      private static extern bool DestroyIcon(IntPtr hIcon);

      private enum ShouldDestroyIcon
      {
         No = 0,
         Yes = 1
      }

      private enum FolderState
      {
         Open = 1,
         Closed = 2
      }

      public MyControl()
      {
         InitializeComponent();
      }

      internal void Fill(IVsSolution solution)
      {
         IVsHierarchy hierarchy;

         this.TreeViewExplorer.ImageList = this.ImageListImages;

         hierarchy = solution as IVsHierarchy;

         this.TreeViewExplorer.Nodes.Clear();
         ProcessHierarchy(hierarchy, null);
      }


      protected override bool ProcessDialogChar(char charCode)
      {
         // If we're the top-level form or control, we need to do the mnemonic handling
         if (charCode != ' ' && ProcessMnemonic(charCode))
         {
            return true;
         }
         return base.ProcessDialogChar(charCode);
      }

      private void ProcessHierarchy(IVsHierarchy hierarchy, TreeNode parentNode)
      {
         // Traverse the nodes of the hierarchy from the root node
         ProcessHierarchyNodeRecursively(hierarchy, VSITEMID_ROOT, parentNode);
      }

      private void ProcessHierarchyNodeRecursively(IVsHierarchy hierarchy, uint itemId, TreeNode parentNode)
      {
         int result;
         IntPtr nestedHiearchyValue = IntPtr.Zero;
         uint nestedItemIdValue = 0;
         object value = null;
         uint visibleChildNode;
         Guid nestedHierarchyGuid;
         IVsHierarchy nestedHierarchy;
         TreeNode treeNode;
         string treeNodeText;
         int openImageIndex;
         int closedImageIndex;

         // First, guess if the node is actually the root of another hierarchy (a project, for example)
         nestedHierarchyGuid = typeof(IVsHierarchy).GUID;
         result = hierarchy.GetNestedHierarchy(itemId, ref nestedHierarchyGuid, out nestedHiearchyValue, out nestedItemIdValue);

         if (result == Microsoft.VisualStudio.VSConstants.S_OK && nestedHiearchyValue != IntPtr.Zero && nestedItemIdValue == VSITEMID_ROOT)
         {
            // Get the new hierarchy
            nestedHierarchy = System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(nestedHiearchyValue) as IVsHierarchy;
            System.Runtime.InteropServices.Marshal.Release(nestedHiearchyValue);

            if (nestedHierarchy != null)
            {
               ProcessHierarchy(nestedHierarchy, parentNode);
            }
         }
         else // The node is not the root of another hierarchy, it is a regular node
         {
            treeNodeText = GetTreeNodeName(hierarchy, itemId);
            closedImageIndex = GetTreeNodeImageIndex(hierarchy, itemId, FolderState.Closed);
            openImageIndex = GetTreeNodeImageIndex(hierarchy, itemId, FolderState.Open);

            treeNode = new TreeNode();
            treeNode.Text = treeNodeText;

            treeNode.ImageIndex = closedImageIndex;
            treeNode.SelectedImageIndex = treeNode.ImageIndex;

            if (parentNode == null)
            {
               this.TreeViewExplorer.Nodes.Add(treeNode);
            }
            else
            {
               parentNode.Nodes.Add(treeNode);
            }

            // Get the first visible child node
            result = hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_FirstVisibleChild, out value);

            while (result == Microsoft.VisualStudio.VSConstants.S_OK && value != null)
            {
               if (value is int && (uint)(int)value == VSITEMID_NIL)
               {
                  // No more nodes
                  break;
               }
               else
               {
                  visibleChildNode = Convert.ToUInt32(value);

                  // Enter in recursion
                  ProcessHierarchyNodeRecursively(hierarchy, visibleChildNode, treeNode);

                  // Get the next visible sibling node
                  value = null;
                  result = hierarchy.GetProperty(visibleChildNode, (int)__VSHPROPID.VSHPROPID_NextVisibleSibling, out value);
               }
            }
         }
      }

      private string GetTreeNodeName(IVsHierarchy hierarchy, uint itemId)
      {
         int result;
         object value = null;
         string name = "";

         result = hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_Name, out value);

         if (result == Microsoft.VisualStudio.VSConstants.S_OK && value != null)
         {
            name = value.ToString();
         }

         return name;
      }


      private int GetTreeNodeImageIndex(IVsHierarchy hierarchy, uint itemId, FolderState folderState)
      {
         int result;
         string canonicalName;

         result = IMAGE_NONE;

         if (hierarchy != null)
         {
            if (itemId > 0 && itemId != Microsoft.VisualStudio.VSConstants.VSITEMID_NIL)
            {
               hierarchy.GetCanonicalName(itemId, out canonicalName);

               // Case 1: Try to get the icon from the hierachy with imagelist
               result = GetIconWithImageList(hierarchy, itemId, folderState);

               // Case 2: Try to get the icon from the hierachy without imagelist
               // This is the case, for example, of files of project VS 2010, Database > SQL Server > SQL Server 2005 Database Project
               if (result == IMAGE_NONE)
               {
                  result = GetIconWithoutImageList(hierarchy, itemId, folderState);
               }

               // Case 3: Try to get the icon from the Windows shell
               if (result == IMAGE_NONE)
               {
                  result = GetIconFromShell(canonicalName);
               }
            }
         }

         return result;
      }

      private int GetIconWithImageList(IVsHierarchy hierarchy, uint itemId, FolderState folderState)
      {
         const int ILD_NORMAL = 0;

         int result;
         int callResult;
         object imageListHandleObject = null;
         IntPtr imageListHandle;
         object iconIndexObject = null;
         int iconIndex = 0;
         IntPtr iconHandle = IntPtr.Zero;

         result = IMAGE_NONE;

         callResult = hierarchy.GetProperty(Microsoft.VisualStudio.VSConstants.VSITEMID_ROOT, 
            (int)__VSHPROPID.VSHPROPID_IconImgList, out imageListHandleObject);

         if (callResult == Microsoft.VisualStudio.VSConstants.S_OK) // It uses ImageList
         {
            imageListHandle = new IntPtr(Convert.ToInt64(imageListHandleObject));

            // Get the icon index
            callResult = Microsoft.VisualStudio.VSConstants.S_FALSE;

            switch (folderState)
            {
               case FolderState.Closed:

                  callResult = hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_IconIndex, out iconIndexObject);
                  break;

               case FolderState.Open:

                  callResult = hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_OpenFolderIconIndex, out iconIndexObject);
                  break;
            }

            if (callResult == Microsoft.VisualStudio.VSConstants.S_OK) // Icon index retrieved
            {
               iconIndex = Convert.ToInt32(iconIndexObject);

               iconHandle = ImageList_GetIcon(imageListHandle, iconIndex, ILD_NORMAL);

               if (iconHandle != IntPtr.Zero)
               {
                  result = AddImage(iconHandle, ShouldDestroyIcon.Yes);
               }
            }
         }
         return result;
      }

      private int GetIconWithoutImageList(IVsHierarchy hierarchy, uint itemId, FolderState folderState)
      {
         int result;
         int callResult;
         IntPtr iconHandle = IntPtr.Zero;
         object iconHandleObject = null;

         result = IMAGE_NONE;

         callResult = Microsoft.VisualStudio.VSConstants.S_FALSE;

         switch (folderState)
         {
            case FolderState.Closed:

               callResult = hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_IconHandle, out iconHandleObject);
               break;

            case FolderState.Open:

               callResult = hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_OpenFolderIconHandle, out iconHandleObject);
               break;
         }

         if (callResult == Microsoft.VisualStudio.VSConstants.S_OK) // Icon handle retrieved
         {
            iconHandle = new IntPtr(Convert.ToInt64(iconHandleObject));

            if (iconHandle != IntPtr.Zero)
            {
               result = AddImage(iconHandle, ShouldDestroyIcon.No);
            }
         }
         return result;
      }

      private int GetIconFromShell(string canonicalName)
      {
         int result;
         IntPtr iconHandle = IntPtr.Zero;
         string extension;

         result = IMAGE_NONE;

         if (!String.IsNullOrEmpty(canonicalName))
         {
            extension = System.IO.Path.GetExtension(canonicalName);

            iconHandle = GetShellIcon(canonicalName);
            if (iconHandle != IntPtr.Zero)
            {
               result = AddImage(iconHandle, ShouldDestroyIcon.Yes);
            }
         }

         return result;
      }

      private int AddImage(IntPtr iconHandle, ShouldDestroyIcon shouldDestroyIcon)
      {
         int result;
         Bitmap bitmap = null;

         result = IMAGE_NONE;

         if (iconHandle != IntPtr.Zero)
         {
            bitmap = this.ConstructBitmap(iconHandle, shouldDestroyIcon);

            this.ImageListImages.Images.Add(bitmap);

            result = this.ImageListImages.Images.Count - 1;
         }

         return result;
      }

      private Bitmap ConstructBitmap(IntPtr iconHandle, ShouldDestroyIcon shouldDestroyIcon)
      {
         Bitmap bitmap = null;
         Icon icon;
         Graphics graphics;

         if (iconHandle != IntPtr.Zero)
         {
            icon = Icon.FromHandle(iconHandle);

            using (icon)
            {
               bitmap = new Bitmap(icon.Width, icon.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

               graphics = Graphics.FromImage(bitmap);

               using (graphics)
               {
                  graphics.DrawIcon(icon, 0, 0);
               }
            }

            if (shouldDestroyIcon == ShouldDestroyIcon.Yes)
            {
               DestroyIcon(iconHandle);
            }
         }
         return bitmap;
      }

      private IntPtr GetShellIcon(string fullFileName)
      {
         const int SHGFI_ICON = 0x100;
         const int SHGFI_SMALLICON = 0x001;

         IntPtr result = IntPtr.Zero;
         SHFILEINFO shellFileInfo;
         uint size;

         if (!String.IsNullOrEmpty(fullFileName))
         {
            shellFileInfo = new SHFILEINFO();

            size = (uint)Marshal.SizeOf(shellFileInfo);

            SHGetFileInfo(fullFileName, 0, ref shellFileInfo, size, SHGFI_ICON | SHGFI_SMALLICON);

            if (shellFileInfo.hIcon != IntPtr.Zero)
            {
               result = shellFileInfo.hIcon;
            }
         }
         return result;
      }

      private void TreeViewExplorer_AfterCollapse(object sender, TreeViewEventArgs e)
      {
         e.Node.ImageIndex -= 1;
         e.Node.SelectedImageIndex = e.Node.ImageIndex;
      }

      private void TreeViewExplorer_AfterExpand(object sender, TreeViewEventArgs e)
      {
         e.Node.ImageIndex += 1;
         e.Node.SelectedImageIndex = e.Node.ImageIndex;
      }
   }
}
  • When you run the project and a second Visual Studio instance is loaded, click the "View", "Other Windows", "My Tool Window" menu to show the toolwindow.

Related articles



Go to the 'Visual Studio Extensibility (VSX)' web site for more articles like this (Articles section)


Top