locked
Designer Rehosting, and Toolbox Items BitmapName property RRS feed

  • Question

  • Hello, 

    I am trying to add my custom icons on the toolbox items that I added on my re-hosted workflow designer

    I have both standard statements as well as custom ones, and I am wondering how to specify my own icon as Visual Studio does. 

    Since I am adding the toolbox items programmatically, I need to specify something on the BitmapName property of the ToolboxItemWrapper class.

    I tried also to dynamically define the ToolboxBitmapAttribute attribute by using the AttributeTableBuilder, but without results.

    This is the sample code  I use:

            Dim activityEnumerator As ActivityEnumerator = activityEnumerator.Current
            Dim atb As New Metadata.AttributeTableBuilder()

            For Each category As String In activityEnumerator.Categories
                Dim toolboxCategory As New ToolboxCategory(category)

                For Each item As ActivityEnumerator.ActivityEnumeratorItem In activityEnumerator.GetActivitiesByCategory(category)
                    Dim toolboxItem As New ToolboxItemWrapper()

                    ' obtain type by name
                    toolboxItem.ToolName = item.Type.FullName
                    toolboxItem.AssemblyName = item.Assembly.GetName().ToString()
                    toolboxItem.DisplayName = item.Activity.Description
                    'toolboxItem.BitmapName = ... 

                    ' map the toolbox bitmap by linking the specified "bitmapName" information (eg "Resources.MyImage.png")
                    atb.AddCustomAttributes(item.Type, New System.Drawing.ToolboxBitmapAttribute(item.ActivityProvider.GetType(), item.Activity.BitmapName))
                    
                    toolboxCategory.Add(toolboxItem)
                Next

                toolbox.Categories.Add(toolboxCategory)
            Next

            ' associate bitmaps.
            Metadata.MetadataStore.AddAttributeTable(atb.CreateTable())
    ...

    However, when the toolbox renders, the output still remains with the default "gear" on all the items.

    Any hint on doing this kind of customization?

    Thank you for help,
    Cheers

    Adriano

    Adriano
    Thursday, November 5, 2009 3:36 PM

Answers

  • Hello Adriano,

    To load bitmap from resource, you can override ToolboxItem, load the bitmap in the default constructor. Then, use ToolboxItemAttribute to specify the toolbox item on the activity. However, for each toolbox item, you will need derive such a type for loading the bitmap resource.

    Best Regards,

    Leo

    This posting is provided "AS IS" and confers no rights or warranties.

    Saturday, November 7, 2009 6:04 AM
  • BitmapName is the path to the bitmap file, so you will need to specify the path to the image file.

    Best Regards,

    Leo


    This posting is provided "AS IS" and confers no rights or warranties.
    Friday, November 6, 2009 4:56 AM

All replies

  • BitmapName is the path to the bitmap file, so you will need to specify the path to the image file.

    Best Regards,

    Leo


    This posting is provided "AS IS" and confers no rights or warranties.
    Friday, November 6, 2009 4:56 AM
  • Thank you Leo for your answer.

    However I am wondering if possible to provide a resource instead of a physical path to the image file? Basically I would like to include my images directly as resources of the assembly where I implement the activities themselves.

    Thank you in advance!
    Cheers
    Adriano


    Adriano
    Friday, November 6, 2009 8:08 AM
  • Hello Adriano,

    To load bitmap from resource, you can override ToolboxItem, load the bitmap in the default constructor. Then, use ToolboxItemAttribute to specify the toolbox item on the activity. However, for each toolbox item, you will need derive such a type for loading the bitmap resource.

    Best Regards,

    Leo

    This posting is provided "AS IS" and confers no rights or warranties.

    Saturday, November 7, 2009 6:04 AM
  • Thank you Leo, it definitely works!
    Adriano
    Friday, November 13, 2009 10:00 PM
  • The problem here is, if you want to have a resource bitmap for standard activities from system.activities most of them are sealed. What can I do to load the toolbox bitmap from resource for standard activities (i.e. If)?

    Thomas

    Sunday, November 15, 2009 8:52 AM
  • Hi Thomas, did you try to add the Attribute through Metadata table as I was doing above? However, by adding the "ToolboxItem" attribute and NOT the "ToolboxBitmap" attribute ? I know this one works for Toolbox Items to associate the activity "Designer", it could work also to add this "ToolboxItem" class which, in turn, specifies the "Bitmap" on the constructor.

    Cheers,
    Adriano
    Monday, November 16, 2009 9:43 AM
  •  

    Hi Adriano,

    Thank you for your tip. I tried it before in a different way by using

    TypeDescriptionProvider tdp = System.ComponentModel.TypeDescriptor.AddAttributes(wrapper.Type, new ToolboxItemAttribute((typeof(MyToolboxItem))));

     

    Unfortunately it didn’t work. Also with MetadataStore.AddAttributeTable I can’t see my Bitmap. I defined a MyToolboxItem and assigned my Bitmap in the constructor. Binding the attribute [ToolboxItem(typeof(MyToolboxItem))] statically on a MyIf activity did the job but this is not possible for the sealed standard activities.

    With reflector I analyzed ToolboxItemWrapper.ResolveToolboxItem() and found out, that the activity type gets loaded over its name and directly after this the attributes are retrieved:

     

      Type toolType = Assembly.Load(this.AssemblyName).GetType(this.ToolName, true, true);

      this.ValidateTool(toolType);

      ToolboxItemAttribute[] customAttributes = ToolType.GetCustomAttributes(typeof(ToolboxItemAttribute), true) as ToolboxItemAttribute[];

     

    I do not fully understand how the attributes get bound to types at runtime but perhaps it’s a problem, that ToolboxItemWrapper.ResolveToolboxItem() creates a new “type instance” of the activity. That’s why binding the attributes to the standard If-activity is useless. Using the wrapper.Type is not possible either, because the attributes are read directly after creating the type.

     

    Here is my code derived from yours:

    Toolbox.Categories.Add(new ToolboxCategory("standard activities"));

    ToolboxItemWrapper wrapper = new ToolboxItemWrapper(typeof(If)); //Using MyIf here works

    //It would be nice to set the attribute to wrapper.Type here, but ResolveToolboxItem() is not called yet

    Toolbox.Categories[1].Add(wrapper);

               

    //System.ComponentModel.TypeDescriptor.AddAttributes(wrapper.Type, new ToolboxItemAttribute((typeof(MyToolboxItem))));

                            

    //map the toolbox bitmap by linking the specified "bitmapName" information (eg "Resources.MyImage.png")

    AttributeTableBuilder atb = new AttributeTableBuilder();

    atb.AddCustomAttributes(wrapper.Type, //using typeof(If) doesn’t work too, even if I do this before the wrapper gets created

    new ToolboxItemAttribute((typeof(MyToolboxItem))));

    MetadataStore.AddAttributeTable(atb.CreateTable());

     

    Is this solvable or a design bug in beta2?

     

    Greetings, Thomas

    Monday, November 16, 2009 12:42 PM
  • Dear Thomas, I think that this is currently a bug/limitation of this beta.
    Microsoft should look into for the final release. 
    I think that for seales activities Microsoft is somehow overriding the attributes.

    However attribute table can be used with success for adding the custom designer, always talking about custom activities... I never tried with sealed ones, also because normally one would not like to rewrite them, normally :)

    Cheers,
    Adriano





    Adriano
    Monday, November 16, 2009 5:02 PM
  • I would appreciate it very much if microsoft just uses the standard vector DrawingBrush icons as toolbox bitmaps so that we would never have to deal with this stuff in the final release :)
    Perhaps I should open a new question for this so that a MSFT guy is engaged, what do you think?
     
    However - thank you for your time,

    Thomas
    Monday, November 16, 2009 7:18 PM
  • Dear Thomas, I hope that the current behavior is something that is on the list of stuff to fix! However, you may post it as a bug on MS Connect. I can vote for it, since I need the same in the near future  :)

    Ciao!


    Adriano
    Monday, November 16, 2009 10:13 PM
  • The bitmap icons are in the Microsoft.VisualStudio.Activities' resources. Why dont you extract it from there and then apply it through the constructor of the ToolboxItemWrapper?

    I know this is some code to extract the bitmaps from the resources. But certainly is straight forward.

    Also, in the meantime we are working hard to make this icons available publicly through a re-distributable. But that might take a few more days.

    Thanks,
    Kushal. 


    Kushal Shah - This posting is provided "AS IS" with no warranties, and confers no rights
    • Proposed as answer by kushals Tuesday, November 24, 2009 1:29 AM
    Tuesday, November 24, 2009 1:29 AM
  • Hi Kushal, is you are right it can be done also quickly, however in my case I am rehosting the designer and thus I guess I cannot use that assembly? I mean, I think this is not redistributable, right?

    Thank you!

    Adriano
    Tuesday, November 24, 2009 8:00 AM
  • Microsoft.VisualStudio.Activities is VS specific. I understand that point.

    But you can also use System.Activities.Presentation resources that have the Xaml icons. Though converting them to BitMap wont be as good looking, would it be  adecent workaround till we get our redistributable story figured out?\

    Thanks,
    Kushal.
    Kushal Shah - This posting is provided "AS IS" with no warranties, and confers no rights
    • Proposed as answer by kushals Tuesday, November 24, 2009 6:48 PM
    Tuesday, November 24, 2009 6:48 PM
  • Hi Kushal

    please let me answer your question "Why dont you extract it from there and then apply it through the constructor of the ToolboxItemWrapper?" The constructor of the ToolboxItemWrapper only accepts BitmapName as argument. The Bitmap property is readonly. Adriano and I don't want to deliver bitmap files with our application. We want to load the bitmaps (or DrawingBrushes) from resource.
    If you do not have a special trick to draw to a readonly bitmap instance (this is a way I can imagine) I cannot see how you apply resource bitmaps through the constructor of the ToolboxItemWrapper.

    Please take the time to read through our problem descriptions (especially my post on Monday, November 16, 2009 12:42 PM) and don't just mark my feedback item 511401 as not reproducible. I spent some time on beta2 and see a design problem that does not allow us to change the gear symbol on standard activities without using bitmap files. Perhaps I miss something obvious.

    kind regards
    Thomas
    Tuesday, November 24, 2009 9:07 PM
  • Hi Thomas -

    I havent played with icons a lot. But are you saying there is no way to cast the Xaml icon passed from the resource file for S.A.P to a bitmap so that it can passed in to the ToolboxItemWrapper constructor?

    If so, even if we deliver hte icons as a re-dist you would have to incorporate that into your re-hosted application(maybe as a resource there).

    Thanks,
    Kushal.
    Kushal Shah - This posting is provided "AS IS" with no warranties, and confers no rights
    Wednesday, November 25, 2009 7:37 PM
  • Hi Kushal,

    I am really sorry that it seems as if I can not make clear what my problem is, so I try it in simple code examples:

    There are 2 ctors of ToolboxItemWrapper of interest:
    public ToolboxItemWrapper(string toolName, string assemblyName, string bitmapName, string displayName)
    public ToolboxItemWrapper(Type toolType, string bitmapName, string displayName)

    Both only accepting a bitmapName argument. This string will be passed to a Bitmap constructor in private void ToolboxItemWrapper.LoadBitmap():

    new Bitmap(this.BitmapName);

    where  the only ctor of Bitmap which takes a string uses this argument as a filename. (not i.e. resource path --> this would be cool)

    So please tell me (perhaps as a short code example) how to pass a bitmap to the ToolboxItemWrapper constructor without using bitmap files...

    Then I surely could convert or paint every Bitmap I want to a resource bitmap and assign it.

    Hey and sorry - I am really thankful for any help.

    Thomas
    Wednesday, November 25, 2009 10:39 PM
  • It's been a while since the last post here, but to finish the discussion and to show anybody how to do it, here some magic from msdn blogs:
    http://blogs.msdn.com/asgisv/archive/2010/02/10/displaying-net-framework-4-built-in-workflow-activity-icons-in-a-rehosted-workflow-designer.aspx

    We were really close. The trick is to still use the ToolboxBitmapAttribute instead of ToolboxItemAttribute but with the magic to get a private constructor for the attribute and invoke it. Then it makes no difference wich way you use to get the type variable to pass to builder.AddCustomAttributs and where do get the bitmap from.
    Here is the interesting part of the blog post:

    Type tbaType = typeof(System.Drawing.ToolboxBitmapAttribute); 

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

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

    System.Drawing.ToolboxBitmapAttribute tba = constructor.Invoke(new object[] { bitmap, bitmap }) as System.Drawing.ToolboxBitmapAttribute; 

    builder.AddCustomAttributes(builtInActivityType, tba);

    MetadataStore.AddAttributeTable(builder.CreateTable());

     

    • Proposed as answer by tweigmann Thursday, February 11, 2010 10:45 PM
    Thursday, February 11, 2010 10:44 PM
  • Magic find! I have implemented something similar this to get our own custom activities showing from within embedded resources.

    Thursday, February 25, 2010 1:31 AM
  • Here's a more complete code sample that pulls the icons from the presentation assembly. It's a bit slow, so I'm accepting ideas on performance. Also, it appears that the themes file has a typo on the Flowchart activity. I'm not sure if they have any other typos.


     

    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;
    		}
    

     

    Wednesday, August 11, 2010 8:22 PM
  • I've just come up with another solution which needs far less code, and makes use of the XAML icons. See below for details.

    http://blogs.msdn.com/b/morgan/archive/2011/03/15/using-standard-icons-a-rehosted-workflow-designer.aspx

    Cheers,

    Morgan

    Tuesday, March 15, 2011 2:17 PM