Roy Osherove

View Original

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.