Displaying .NET Framework 4 Built-In Workflow Activity Icons in a rehosted Workflow Designer

Displaying .NET Framework 4 Built-In Workflow Activity Icons in a rehosted Workflow Designer

Rate This
  • Comments 2

Rehosting the .NET4 Workflow Designer enables all the Built-In or ‘Standard’ Activities are available within the ISV partner’s custom application.  However one issue our ISV partners are running into is that the Activity icons do not show up. The (default) Workflow Designer hosted in Visual Studio has contextual icons associated with the Standard Activities (Figure 1 - Standard Activity icons in VS10 WF Designer); while the rehosted Workflow Designer (Figure 2 - Standard Activity have default icon in Rehosted WF Designer) always references one default icon.

Figure 1 - Standard Activity icons in VS10 WF Designer

Figure 2 - Standard Activity have default icon in Rehosted WF Designer

The issue is not trivial since there are over 40 Built-In Activities http://msdn.microsoft.com/en-us/library/dd489459 and all of them are assigned the one default icon (Figure 3 - Default Standard Activity icons in Rehosted WF Designer).

Figure 3 - Default Activity Icons in Rehosted WF Designer

While it’s possible to set the BitmapName property (review in code snippet below); it’s not trivial task to manually copy/create and associate ‘contextual’ icons for the Activities. 

ToolboxItemWrapper flowchartAct = new ToolboxItemWrapper (typeof (System.Activities.Statements.Flowchart), "Flowchart");
flowchartAct.BitmapName = @"D:\<file path>\logoflowchart1.png";

Since it’s also important for the ISVs to ensure that the Workflow Designer presents a common user experience irrespective of the hosting paradigm; an acceptable solution would be to acquire (from the Microsoft.VisualStudio.Activities.dll at design time) the Activity icons and reuse them in the rehosted Workflow Designer. To ensure acceptable performance characteristics at runtime the design should avoid placing and extracting the icons from a file system.

Solution

A programmatic solution is to use reflection into assembly and load the appropriate icons via the MetadataStore (not from the file system) as needed. This requires copying the Microsoft.VisualStudio.Activities.dll to the referenced assemblies’ folder for the rehosted Workflow Designer (this sample hard codes the path: C:\RehostedDesigner_DependentDlls\). The design, sample code and using the code are elaborated below.

Design

LoadToolboxIconsForBuiltInActivities applies the ToolboxBitmapAttribute at runtime to all Standard Activities for which an icon is found in the resources of Microsoft.VisualStudio.Activities.dll. It makes use of the MetadataStore to do this.

CreateToolBoxBitmapAttributeForActivity: if an icon is available, this method creates the actual ToolboxBitmapAttribute by invoking a private constructor that takes two images (a large and small icon). The attribute is applied to the Activity type by means of the AttributeTableBuilder’s AddCustomAttribute method.

ExtractBitmapResource searches through the resources by name, and if the resource Key (it’s name) matches the class name (without namespace) of the activity the bitmap is loaded from the Value of the resource entry. The bitmap has a solid colored background and needs to be made transparent. By convention, looking at the top right pixel will tell you what color in the image considered transparent, so this code uses the color of that pixel for transparency before returning the icon.

Sample Code

   1: private static void LoadToolboxIconsForBuiltInActivities()
   2: {
   3:     AttributeTableBuilder builder = new AttributeTableBuilder();
   4:  
   5:     Assembly sourceAssembly = Assembly.LoadFile(@"C:\RehostedDesigner_DependentDlls\Microsoft.VisualStudio.Activities.dll");
   6:  
   7:     System.Resources.ResourceReader resourceReader = new System.Resources.ResourceReader(
   8:         sourceAssembly.GetManifestResourceStream("Microsoft.VisualStudio.Activities.Resources.resources"));
   9:  
  10:     foreach (Type type in typeof(System.Activities.Activity).Assembly.GetTypes().Where(t => t.Namespace == "System.Activities.Statements"))
  11:     {
  12:         CreateToolboxBitmapAttributeForActivity(builder, resourceReader, type);
  13:     }
  14:  
  15:     MetadataStore.AddAttributeTable(builder.CreateTable());
  16: }
  17:  
  18: private static void CreateToolboxBitmapAttributeForActivity(AttributeTableBuilder builder, System.Resources.ResourceReader resourceReader, Type builtInActivityType)
  19: {
  20:     System.Drawing.Bitmap bitmap = ExtractBitmapResource(resourceReader, builtInActivityType.IsGenericType ? builtInActivityType.Name.Split('`')[0] : builtInActivityType.Name);
  21:  
  22:     if (bitmap != null)
  23:     {
  24:         Type tbaType = typeof(System.Drawing.ToolboxBitmapAttribute);
  25:         Type imageType = typeof(System.Drawing.Image);
  26:         ConstructorInfo constructor = tbaType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { imageType, imageType }, null);
  27:         System.Drawing.ToolboxBitmapAttribute tba = constructor.Invoke(new object[] { bitmap, bitmap }) as System.Drawing.ToolboxBitmapAttribute;
  28:         builder.AddCustomAttributes(builtInActivityType, tba);
  29:     }
  30: }
  31:  
  32: private static System.Drawing.Bitmap ExtractBitmapResource(System.Resources.ResourceReader resourceReader, string bitmapName)
  33: {
  34:     System.Collections.IDictionaryEnumerator dictEnum = resourceReader.GetEnumerator();
  35:  
  36:     System.Drawing.Bitmap bitmap = null;
  37:     while (dictEnum.MoveNext())
  38:     {
  39:         if (String.Equals(dictEnum.Key, bitmapName))
  40:         {
  41:             bitmap = dictEnum.Value as System.Drawing.Bitmap;
  42:             System.Drawing.Color pixel = bitmap.GetPixel(bitmap.Width-1, 0);
  43:             bitmap.MakeTransparent(pixel);
  44:  
  45:             break;
  46:         }
  47:     }
  48:  
  49:     return bitmap;
  50: }

Using the sample code

Call LoadToolboxIconsForBuiltInActivities (code sample above) when you are loading up the rehosted Workflow Designer as shown below:

   1: private void LoadWorkflowDesigner(object workflow)
   2:     {
   3:     //creating a new designer
   4:     workflowDesinger = new WorkflowDesigner();
   5:     (new DesignerMetadata()).Register();
   6:     LoadToolboxIconsForBuiltInActivities();

Confirmation

Using the above approach the Workflow Activities in the rehosted Designer use the appropriate (VS/WF Designer) icons (Figure 4 - Standard Activities in Rehosted WF Designer have appropriate icons).

Figure 4 - Standard Activities in Rehosted WF Designer have appropriate icons

 
Note

This issue has been reported by some customers as WF4: No Toolbox-Bitmaps for standard activities and no resource bitmaps assignable. Due to the product schedule the engineering team is unable to deliver the icons in a re-distributable assembly in Visual Studio 10 release. For the interim the guidance provided is: get the XAML icon from the activity designer; take a WPF rectangle and fill with the drawing-brush (the icon), and then use a technique similar to the one in the link to convert the rectangle’s content to a bitmap http://www.west-wind.com/Weblog/posts/150676.aspx. Since the ‘WPF-Paintbrush’ approach is also applicable to custom Activity icons, our team plans to verify and document this approach as part 2 of this blog post.

  • Nigel Page, thank you for reporting the bug on the method 'CreateToolboxBitmapAttributeForActivity'.

    The sample code is updated (first line) as below:

    Bitmap bitmap = ExtractBitmapResource(resourceReader, builtInActivityType.IsGenericType ? builtInActivityType.Name.Split('`')[0] : builtInActivityType.Name);

    Thank you!

  • Here is some code to extract the icons from the System.Activities.Presentation DLL, which is distributable. I'm looking for performance tips on this as it's a bit slow.

    protected void LoadToolBox()

    {

    var dict = new ResourceDictionary {Source = new Uri("pack://application:,,,/System.Activities.Presentation;component/themes/icons.xaml")};

    Resources.MergedDictionaries.Add(dict);

    var builder = new AttributeTableBuilder();

    var standtypes = typeof(Activity).Assembly.GetTypes().

    Where(t => typeof(Activity).IsAssignableFrom(t) && !t.IsAbstract && t.IsPublic && !t.IsNested && t.HasDefaultConstructor());

    var smtypes = typeof(Receive).Assembly.GetTypes().

    Where(t => typeof(Activity).IsAssignableFrom(t) && !t.IsAbstract && t.IsPublic && !t.IsNested && t.HasDefaultConstructor());

    var types = typeof(FileToString).Assembly.GetTypes().

    Where(t => typeof(Activity).IsAssignableFrom(t) && !t.IsAbstract && t.IsPublic);

    var primary = new ToolboxCategory("Microsoft Primary");

    var secondary = new ToolboxCategory("Microsoft Secondary");

    foreach (var type in standtypes.OrderBy(t => t.Name))

    {

    var w = new ToolboxItemWrapper(type, type.ToGenericTypeString());

    if (AddIcon(type, builder))

    {

    secondary.Add(w);

    }

    else

    {

    primary.Add(w);

    }

    }

    var sm = new ToolboxCategory("Microsoft ServiceModel");

    foreach (var type in smtypes.OrderBy(t => t.Name))

    {

    AddIcon(type, builder);

    var w = new ToolboxItemWrapper(type, type.ToGenericTypeString());

    sm.Add(w);

    }

    var cat = new ToolboxCategory("Custom");

    foreach (var type in types.OrderBy(t => t.Name))

    {

    var w = new ToolboxItemWrapper(type, type.ToGenericTypeString());

    cat.Add(w);

    }

    MetadataStore.AddAttributeTable(builder.CreateTable());

    tbc.Categories.Add(primary);

    tbc.Categories.Add(secondary);

    tbc.Categories.Add(sm);

    tbc.Categories.Add(cat);

    }

    protected bool AddIcon(Type type, AttributeTableBuilder builder)

    {

    var secondary = false;

    var tbaType = typeof(ToolboxBitmapAttribute);

    var imageType = typeof(System.Drawing.Image);

    var constructor = tbaType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { imageType, imageType }, null);

    string resourceKey = type.IsGenericType ? type.GetGenericTypeDefinition().Name : type.Name;

    int index = resourceKey.IndexOf('`');

    if (index > 0)

    {

    resourceKey = resourceKey.Remove(index);

    }

    if (resourceKey == "Flowchart")

    {

    resourceKey = "FlowChart"; // it appears that themes/icons.xaml has a typo here

    }

    resourceKey += "Icon";

    Bitmap small, large;

    object resource = TryFindResource(resourceKey);

    if (!(resource is DrawingBrush))

    {

    resource = FindResource("GenericLeafActivityIcon");

    secondary = true;

    }

    var dv = new DrawingVisual();

    using(var context = dv.RenderOpen())

    {

    context.DrawRectangle(((DrawingBrush)resource), null, new Rect(0, 0, 32, 32));

    context.DrawRectangle(((DrawingBrush)resource), null, new Rect(32, 32, 16, 16));

    }

    var rtb = new RenderTargetBitmap(32, 32, 96, 96, PixelFormats.Pbgra32);

    rtb.Render(dv);

    using (var outStream = new MemoryStream())

    {

    BitmapEncoder enc = new PngBitmapEncoder();

    enc.Frames.Add(BitmapFrame.Create(rtb));

    enc.Save(outStream);

    outStream.Position = 0;

    large = new Bitmap(outStream);

    }

    rtb = new RenderTargetBitmap(16, 16, 96, 96, PixelFormats.Pbgra32);

    dv.Offset = new Vector(-32, -32);

    rtb.Render(dv);

    using (var outStream = new MemoryStream())

    {

    BitmapEncoder enc = new PngBitmapEncoder();

    enc.Frames.Add(BitmapFrame.Create(rtb));

    enc.Save(outStream);

    outStream.Position = 0;

    small = new Bitmap(outStream);

    }

    var tba = constructor.Invoke(new object[] { small, large }) as ToolboxBitmapAttribute;

    builder.AddCustomAttributes(type, tba);

    return secondary;

    }

    public static string ToGenericTypeString(this Type t)

    {

    if (!t.IsGenericType)

    return t.Name;

    string genericTypeName = t.GetGenericTypeDefinition().Name;

    genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));

    string genericArgs = string.Join(",", t.GetGenericArguments().Select(ToGenericTypeString));

    return genericTypeName + "<" + genericArgs + ">";

    }

    public static bool HasDefaultConstructor(this Type t)

    {

    return t.GetConstructors().Where(c => c.GetParameters().Length <= 0).Count() > 0;

    }

Page 1 of 1 (2 items)
Leave a Comment
  • Please add 7 and 1 and type the answer here:
  • Post