Udostępnij za pośrednictwem


Creating multiple activities from the toolbox

This blog post will show you how you can add a custom tool to the toolbox which, when selected, will drop a custom activity onto the designer surface. In addition I’ll also show how to get non-activity classes onto the toolbox. There are many times when you might want to do this – a couple are outlined below…

  • You wish to drop a number of related activities, in a similar manner to when a Parallel is created and within it two branches are created for you
  • You have an item which is not an activity that you would like to drop onto the designer surface (such as the PickBranch)

When you create custom activities, Visual Studio will automagically add these to the toolbox (as long as they are within the same solution file). When you drag and drop an activity this activity is added to the designer. You can however augment this behaviour by implementing the IActivityTemplateFactory interface, which is used in preference to construction of the actual activity.

So, you can create a placeholder activity that shows up on the toolbox, but when it’s dropped it constructs an entirely different sent of activities on the designer surface. All you do is implement the Create method of IActivityTemplateFactory and within this you can then create any activities (and optionally hook these together as you need).

First up create a Workflow Console Application and then add an Activity Library project to the same solution. You can remove the Activity1.xaml file as we’ll not need that. Then add a reference to both the WindowsBase and also System.Activities.Presentation assemblies.

Next you can code up the Create method. An example is shown below…

 public class Dropper : Activity, IActivityTemplateFactory
{
    public Activity Create(DependencyObject target)
    {
        Pick pick = new Pick();
        PickBranch item = new PickBranch();
        item.DisplayName = "Branch1";
        pick.Branches.Add(item);
        PickBranch branch2 = new PickBranch();
        branch2.DisplayName = "Branch2";
        pick.Branches.Add(branch2);
        return pick;
    }
}

I shamelessly stole the code above from the PickWithTwoBranchesFactory class from the framework. Have a look in Reflector to find all classes that derive from IActivityTemplateFactory to get some ideas as to what the inbuilt factories do.

When you compile up your project you’ll find the ‘Dropper’ activity on the toolbox, and when you drag & drop an instance of this activity you’ll get a Pick with two branches. Sorted.

Well, sort of.

I mentioned at the start of the article that I’d show how to drop not only a bunch of related activities, but also something that doesn’t derive from the Activity class. Your first thought might be to change the Create method and return (for example) a PickBanch, however there’s a problem. The return type is defined as Activity, so using this method to add non-Activity derived classes isn’t going to work.

Stage 2 – Adding non-Activity classes to the toolbox

If you are hosting the Workflow Designer yourself then this is as easy as adding anything else to the toolbox – you simply get hold of the ToolboxControl and add a new tool to it…

 private void InitializeToolbox()
{
    ToolboxControl toolbox = new ToolboxControl();
    ToolboxCategory cat = new ToolboxCategory("Activities");
    cat.Tools.Add(new ToolboxItemWrapper(typeof(Sequence), "Sequence"));
    cat.Tools.Add(new ToolboxItemWrapper(typeof(If), "If/Else"));
    cat.Tools.Add(new ToolboxItemWrapper(typeof(Assign), "Assign"));
    cat.Tools.Add(new ToolboxItemWrapper(typeof(Message), "MessageBox"));
    cat.Tools.Add(new ToolboxItemWrapper(typeof(Pick), "Pick"));
    cat.Tools.Add(new ToolboxItemWrapper(typeof(PickBranch), "PickBranch"));
    toolbox.Categories.Add(cat);

    this.SetValue(ref _toolbox, toolbox, "Toolbox");
}

Here I’ve added a bunch of activities and also a PickBranch (which isn’t an Activity) to my toolbox. Simple.

The problem comes however when you want to do this within Visual Studio. The Workflow folks have very nicely integrated any custom activities into the toolbox so they show up as if by magic, but in order to get something onto the toolbox that isn’t an Activity you have to do a bit more work.

Stage 3 – Adding non-Activity classes to the toolbox in Visual Studio

There are currently (as far as I know!) two ways to do this. One is somewhat more involved than the other so I’ll present the easier of the two here. The first way is to create a Visual Studio Extensibility project which, when registered with Visual Studio, adds new items to the toolbox. The code for this is a bit on the gnarly side and there’s already an example of this available online (see the State Machine activity pack available on codeplex) so instead I’ll do the cheap & cheerful approach.

In the second case what you can do is update the toolbox in Visual Studio from within the activity designer code itself. The trick (if you can call it that) in this instance is to add code to your activity designer that updates the toolbox when your main activity is added to the designer. There’s a nice managed interface that you can use from within your code which is IActivityToolboxService.

What you need to do here is create an activity, add a designer to it, and within the designer you can then hook events (in particular the addition of the activity to the design surface), and at this point you can then add your other items to the toolbox.

In my example I’ve create a DayScheduler activity, which when dropped onto the design surface constructs two branches for you as I’ve also implemented IActivityTemplateFactory to construct these brnaches and add them as well…

DayScheduler

The activity contains a number of branches similar to the Parallel activity – indeed I modelled most of the code on that activity as mine is very similar. When you drop the DayScheduler onto the designer it constructs two braches which are instances of the DaySchedulerBranch class. This class isn’t an activity – it’s simply a class derived from Object (in the same way as ParallelBranch is).

There' are two mandatory properties on the DayScheduleBranch – Action (an Activity) and ScheduleDays (an enum). This second property defines which day of the week the child activity will be scheduled – so the DayScheduler will execute children (pun intended) based on the current day of the week. In the example code when I add a DayScheduler activity I construct two branches – one that executes on weekdays only and another that executes on weekends only. You can add in any number of branches and select which days the child activities execute on. I defined an enum for this containing individual weekdays, and used the [Flags] attribute to also include all weekdays and both weekend days to make your life easier when selecting which day(s) your child activities should run.

The main point of the code is to show how, within Visual Studio, you can add non-Activity classes to the toolbox. This is done within the designer for the DayScheduler activity and the main parts of this designer class are shown below…

 /// <summary>
/// This designer updates the toolbox to add in a new tool as necessary
/// </summary>
public partial class DaySchedulerDesigner
{
    public DaySchedulerDesigner()
    {
        InitializeComponent();

        // Use the same cateory name as the assembly
        _category = this.GetType().Assembly.GetName().Name;
    }

    /// <summary>
    /// Hook the property change notification
    /// </summary>
    /// <param name="newItem"></param>
    protected override void OnModelItemChanged(object newItem)
    {
        base.OnModelItemChanged(newItem);

        if (null == _toolbox)
            _toolbox = this.Context.Services.GetService<IActivityToolboxService>();

        AddToolboxItem();
    }

    /// <summary>
    /// Add the tool to the toolbox
    /// </summary>
    private void AddToolboxItem()
    {
        bool exists = _toolbox.EnumCategories().Contains(_category);

        if (!exists)
            _toolbox.AddCategory(_category);

        string _toolName = typeof(DaySchedulerBranch).AssemblyQualifiedName;
        bool toolExists = _toolbox.EnumItems(_category).Contains(_toolName);

        if (!toolExists)
        {
            _toolbox.AddItem(_toolName, _category);
        }
    }

    /// <summary>
    /// Stores a link to the toolbox
    /// </summary>
    private IActivityToolboxService _toolbox;

    /// <summary>
    /// Defines the category that this tool is added to
    /// </summary>
    private string _category;
}

The code overrides the OnModelItemChanged method which calls AddToolboxItem(). OnModelItemChanged is called when you drop the DayScheduler activity onto the designer, and here I obtain a reference to the IActivityToolboxService interface which is used to add/enumerat and delete items from the toolbox.

Within AddToolboxItem I then check if the category and tool have already been defined, and if not simply call the relevant methods to add the tool to the toolbox.

So, within Visual studio you’ll initially see just the DayScheduler activity within the toolbox, however once you’ve dropped an instance of this onto the designer you’ll then see the following in the toolbox…

DaySchedulerBranch

You can then of course use this to drop an instance of the branch activity onto the design surface. Voila!

The Download

I’ve create a VS2010 project that includes all of the code for you to look at. Please download from here. If the code works it was written by me, if not I don’t know who wrote it.