Creating Custom Composite Activities

When you are creating custom composite activities, you have several options when you design and create default logic for your custom composite activity.

Adding Design-Time Support to a Custom Composite Activity

If your activity derives from CompositeActivity and you want design-time support for your activity, you must set the DesignerAttribute to a derivative of CompositeActivityDesigner, such as ParallelActivityDesigner or SequentialActivityDesigner. Of course, you may also create your own composite activity designer by deriving directly from CompositeActivityDesigner.

For example, if you want to create a custom composite activity that mirrors the design-time experience of the ParallelActivity activity, you must add the following code to your custom activity definition:

<Designer(GetType(ParallelActivityDesigner), GetType(IDesigner))> _
Public Class CustomCompositeActivity
    Inherits CompositeActivity
    ' Define custom activity here.
End Class
[Designer(typeof(ParallelActivityDesigner), typeof(IDesigner))]
public class CustomCompositeActivity : CompositeActivity
{
    // Define custom activity here.
}

You can also use the SequentialActivityDesigner to create a custom activity that mirrors the design-time experience of the SequenceActivity activity, although an easier method is to derive your custom activity from SequenceActivity itself. Also, by having this activity as a base class for your custom activity, the default logic for scheduling the execution of activities in a sequential manner is provided for you.

Conversely, if you want to hide the internal structure of your custom composite activity, such as one derived from SequenceActivity, you should set the DesignerAttribute to ActivityDesigner because this type does not have design-time support for composite activities.

Adding Child Activities to a Custom Composite Activity

You can choose how to add child activities to your custom activity. There are two methods that you can use to add child activities to your custom composite activity:

  • Deriving the custom activity from SequenceActivity.

  • Deriving the custom activity from CompositeActivity.

The CompositeActivity activity does not contain default logic for handling child activities. If you want to create a custom composite activity, you must still override Execute to handle the execution of your child activities and Cancel to cancel your child activities and close your custom activity.

The following example shows how to add CodeActivity activities to a custom composite activity that behaves like a ParallelActivity activity. You must also inherit from IActivityEventListener to be notified when the Closed and Executing events on a child activity are raised.

<Designer(GetType(ParallelActivityDesigner), GetType(IDesigner))> _
Public Class CustomCompActivity
    Inherits CompositeActivity
    Implements IActivityEventListener(Of ActivityExecutionStatusChangedEventArgs)

    ' Declare variables.
    Private ca1 As CodeActivity
    Private ca2 As CodeActivity
    Private ca3 As CodeActivity
    Private ca4 As CodeActivity
    Private index As Integer

    Public Sub New()
        InitializeComponent()
    End Sub

    ' Initialize your child activities and add them to your custom 
    ' composite activity.
    Private Sub InitializeComponent()
        Me.CanModifyActivities = True
        Me.ca1 = New CodeActivity("CodeActivity1")
        Me.ca2 = New CodeActivity("CodeActivity2")
        Me.ca3 = New CodeActivity("CodeActivity3")
        Me.ca4 = New CodeActivity("CodeActivity4")
        AddHandler ca.ExecuteCode, AddressOf ca_ExecuteCode
        AddHandler ca2.ExecuteCode, AddressOf ca_ExecuteCode
        AddHandler ca4.ExecuteCode, AddressOf ca_ExecuteCode
        AddHandler ca4.ExecuteCode, AddressOf ca_ExecuteCode
        Me.Activities.Add(ca1)
        Me.Activities.Add(ca2)
        Me.Activities.Add(ca3)
        Me.Activities.Add(ca4)

        ' Cache index value of last child activity in the collection.
        index = Me.Activities.Count - 1
        Me.CanModifyActivities = False
    End Sub

    Protected Overrides Function Execute(ByVal ExecutionContext As ActivityExecutionContext) As ActivityExecutionStatus
        If (ExecutionContext Is Nothing) Then
            Throw New ArgumentNullException("ExecutionContext")
        End If

        ' If there are no child activities initialized, then close 
        ' the parent activity.
        If (Me.EnabledActivities.Count = 0) Then
            Return ActivityExecutionStatus.Closed
        End If


        ' Cycle through all of the enabled child activities in your 
        ' activity, register for their Closed and Executing events, 
        ' and then run the child activity.
        Dim i As Integer
        For i = 0 To Me.EnabledActivities.Count - 1 Step +1
            Dim ChildActivity As Activity = Me.EnabledActivities(i)
            ChildActivity.RegisterForStatusChange(Activity.ClosedEvent, Me)
            ChildActivity.RegisterForStatusChange(Activity.ExecutingEvent, Me)
            ExecutionContext.ExecuteActivity(ChildActivity)
        Next

        Return ActivityExecutionStatus.Executing
    End Function

    ' This event handler is used for the ExecuteCode event that 
    ' each CodeActivity activity will raise during its execution.
    Private Sub ca_ExecuteCode(ByVal sender As Object, ByVal e As EventArgs)
        Console.WriteLine("In event handler for {0}.ExecuteCode event...", CType(sender, Activity).Name)
    End Sub

    ' Implement the IActivityEventListener.OnEvent method to 
    ' display the current activity state and close your 
    ' parent custom activity when all of the child activities are
    ' are either in the Initialized or Closed state. Otherwise, keep
    ' the parent activity running.
    Sub OnEvent(ByVal Sender As Object, ByVal E As ActivityExecutionStatusChangedEventArgs) Implements IActivityEventListener(Of System.Workflow.ComponentModel.ActivityExecutionStatusChangedEventArgs).OnEvent

        If (Sender Is Nothing) Then
            Throw New ArgumentNullException("Sender")
        End If

        If (E Is Nothing) Then
            Throw New ArgumentNullException("E")
        End If

        Dim Context As ActivityExecutionContext = CType(Sender, ActivityExecutionContext)

        If (Context Is Nothing) Then
            Throw New ArgumentException("The sender must be an ActivityExecutionContext object", "Sender")
        End If

        Dim CustActivity As CustomCompActivity = CType(Context.Activity, CustomCompActivity)

        ' Display the current state of the child activity.
        Console.WriteLine("{0} is in the {1} state.", E.Activity.Name, E.ExecutionStatus)

        ' As a good coding practice, unregister for the Closed event if 
        ' the child activity is in the closed state.
        If (E.Activity.ExecutionStatus = ActivityExecutionStatus.Closed) Then
            E.Activity.UnregisterForStatusChange(Activity.ClosedEvent, Me)
        End If

        ' Check whether the activity passed into the payload of the
        ' ActivityExecutionStatusChangedEventArgs object is the last 
        ' child activity and if it is in either the Initialized state
        ' or Closed state before closing the parent activity.
        If (E.Activity.Equals(CustActivity.EnabledActivities(index)) And (E.ExecutionStatus = ActivityExecutionStatus.Closed Or E.ExecutionStatus = ActivityExecutionStatus.Initialized)) Then

            ' The child activities are now in a closed state, so it is 
            ' safe to put your custom activity in a closed state as well.
            Context.CloseActivity()
        End If
    End Sub
End Class
[Designer(typeof(ParallelActivityDesigner), typeof(IDesigner))]
public class CustomCompActivity : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
{
    // Declare variables.
    private CodeActivity ca1;
    private CodeActivity ca2;
    private CodeActivity ca3;
    private CodeActivity ca4;
    private int index;

    public CustomCompActivity()
    {
        InitializeComponent();
    }

    // Initialize your child activities and add them to your custom 
    // composite activity.
    private void InitializeComponent()
    {
        this.CanModifyActivities = true;
        this.ca1 = new CodeActivity("CodeActivity1");
        this.ca2 = new CodeActivity("CodeActivity2");
        this.ca3 = new CodeActivity("CodeActivity3");
        this.ca4 = new CodeActivity("CodeActivity4");
        ca1.ExecuteCode += new EventHandler(ca_ExecuteCode);
        ca2.ExecuteCode += new EventHandler(ca_ExecuteCode);
        ca3.ExecuteCode += new EventHandler(ca_ExecuteCode); 
        ca4.ExecuteCode += new EventHandler(ca_ExecuteCode); 
        this.Activities.Add(ca1);
        this.Activities.Add(ca2);
        this.Activities.Add(ca3);
        this.Activities.Add(ca4);

        // Cache index value of last child activity in the collection.
        index = this.Activities.Count - 1;
        this.CanModifyActivities = false;
    }

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
        if (executionContext == null)
            throw new ArgumentNullException("executionContext");

        // If there are no child activities initialized, then close 
        // the parent activity.
        if (this.EnabledActivities.Count == 0)
        {
            return ActivityExecutionStatus.Closed;
        }

        // Cycle through all of the enabled child activities in your 
        // activity, register for their Closed event, and then run the
        // child activity.
        for (int i = 0; i < this.EnabledActivities.Count; ++i)
        {
            Activity childActivity = this.EnabledActivities[i];
            childActivity.RegisterForStatusChange(Activity.ClosedEvent, this);
            childActivity.RegisterForStatusChange(Activity.ExecutingEvent, this);
            executionContext.ExecuteActivity(childActivity);
        }

            return ActivityExecutionStatus.Executing;
    }

    // This event handler is used for the ExecuteCode event that 
    // each CodeActivity activity will raise during its execution.
    void ca_ExecuteCode(object sender, EventArgs e)
    {
        Console.WriteLine ("In event handler for {0}.ExecuteCode event...", ((Activity)sender).Name);
    }

    // Implement the IActivityEventListener.OnEvent event handler to 
    // display the current activity state and close your parent custom
    // activity when all of the child activities are either in the 
    // Initialized or Closed state. Otherwise, keep the parent activity 
    // running.
    public void OnEvent(object sender, ActivityExecutionStatusChangedEventArgs e)
    {
        if (sender == null)
            throw new ArgumentNullException("sender");
        if (e == null)
            throw new ArgumentNullException("e");

        ActivityExecutionContext context = sender as ActivityExecutionContext;

        if (context == null)
            throw new ArgumentException("The sender must be an ActivityExecutionContext object", "sender");

        CustomCompActivity custActivity = context.Activity as CustomCompActivity;

        // Display the current state of the child activity.
        Console.WriteLine ("{0} is in the {1} state.", e.Activity.Name, e.ExecutionStatus);

        // As a good coding practice, unregister for the Closed event 
        // if the child activity is in the closed state.  
        if (e.Activity.ExecutionStatus == ActivityExecutionStatus.Closed)
        {
            e.Activity.UnregisterForStatusChange(Activity.ClosedEvent, this);
        }

        // Check whether the activity passed into the payload of the
        // ActivityExecutionStatusChangedEventArgs object is the last 
        // child activity and if it is in either the Initialized state
        // or Closed state before closing the parent activity.
        if (e.Activity.Equals(custActivity.EnabledActivities[index]) && (e.ExecutionStatus == ActivityExecutionStatus.Closed || e.ExecutionStatus == ActivityExecutionStatus.Initialized)) 
        {
            // The child activities are now in a closed state, so it is 
            // safe to put your custom activity in a closed state as 
            // well.
            context.CloseActivity();
        }
    }
}

By using the previous example, your custom activity will be locked when it is used inside a workflow designer. If you want to provide a custom activity that can have additional child activities added to it within the workflow designer, you must create a custom class that derives from ActivityToolboxItem. Note that the data you have added is still locked. Deriving from ActivityToolboxItem merely gives users the ability to add additional data; they cannot modify what is already within the custom activity.

Custom Toolbox Items

The following code example shows a custom toolbox item for a custom composite activity. The toolbox item adds a CodeActivity activity to the custom composite activity.

<Serializable()> _
Public Class SampleToolboxItem
    Inherits ActivityToolboxItem
    Protected Overrides Function CreateComponentsCore(ByVal designerHost As IDesignerHost) As IComponent()
        Dim CompActivity As CustomCompositeActivity = New CustomCompositeActivity()
        CompActivity.Activities.Add(New CodeActivity())
        CompActivity.Activities.Add(New CodeActivity())
        Return (New IComponent() {CompActivity})
    End Function
End Class
[Serializable]
public class SampleToolboxItem: ActivityToolboxItem
{
    protected override IComponent[] CreateComponentsCore(IDesignerHost designerHost)
    {
        CustomCompositeActivity CompActivity = new CustomCompositeActivity();
        CompActivity.Activities.Add(new CodeActivity());
        CompActivity.Activities.Add(new CodeActivity());
        return new IComponent[] {CompActivity};
    }
}

When you are using SampleToolboxItem with your custom composite activity, you must declare it through the ToolboxItemAttribute as shown in the following example.

<ToolboxItem(GetType(SampleToolboxItem))> _
[ToolboxItem(typeof(SampleToolboxItem))]

As noted in a previous section, if you need the designer for your custom composite activity to show child activities, such as the CodeActivity activities in this example, your custom activity must be associated with a composite activity designer like ParallelActivityDesigner or with a custom designer deriving from any of the base composite activity designer classes.

See Also

Reference

CompositeActivity
SequenceActivity
CompositeActivityDesigner
ParallelActivityDesigner
SequentialActivityDesigner
ActivityToolboxItem

Concepts

Creating Custom Activities
Understanding the Activity Execution Context
Understanding the Activity State Model

Other Resources

Windows Workflow Foundation Activities