Someone asked an interesting question on the Windows Workflow Foundation forums regarding how to prevent a custom composite activity from being a direct/indirect child of itself, similar to how the TransactionScopeActivity does. However, he wanted to be able to control this in the WF designer, and not just using a custom activity validator like the TransactionScopeActivity does.

After looking a bit, I came to the conclusion that there's no 100% sure way of doing this using the current WF designer bits. The problem is that basically you get pretty good notifications in a composite activity designer when something happens to your children, but very little in the way of what happens further down the activity tree. That said, you can control some of the most common scenarios directly, and leave some corner cases to an ActivityValidator.

In particular, one can fairly easily cover the following scenarios:

  1. You can prevent the user from adding a direct child of the same type
  2. You can prevent the user from copy-pasting/dragging a direct composite activity child that contains an activity of the same as a direct/indirect child (i.e. you can prevent the user from dragging a sequence activity containing one of your activities into your activity).
  3. You can prevent an instance of your activity being added anywhere there's an activity of the same type as a parent/grandparent.

Here's a sample ActivityDesigner that covers these three scenarios tied to a sample activity called ParentActivity:

public class ParentActivityDesigner : SequenceDesigner

{

   /// <summary>

   /// Verify that the activities

   /// being inserted are not and don't contain

   /// a ParentActivity as a child.

   /// summary>

   /// <param name="insertLocation">param>

   /// <param name="activitiesToInsert">param>

   /// <returns>returns>

   public override bool CanInsertActivities(HitTestInfo insertLocation,

      System.Collections.ObjectModel.ReadOnlyCollection activitiesToInsert)

   {

      bool canDo = base.CanInsertActivities(insertLocation, activitiesToInsert);

      bool contains = ContainsParentActivity(activitiesToInsert);

      return canDo && !contains;

   }

 

 

   private bool ContainsParentActivity(IEnumerable activities)

   {

      bool found = false;

      foreach ( Activity act in activities )

      {

         if ( act is ParentActivity )

            found = true;

         else

         {

            CompositeActivity composite = act as CompositeActivity;

            if ( composite != null )

            {

               found = ContainsParentActivity(composite.Activities);

            }

         }

         if ( found )

            return true;

      }

      return false;

   }

 

   /// <summary>

   /// Verify that in the parent chain where

   /// we are going to be inserted there isn't any

   /// other ParentActivity anywhere

   /// summary>

   /// <param name="parentActivityDesigner">param>

   /// <returns>returns>

   public override bool CanBeParentedTo(CompositeActivityDesigner parentActivityDesigner)

   {

      bool canDo = base.CanBeParentedTo(parentActivityDesigner);

 

      Activity parent = parentActivityDesigner.Activity;

      while ( parent != null )

      {

         if ( parent is ParentActivity )

            return false;

         parent = parent.Parent;

      }

 

      return canDo;

   }

}

The code is fairly simple. We override the CanInsertActivities() method to check if any of the activities the user is trying to insert as a child of ourselves is a ParentActivity. However, we also go one step further and check if any of those activities contain directly or indirectly a child of type ParentActivity. With this, we cover scenarios 1 and 2 described above.

We also override CanBeParentedTo(), which allows us to cover scenario 3. by simply walking upwards on the activity tree (starting at the insert point) until we either get to the root or we find a ParentActivity (in which case we simply say no thanks).

 One particular scenario that is not covered by this, however, is a more complex case of scenario 2 above, and that's when the sequence is not being inserted as a direct child, but as an indirect child, such as inserting it into a sequence activity which is a child of our activity. The screenshot on the side shows this scenario, which is basically dragging the bottom sequence activity (which contains parentActivity2) into the sequence activity contained in parentActivity1 above. That sounds more complicated than it really is :-)


Tomas Restrepo

Software developer located in Colombia.