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

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

The issue is not trivial since there are over 40 Built-In Activities https://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).

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).

 
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 https://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.