Search The Blog
My Books

New:

My Songs

 

The Art of Unit Testing

Buy PDF or Print book at Manning

Buy on Amazon

Latest Posts
from 5whys.com
Twitter: @RoyOsherove
About this site

TDD in .NET Online Course

TDD and BDD in Ruby Online Course

 

Subscribe!

This site aims to connect all the dots of my online activities - from tools, books blogs and twitter accounts, to upcoming conferences, engagements and user group talks.

« Deadlock detectors | Main | My Son - Itamar the Snake Tamer »
Saturday
Aug022008

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.

PrintView Printer Friendly Version

Reader Comments (1)

Hi,

nice post!

I have been doing it the paintfull way from MSDN.

I have a question. Is there a way how to change the icon and label for the button dynamically?

For example, I am working on an addin which is doing something with project references and I would like to have different label, tooltip and icon for a reference to project and for a reference to dll assembly (for the context menu while right-clicking on it).

Would you happen how to do that?

regards,
Jan

July 22, 2011 | Unregistered Commenterjan

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>
Web Analytics