Compartilhar via


WF Custom Activities and the ToolboxBitmap attribute

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.

Comments