Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Creating Menu Bitmaps and AboutIcons for Add-ins in Visual Studio .NET 2005


:P
On this page:

Creating Bitmaps and About Icon data for Add-ins is still a royal pain in the ass in Visual Studio 2005. The process has changed some and you can now at least use managed resources, but the process to accomplish this as outlined on MSDN is substantial:

 

http://msdn2.microsoft.com/en-us/library/e9zazcx5.aspx

 

This process outline is fraught with bad practices. It basically advocates adding resources to a project, creating a RESX file from these resources, modifying the resx source file and then compiling these resources. Uh, I don’t think so…

 

The first problem with this is that the Visual Studio 2005 Managed Resource Editor – while a big improvement over none at all in Visual Studio 2003 – is that it can’t create named resources. It can only generate numeric ids for icons, which is a problem if you want to say use a resource as an AboutIcon.

Creating Resources more easily

So, do yourself a favor and skip the VS resource editor and download Lutz Roeders nice and simple Resourcer utility and build your resources. Resourcer works against .RESX files or creates them and lets you add bitmaps, icons and text to the resource project easily. With it you can select files and more importantly you can name the resources properly.

 

 

 

In the shot above there’s is a bitmap for use as a menu icon. Menu icons should be 16x16 and can be up to true color (32 bit). If you want your menu bitmap to be transparent, make the background BrightGreen with RGB (0,254,0). Notice 254, not 255! For the AboutIcon use Icon transparency to indicate your transparency. I use IconLover for editing small images to create icons both for real icons or icon like images in Web apps, which makes transparency and general image filling a snap.

 

Menu bitmaps should be assigned numeric name starting no lower than 100 because the VS.ADD Command object uses numeric index ids to identify images. Menu bitmaps should be imported from Bitmap files.

 

The AboutIcon that is used in the Visual Studio About Box should be 32x32 pixels and should use standard Icon transparency (IconLover or the like can do this easily). AboutIcon data should be imported from an Icon not a bitmap.

 

Once you have your resources together, you need to compile this resource file into a Resource assembly dll. To do this you need ResGen.exe and AL.exe (Assembly Linker). I tend to create a batch file that does this for me, so I can just double click in Explorer to create my resource DLL and immediately copy it to my target directory:

 

cd %curdir%

@set PATH=D:\Programs\vs2005\Common7\IDE;D:\Programs\vs2005\VC\BIN;D:\Programs\vs2005\Common7\Tools;D:\Programs\vs2005\Common7\Tools\bin;D:\Programs\vs2005\VC\PlatformSDK\bin;D:\Programs\vs2005\SDK\v2.0\bin;D:\WINDOWS\Microsoft.NET\Framework\v2.0.50727;D:\Programs\vs2005\VC\VCPackages;%PATH%

 

Resgen.exe ImageResources.resx

AL.exe /embed:ImageResources.resources /culture:en-US /out:HelpBuilderVs2005Addin.resources.dll

 

copy HelpBuilderVs2005Addin.resources.dll d:\installs\wwhelp\vsAddin\en-us

 

pause

 

Note that your paths may be different. You also need to make sure you have the .NET Framework Sdk 2.0 installed to find ResGen and the Assembly Linker.

 

Most of this is to get all the paths for the required SDKs that hold the resource compilers. You need to ResGen create a resource binary meta file and the Assembly Linker (AL) to embed the resource into a DLL assembly. Finally I like to copy the DLL into my actual add-in directory’s resource directory.

 

In order to use a satellite resource DLL with your Add-in you need to create a resource directory below your add-in DLLs directory. The directory needs to be named with the locale ID (en-us, de-de,fr-CA etc.)  and copy the resource assembly into it. Note that you have to create directories for each locale you will end up using – there’s no way that I could see to use a culture neutral directory.

 

So:

 

AddInDllDirectory

\en-us

\de-de

\fr-fr

 

Each of those resource directories requires your Resource DLL. Usually you’ll install the right DLL into the right directory when you install your Add-in.

 

You now have a resource DLL that you can access from menu buttons and for your about icon.

Hooking up Command Button Images

Command button images are hooked up with one of the various Command addition options. Here’s an example using AddNamedCommand:

 

command = ((Commands2)this.DTE.Commands).AddNamedCommand(

               this.addInInstance,

               CommandName, Caption, Description,

               true, 101,

               ref contextGUIDS,

               (int)vsCommandStatus.vsCommandStatusSupported +

               (int)vsCommandStatus.vsCommandStatusEnabled);

 

Note the true and 101 parameters which state that an icon resource is to be used and the id is 101. The value is an integer, which is why you want to create menu bitmaps with numeric ids in the resource file. Make sure you use values over 100 or even higher to make sure you don’t conflict with an existing icon that VS.NET natively provides.

 

Because creating command bar buttons is a pain, I tend to use wrapper methods to add command bars and buttons and add them to the command bars:

 

public AddCommandReturn AddCommand(string CommandName,string Caption, string Description,

      int IconId,CommandBar commandBar, int InsertionIndex,

      bool BeginGroup, string HotKey)

{

     

      object []contextGUIDS = new object[] { };

     

      // *** Check to see if the Command exists already to be safe

      Command command = null;

      try

      {

            command = this.DTE.Commands.Item(this.addInInstance.ProgID + "." + CommandName,-1);

      }

      catch {;}

     

      // *** If not create it!

      if (command == null)

      {

    command = ((Commands2)this.DTE.Commands).AddNamedCommand(

       this.addInInstance,

       CommandName, Caption, Description,

       IconId == 0? true : false, IconId,

       ref contextGUIDS,

       (int)vsCommandStatus.vsCommandStatusSupported +

       (int)vsCommandStatus.vsCommandStatusEnabled);

 

            if (HotKey != null && HotKey != "")

            {                            

                  object [] bindings = (object [])command.Bindings ;

                  if (bindings != null)

                  {

                        bindings = new object[1];

                        //bindings[0] = (object)"Windows Forms Designer::alt+f1";

                        bindings[0] = (object) HotKey;

                        try

                        {

                              command.Bindings = (object) bindings;

                        }

                        catch(Exception ex) { string t = ex.Message; }

                  }

            }

      }

      CommandBarControl cb = (CommandBarControl) command.AddControl(commandBar,InsertionIndex);

      cb.BeginGroup = BeginGroup;

 

      return new AddCommandReturn(command,cb);

}

 

/// <summary>

/// Creates a new CommandBar Control object ready to add new items to

/// </summary>

/// <param name="ParentCommandBar"></param>

/// <param name="InsertionIndex"></param>

/// <param name="Caption"></param>

/// <param name="BeginGroup"></param>

/// <returns></returns>

public CommandBarControl AddCommandBar(CommandBar ParentCommandBar, string Caption, int InsertionIndex, bool BeginGroup)

{

 CommandBarControl CommandBar =

     ParentCommandBar.Controls.Add(

          MsoControlType.msoControlPopup,

          System.Type.Missing, System.Type.Missing,

          InsertionIndex, true);

 

 CommandBarPopup commandBarPopup = (CommandBarPopup) CommandBar;

 commandBarPopup.Caption = Caption;

 commandBarPopup.BeginGroup = true;

 commandBarPopup.Visible = true;

 

 return CommandBar;

}

 

This can then be used to create a new command bar and add buttons to it with code like this:

 

// *** Retrieve our Main Menu bar to attach menu to

CommandBars commandBars = this.applicationObject.CommandBars as CommandBars;

CommandBar commandBarHelp = (CommandBar)commandBars[this.GetLocalizedString("Help")];

 

 

// *** This code is easier but creates a 'permanent' menu bar that needs to be removed

CommandBarPopup HelpMenuPopup = this.AddInHelper.AddCommandBar(toolsPopup.CommandBar,

                                    AddinVs2005.WWHELP_APPNAME,

this.AddInHelper.GetIndexOfCommandBarControl(toolsPopup.CommandBar,

                                    AddinVs2005.WWHELP_HELPMENU_PREVITEM_ID) + 1,

                                    true) as CommandBarPopup;

 

int HelpPopupInsertionIndex = 1;

 

 

// *** Add button to menu

this.AddInHelper.AddCommand("ShowHelpBuilder", "Show Html Help Builder",

  "Brings up Html Help Builder for the current control", 101,

   HelpMenuPopup.CommandBar, HelpPopupInsertionIndex, true, "Global::alt+f1");

 

HelpPopupInsertionIndex++;

 

// *** Add another button

this.AddInHelper.AddCommand("UpdateComment", "Update Xml Comment ",

  "Updates XML Comment from Html Help Builder Topic", 101,

   HelpMenuPopup.CommandBar, HelpPopupInsertionIndex, true, "Global::ctrl+f1");

 

A typical Add-in will add options to a number of command bars, so once you’ve created a Command you won’t want to re-create the command again. Instead you’ll want to simply add the first one with the full command, then use the Command’s AddControl method to add the command to another toolbar:

 

// *** Add to the Context Menu for Code -  same as above but no Hotkeys

AddCommandReturn Return = this.AddInHelper.AddCommand(

                "ShowHelpBuilderContext", "Show Html Help Builder",

                "Brings up Html Help Builder in the current control",

                101, SelectionPopup.CommandBar, SelectionIndex, true, null);

 

Command command = Return.Command;

 

// *** Add same command to another menu – WinForm Container Context in this case

CommandBarControl cb = null;

cb = command.AddControl(ContainerPopup.CommandBar, ContainerInsertionIndex) as CommandBarControl;

cb.BeginGroup = true;

 

// *** And to another – CodeWindow Popup

cb = command.AddControl(CodeWindowPopup.CommandBar, CodeWindowInsertionIndex) as CommandBarControl;

cb.BeginGroup = true;

 

With these helpers adding command buttons gets manageable, but it still gets pretty hectic especially if you attach commands to multiple menus.

Registering your Add-in

Once you’ve built your Add-in you need to register it. This is one place where things get a lot easier in Visual Studio 2005! To register your addin all you need to do is place a .Addin file in the Visual Studio Add-ins directory in My Documents folder:

 

D:\Documents and Settings\ricks\My Documents\Visual Studio 2005\Addins

 

The .Addin file contains information on the location and name of your add-in – everything VS needs to load and display information about your add-in. The .Addin file is an XML document and it looks something like this:

 

?xml version="1.0" encoding="UTF-16" standalone="no"?>

<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">

      <HostApplication>

            <Name>Microsoft Visual Studio Macros</Name>

            <Version>8.0</Version>

      </HostApplication>

      <HostApplication>

            <Name>Microsoft Visual Studio</Name>

            <Version>8.0</Version>

      </HostApplication>

      <Addin>

            <FriendlyName>Html Help Builder VS.NET 2005 Add-in</FriendlyName>

            <Description>Provides Html Help Builder integration to VS.NET 2005</Description>

            <AboutBoxDetails>West Wind Html Html Help Builder Add-In\r\n(c) 2003-2006 West Wind Technologies</AboutBoxDetails>

            <AboutIconData>@AboutIcon</AboutIconData>

            <Assembly>D:\INSTALLS\WWHELP\vsAddin\HelpBuilderVs2005Addin.dll</Assembly>

            <FullClassName>HelpBuilderAutomation.AddinVs2005</FullClassName>

            <LoadBehavior>1</LoadBehavior>

            <CommandPreload>1</CommandPreload>

            <CommandLineSafe>0</CommandLineSafe>

      </Addin>

</Extensibility>

 

The key elements are the AssemblyPath, which points your Add-in’s main Assembly and provides VS with a startup path for looking for related resources. The FullClassName is the name of your Add-in class that gets loaded when the Add-in is fired up.

 

As you see you can also add descriptive text and information that is displayed in the About box specifically the AboutBoxDetails and AboutIconData.

 

Getting the AboutIcon to work

An Add-in gets an About Icon in the Visual Studio About box once the Add-in is loaded. To do this you need to use a 32x32 icon. Here’s what this looks like loaded into Visual Studio’s About box:

 

To get this to work first you need a 32x32 icon. If you already have a resource DLL associated with your project, the easiest thing to do is add the icon to the resource DLL. Note that you can can’t use the VS.NET resource editor to create properly named resources (as outlined by the MSDN article) – if you do you have to manually edit the resource XML file to change the name from a numeric ID. Therefore again I highly recommend you use a separate resource editor like Resourcer. With it you can name your icon – in the example above I named it AboutIcon.

 

As you can see in the .AddIn file you can reference the icon by its Icon name prefixed with a @. Note that value cannot start with a number and this is why the stock VS.NET Resource editor doesn’t work without manually editing the resx file.

 

<AboutIconData>@AboutIcon</AboutIconData>

 

This works great if you already use a resource dll. But if you don’t use custom icons or localized text in your menus then you probably don’t want to create a resource DLL just for the About icon. Instead, you can also embed a HexBinary representation of the raw Icon data.

 

How do you get a HexBinary representation? Here’s a small snippet that does the trick on a Win Form:

 

private void btnDir_Click(object sender, EventArgs e)

{

 

    OpenFileDialog fd = new OpenFileDialog();

   

    fd.CheckFileExists = true;

    if (fd.ShowDialog() == DialogResult.OK)

    {

        this.txtFilename.Text = fd.FileName;

 

        StringBuilder sb = new StringBuilder();

        Stream sr = fd.OpenFile();

 

        while (true)

        {

            int LastByte = sr.ReadByte();

            if (LastByte == -1)

                break;

 

            sb.Append(LastByte.ToString("X2"));

        }

 

        this.txtResult.Text = sb.ToString();

        Clipboard.SetData(DataFormats.Text, this.txtResult.Text);

    }

}

 

You can download this simple utility from:

 

http://www.west-wind.com/files/tools/binarystringconverter.zip

 

The output that this thing generates can then be pasted directly into your AboutIconData element like this:

 

<AboutBoxDetails>For more information about West Wind Technologies, see the West Wind Technologies website at\r\nhttp://www.West Wind Technologies.com\r\n (c) 2005 West Wind Technologies Inc.</AboutBoxDetails>

<AboutIconData>0000010002002020100000000000E8020000260000001010100000000000280100000E0300002800000020000000400000000100040000000000000200000000000000000000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00FFFFFFFFFFFFFFFFFF88CCCCCC88FFFFFFFFFFFFFFFFFFFF8CCCCCCCCCCCC8FFFFFFFFFFFFFFFF8CCCCCCCCCCCCCCCCFFFFFFFFFFFFFF8CCCCCCCCCCCCCCCCCFFFFFFFFFFFFFFCCCCCCCCCCCCCCCCCCFFFFFFFFFFFFFCCCCCCCC8FFFFF88CCCFFFFFFFFFFFF8CCCCCCCFFFFFFFFF8CCFFFFFFFFFFFF8CCCCCCFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCC8FFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCFFFFFFFFFFFFFFFFFFFFFFFFF0CCCCCCFFFFFFFFFFFFFFFFFFFF9999980CCCCC99999F0FFFFFFFFFFFF99999990CCCCC089990FFFFFFFFFFFFF999999990CCCC889908FFFFFFFFFFFF79990999900CCC089809FFFFFF88FFFF9999980898C0CC0C8099FFF88CC8FFFF9999998008C00C880888CCCCCCC8FFFF999999999000080800008CCCCCC8FFF79999999999800000000008CCCCC8FFF79999999999800000077008CCCC8FFFF99999999999800000878000888FFFFFF99999999999800000780008FFFFFFFF7999999999998000080000097FFFFFFF7999999F99980888880888997FFFFFFF9999999F08099089890999999FFFFFFF999999880999089909F8099997FFFFF79999990FF998099908F9009997FFFFF7999999FFF990999990FF998099FFFFF9999999FFF80999999F0F999999FFFFF9999999FFF09999999FFF9999997FFFF9999997FFFF9999997FFF9999999FFFF9999997FFFF9999997FFF8999999FFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000010000000200000000100040000000000C00000000000000000000000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00FFFFFFFF78CCC87FFFFFFF7CCCCCCCC7FFFFFFCCCC8778C7FFFFF7CCC7FFFF77FFFFF7CC8FFFFFFFFF7774CC5777FFFFFF99914C4818FFFFF79119444817F787F79991408888CCC7F899998000808CC7F9999980088087FF7999998888818FFF7999811811199FFF8991F919181117FF9997F199977998FF9997F7998F7999FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</AboutIconData>

 

 

And there you have it. Once you know what to do it’s not too difficult to get this taken care of but the documentation for this process can’t be found in one place that I’ve seen so far. So I hope this helps resolve some of the issues around displaying custom icons and bitmaps in Visual Studio .NET 2005 Add-ins.


The Voices of Reason


 

Buddhike de Silva
December 15, 2005

# re: Creating Menu Bitmaps and AboutIcons for Add-ins in Visual Studio .NET 2005

Extreamly cool stuff! Thank you very much.

Wesley Smith
December 15, 2005

# re: Creating Menu Bitmaps and AboutIcons for Add-ins in Visual Studio .NET 2005

Thanks for the very detailed article!

I'm not sure what you mean about Visual Studio 2005 Resource Editor only generating numeric ids for icons. When adding a new icon, it asks for a name to give the resource, and I can choose a name like AboutIcon without any trouble. In addition, I can right-click on any Icon in the Resource Editor and rename it to whatever I want. The AboutIcon I created this way seems to show up fine for my add-in.

Also, I use a different method for creating the Satellite Assembly which works well for me. I just add a resx file to the assembly, and then make sure it’s named “Resource1.en-US.resx”. Into that resx file go the icons and strings. Then, when building the project, Visual Studio will automatically create the satellite assembly and copy it to a en-US directory in the bin/Debug or bin/Release directory.


Mit
December 12, 2006

# re: Creating Menu Bitmaps and AboutIcons for Add-ins in Visual Studio .NET 2005


Dear Mr.Rick,

Sir! Thanks for quoting such an important blog really helped me solving IDE toolbar icon change problem which i have added already but was not able to change that.

Can you help me giving any clue for "how to add Windows Form as Tabbed panes in VS.NET IDE beside StartPage" using Add-ins.

Awaiting your reply,


With regards,

Mit Brahmbhatt
<mitul.brahmbhatt@yahoo.com>

mob's dev blog
July 12, 2007

# mob's dev blog - Visual Studio 2005 AddIn Resources


Klaus Petersen
August 11, 2007

# re: Creating Menu Bitmaps and AboutIcons for Add-ins in Visual Studio .NET 2005

Very very nice solution Wesley Smith!

I've always found it very cumbersome to add custom icons to my commandbar buttons, but your suggestion works very nicely and is very easy to implement.

I just added a resource file, which I named Resources.en.resx and visual studio automatically disables the custom tool* and copies the resource assembly to the culture specific folder and everything works.

*The custom tool actually prevents naming the resources correctly. It autogenerates a class to access resources. Since every resource has a corresponding property and integers cannot be used as identifiers in c#, its impossible to name the resource correctly in order to make the icon work.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024