Stephen Kaufman's WebLog

Look who's BizTalk'in - Notes on all things integration

WF Custom Activities and the ToolboxBitmap attribute

WF Custom Activities and the ToolboxBitmap attribute

  • Comments 4

Ok, so this isn't just limited to Windows Workflow.  It is relevant to any piece of code that will end up on the Visual Studio toolbox.

I am building a number of custom activities and I got tired of seeing the standard cog graphic in the toolbar for each one.  Within seconds all of my project tasks were put on hold and changing that image was placed to the top of my priority list.  Little did I know that it would be 1 1/2 hours later before I could set my gaze on all of those 16x16 pixel graphic images.

Why did it take so long?  Well, it turns out that the code behind the ToolboxBitmap attribute code attempts to dynamically create a path to point to the embedded resource file.  There are times when the path created does not point to a valid location. 

So when I first started down this path I followed the typical route.  This typical path is to add a graphic as a resource of the assembly with the graphic marked as an embedded resource on the property page.  Then add the ToolboxBitmap attribute to your class providing the type of the object and the location for the graphic.  Compile and add your item to the toolbar and you should see the entry along with your graphic image.

As I was researching this I found that there are two things that can mess this process up.  The first is to make sure that you know the fully qualified path of graphic, including if the graphic is in a folder in the solution and second is around the namespace of the assembly.

If the default namespace has been changed from the original settings you will need to add that to the beginning of the string along with the the folder tree scheme.  Therefore, if I have my graphic image in a folder called Resources and my default namespace is BlogActivity then my resource address is BlogActivity.Resources.MyGraphic.png.  A problem occurs if we change the default namespace.  Lets say that I change it to BlogActivityTest.  This change would have my resource address as BlogActivityTest.Resources.MyGraphic.png.  This in an of itself is not a bad thing but in order for the ToolboxBitmap attribute to find the graphic the attribute code needs to find them in the context of their own namespace.  There are two attribute constructors that are relevant to this (there are three constructors but the third loads the graphic from a location on the file system and that is not what we want).  These two constructors are:

    TooboxBitMap(typeof(<control>), "<assembly>.<resource>")

    ToolboxBitMap(typeof(<control>))

The second constructor is interesting if you name the graphic the same name as the control.  If they are the same then the attribute code will match the graphic with the resource automatically.

It is the first constructor that led me in circles.  As it turns out there is a little bug in the GetImageFromResource method.  If you have a namespace that is different from the assembly name (as I am sure happens in most scenarios) then the GetImageFromResource ends up putting the assembly name in front of the resource address.  In my example, my resource address would look something like BlogActivity.BlogActivity.Resources.MyGraphic.png and this location does not exist.

Now that I understood what was happening I wanted to see what my Visual Studio project thought the address was.  Place the code below in the constructor of your object.   The output of this code will show you the full path to your embedded resource that the runtime will use.  This output should match the string you are using for the second parameter.  This is only to loop through all of the embedded resource for debugging and is not meant to remain in your code.

public BlogItem()

{

    InitializeComponent();

    //Remove this code after debugging

    string[] rn = this.GetType().Assembly.GetManifestResourceNames();

    foreach (string s in rn)

    {

        System.Diagnostics.Trace.WriteLine(s);

    }

}

After verifying and testing this path and things still don't work then follow these steps.  We need to tinker with the internal dynamic path creation mechanism.

    First, create an internal class that is outside of the root namespace.  This class can be called whatever you want (in my example I called it EmbeddedResourceFinder)

    Next, use this class name in the ToolboxBitmap attribute instead of your control name

    Lastly, change the resource location argument to match "<default namespace>.<resourcename>" in order to locate the resource.

The code will look like this:

 

using System.Workflow.Activities;

//this internal class is needed to fool the ToolboxBitMap attribute so that it can actually build the

//assembly/namespace string correctly for it to find the resource.

internal class EmbeddedResourceFinder

{

}

namespace BlogItem.Workflow.Activities

{

    [ToolboxBitmap(typeof(EmbeddedResourceFinder), "BlogActivity.MyGraphic.png")]

    [Designer(typeof(PerformanceMonitorDesigner))]

    [ActivityValidator(typeof(PerformanceMonitorValidator))]

    public partial class BlogActivity : SequenceActivity

    {

    ..........

Now that you are using the empty class and modified attribute you will find that the next time you add your control to the toolbox you will see your picture.