Freeform Custom Activity Designers using ICompositeView (Part 4)

This post is Part 4 of a series on writing custom activity designers. [Part 1 - Part 2 - Part 3 - Part 4 - Part 5 - Part 6]

[UPDATE 01/20/2010: The old version of this post was woefully buggy, as drag and drop only worked for CanvasActivity inside something else, such as a Sequence. I’ve made updates and corrections, overwriting the old version.]

In Parts 1, 2, and 3 we figured out how to implement most of the functions of ICompositeView. Now Cut, Copy, Paste, Undo, Redo, and Delete are working for our custom designer. Next goal…

Supporting Drag and Drop

How does drag and drop work in WorkflowDesigner? Why it uses the standard WPF drag and drop API of course - System.Windows.DragDrop [msdn] [an even better tutorial site]. For workflow designer, there are a couple different data formats used which describe what is being dragged and dropped, depending on which action is being performed.

  1. Dragging an item from the toolbox. The data format (name) is string constant DragDropHelper.WorkflowItemTypeNameFormat . The data is a type name suitable for passing to Type.GetType().
  2. Dragging an activity from one place in the workflow to another. The data format is DragDropHelper.ModelItemDataFormat . The data is a ModelItem from the same EditingContext.

(Explanation: The data formats (data format names) are just public string constants which name the data in the DataObject being passed around and the format it should be in. In the case where existing activities are being moved, the there is also extra data on the DataObject such as where the activity got dragged from. Rather than access that extra data in a low-level way, System.Activities.Presentation.DragDropHelper can access it for us.)

For our ActivityCanvas it is not necessary to implement the 'Start Dragging' part of the scenario. Activities and Toolbox items already know how to start dragging themselves, and they can create the appropriate DataObject.  In fact, we only need to implement the ‘Drag Over’ and ‘Drop’ part of the drag+drop handling scenario, which we do by overriding the methods OnDragEnter, OnDragOver and OnDrop.

Note: before overriding any of those methods will work we also need to set one more property in our designer’s WPF XAML:

<sap:ActivityDesigner x:Class="ActivityLibrary1.CanvasDesigner"

   xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"

    AllowDrop="True">

    <Canvas Name="ContentCanvas" Width="500" Height="500" />

</sap:ActivityDesigner>

 

And at last, some drag and drop code…

    protected override void OnDragEnter(DragEventArgs e)

    {

        //Check the object is actually something we want to be droppable

        if (DragDropHelper.AllowDrop(

                e.Data,

                this.Context,

                typeof(Activity)))

        {

            e.Effects = (DragDropEffects.Move & e.AllowedEffects);

            e.Handled = true;

        }

        base.OnDragEnter(e);

    }

 

    protected override void OnDragOver(DragEventArgs e)

    {

        //Check the object is actually something we want to be droppable

        if (DragDropHelper.AllowDrop(

                e.Data,

                this.Context,

                typeof(Activity)))

        {

            e.Effects = (DragDropEffects.Move & e.AllowedEffects);

            e.Handled = true;

        }

        base.OnDragOver(e);

    }

   protected override void OnDrop(DragEventArgs e)

    {

        //droppedItem - may be a ModelItem or a newly instantiated object (from toolbox)

        object droppedItem = DragDropHelper.GetDroppedObject(this, e, this.Context);

        ModelItem canvasActivity = this.ModelItem;

        canvasActivity.Properties["Children"].Collection.Add(droppedItem);

        e.Handled = true;

        DragDropHelper.SetDragDropCompletedEffects(e, DragDropEffects.Move);

        base.OnDrop(e);

    }

 

So that’s drop basically working: We can drag activities into our CanvasActivity. For this blog post it still looks bad because our activities aren’t positioned on the canvas, we’ll fix that next time.

 

One last thing – what about dragging activities out of our CanvasActivity? This is where need to implement ICompositeView.OnItemMoved() . This is the designer framework’s way of telling us ‘someone wants to pull something out of you and put it somewhere else’, and it expects us to update the model tree correspondingly.

    void ICompositeView.OnItemMoved(ModelItem modelItem)

    {

        ModelItem canvasActivity = this.ModelItem;

        canvasActivity.Properties["Children"].Collection.Remove(modelItem);

    }

[Updated: 07/22/2010]