Howto: Set custom Visual Studio Addin menu icons without a satellite dll

Update: Here is a more full featured declarative example of doing buttons with icons.

OK. Setting icons on your visual studio addins is officially a sucky process and I wish MS would make this a much less painful experience. I cannot believe how much time I have wasted on this.Thanks to Jamie I have a solution for my problems and I’ve added a couple more things.

Problem: You created an addin for VS and you’d like to have custom icons. You google for it and find that it’s not trivial.

Solution: There are a couple of ways to do this. the one I will not show is having a satellite DLL that has the icons resources for the addin. If you are working with VS 2003 as well, you’ll have to do it this way.

Here is the way I’m doing it right now. A lot of this code is from inside testdriven.net’s assemblies (got jamies permission to post this) and I put it in a helper class that I can use easily. In short, you need the following code (will handle .ico and .bmp). see how to use it later below.

using System;

using System.Drawing;

using System.Drawing.Imaging;

using System.Runtime.InteropServices;

using System.Windows.Forms;

using EnvDTE;

using EnvDTE80;

using Microsoft.VisualStudio.CommandBars;

using stdole;

namespace MyAddin1

{

    public class IconUtils

    {

        public static void AddCommandWithicon(Commands2 root,

            CommandBarPopup menuToAddTo,

           string name,

           string buttonText,

           string buttonDescription,

           string iconFile,

           AddIn addInInstance)

        {

            object[] contextGUIDS = null;

            Command command = root.AddNamedCommand2(addInInstance,

                                                        name,

                                                        buttonText,

                                                        buttonDescription,

                                                        true, 59,

                                                        ref contextGUIDS,

                                                        (int)vsCommandStatus.vsCommandStatusSupported +

                                                        (int)vsCommandStatus.vsCommandStatusEnabled,

                                                        (int)vsCommandStyle.vsCommandStylePictAndText,

                                                        vsCommandControlType.vsCommandControlTypeButton);

            CommandBarButton control = (CommandBarButton)command.AddControl(menuToAddTo.CommandBar, 1);

            SetControlPicture(control, iconFile);

        }

 

        private static Color guessTransparentColor(Bitmap bitmap)

        {

            Color pixel = bitmap.GetPixel(0, 0);

            Color color2 = bitmap.GetPixel(bitmap.Width - 1, 0);

            Color color3 = bitmap.GetPixel(0, bitmap.Height - 1);

            Color color4 = bitmap.GetPixel(bitmap.Width - 1, bitmap.Height - 1);

            if (pixel == color2)

            {

                return pixel;

            }

            if (color2 == color3)

            {

                return color2;

            }

            if (color3 == color4)

            {

                return color3;

            }

            return color4;

        }

 

 

        protected static Bitmap PrepareImage(Image image, Color transparentColor)

        {

            Bitmap bitmap = new Bitmap(image);

            Bitmap bitmap2 = new Bitmap(0x10, 0x10, PixelFormat.Format24bppRgb);

            Color color = guessTransparentColor(bitmap);

            for (int i = 0; i < bitmap2.Width; i++)

            {

                for (int j = 0; j < bitmap2.Height; j++)

                {

                    Color baseColor = color;

                    if ((i < bitmap.Width) && (j < bitmap.Height))

                    {

                        baseColor = bitmap.GetPixel(i, j);

                    }

                    baseColor = Color.FromArgb(0xff, baseColor);

                    if (baseColor != color)

                    {

                        bitmap2.SetPixel(i, j, baseColor);

                    }

                    else if ((transparentColor != color) && (baseColor == transparentColor))

                    {

                        if (baseColor.R > 0)

                        {

                            bitmap2.SetPixel(i, j, Color.FromArgb(baseColor.R - 1, baseColor.G, baseColor.B));

                        }

                        else

                        {

                            bitmap2.SetPixel(i, j, Color.FromArgb(baseColor.R + 1, baseColor.G, baseColor.B));

                        }

                    }

                    else

                    {

                        bitmap2.SetPixel(i, j, transparentColor);

                    }

                }

            }

            return bitmap2;

        }

 

        protected static Bitmap PrepareMask(Image image)

        {

            Bitmap bitmap = new Bitmap(image);

            Bitmap bitmap2 = new Bitmap(0x10, 0x10, PixelFormat.Format24bppRgb);

            Color color = guessTransparentColor(bitmap);

            for (int i = 0; i < image.Width; i++)

            {

                for (int j = 0; j < image.Height; j++)

                {

                    Color pixel = bitmap.GetPixel(i, j);

                    Color color3 = ((pixel == color) || (pixel.A < 0xff)) ? Color.White : Color.Black;

                    bitmap2.SetPixel(i, j, color3);

                }

            }

            return bitmap2;

        }

 

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]

        private struct SHFILEINFO

        {

            public IntPtr hIcon;

            public int iIcon;

            public int dwAttributes;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)]

            public char[] szDisplayName;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)]

            public char[] szTypeName;

        }

 

 

        protected static Icon GetIconFromFile(string path)

        {

            SHFILEINFO psfi = new SHFILEINFO();

            SHGetFileInfo(path, 0x80, ref psfi, Marshal.SizeOf(psfi), 0x111);

            return Icon.FromHandle(psfi.hIcon);

        }

 

 

 

 

        [DllImport("shell32.dll", CharSet = CharSet.Auto)]

        private static extern IntPtr SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, int uFlags);

 

 

 

 

        [DllImport("oleaut32.dll", CharSet = CharSet.Auto, SetLastError = true)]

        internal static extern int OleLoadPictureFile(object fileName, [MarshalAs(UnmanagedType.IDispatch)] ref object iPictureDisp);

        static Color VS_MENUCOLOR = Color.FromArgb(0xec, 0xe9, 0xd8);

 

        public static void SetControlPicture(CommandBarButton button,string fileName)

        {

            Image image1 = GetImageFromAnyFormat(fileName);

            Bitmap image2 = PrepareImage(image1, VS_MENUCOLOR);

            Bitmap image3 = PrepareMask(image2);

            button.Picture = CreatePictureDisp(image2);

        }

 

        protected static Image GetImageFromAnyFormat(string path)

        {

            string str = path.ToLower();

            if (str.EndsWith(".exe") || str.EndsWith(".ico"))

            {

                return GetIconFromFile(path).ToBitmap();

            }

            return Image.FromFile(path, true);

        }

 

 

        protected static object OleLoadPictureFile(string fileName)

        {

            object iPictureDisp = null;

            OleLoadPictureFile(fileName, ref iPictureDisp);

            return iPictureDisp;

        }

 

 

        protected static StdPicture CreatePictureDisp(Image image)

        {

            return (StdPicture) ImageConverter.GetIPictureDispFromImage(image);

            //here is another approach:

//            string tempFileName = Path.GetTempFileName();

//            image.Save(tempFileName, ImageFormat.Bmp);

//            return (StdPicture) OleLoadPictureFile(tempFileName);

        }

 

    }

 

    class ImageConverter : AxHost

    {

        // Methods

        internal ImageConverter()

            : base("52D64AAC-29C1-CAC8-BB3A-115F0D3D77CB")

        {

        }

 

        public static IPictureDisp GetIPictureDispFromImage(Image image)

        {

            return (IPictureDisp)AxHost.GetIPictureDispFromPicture(image);

        }

    }

 

 

 

}

 

 

Here is how you can use this code:

Here is what the code in Connect.cs would look if I were creating two buttons with the same icon to the “tools”:

public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)

        {

            _applicationObject = (DTE2)application;

            _addInInstance = (AddIn)addInInst;

            if(connectMode == ext_ConnectMode.ext_cm_UISetup)

            {

                Commands2 commands = (Commands2)_applicationObject.Commands;

                CommandBar menuBarCommandBar = ((CommandBars)_applicationObject.CommandBars)["MenuBar"];

                CommandBarControl toolsControl = menuBarCommandBar.Controls["Tools"];

 

                IconUtils.AddCommandWithicon(commands, (CommandBarPopup)toolsControl,

                    "SomeCommandName",

                    "My Button 1",

                    "Executes the command for MyAddin1",

                    @"c:\a.bmp",

                    _addInInstance);

 

                IconUtils.AddCommandWithicon(commands, (CommandBarPopup)toolsControl,

                    "SomeCommandName2",

                    "My Button 2",

                    "Executes the command for MyAddin1",

                    @"c:\a.bmp",

                    _addInInstance);

            }

        }

 

In the next post I’ll describe how sucky the “command” model is with VS addins and how you can create a somewhatreasonable model for custom actions and buttons that is more maintainable and nderstandable.

Published Saturday, August 02, 2008 6:28 AM by RoyOsherove

Comments

Saturday, August 02, 2008 9:47 AM by Dew Drop - August 2, 2008 | Alvin Ashcraft's Morning Dew

# Dew Drop - August 2, 2008 | Alvin Ashcraft's Morning Dew

Pingback from  Dew Drop - August 2, 2008 | Alvin Ashcraft's Morning Dew

Saturday, August 02, 2008 4:21 PM by ISerializable - Roy Osherove's Blog

# Declarative Visual Studio addin buttons with icons

in the previous pos t I said how I hated doing custom icons for addins and showed how you can make it

Sunday, August 03, 2008 10:40 AM by Visual Studio Hacks

# Visual Studio Links #60

My latest in a series of the weekly, or more often, summary of interesting links I come across related to Visual Studio. Jonathan Wood has created a new video - How Do I: Use the Visual C++ 2008 Feature Pack? Part 2: Using the Ribbon Bar . Greg Duncan

# Reflective Perspective - Chris Alcock &raquo; The Morning Brew #150

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #150