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.
- 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().
- 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]
Comments
Anonymous
June 24, 2010
HI, I am trying to rehost the CanvasActivity in a designer and whatever I do I can not get the drag drop to work. When I drag an activity over the Canvas the "can not drop" icon stays there and my OnDrop does not get called. OnDragEnter does get called but the icon stays and I can not drop? Any ideas why. I followed you example exactly. The only difference is I am hosting the CanvasDesigner in a rehosting app. ThanksAnonymous
June 28, 2010
- Is your drag event handler getting called?
- In drag events, are you modifying the effects correctly? e.Effects = (DragDropEffects.Move & e.AllowedEffects); Tim
Anonymous
June 28, 2010
Hi Tim, yes my drag events are called as I stated in my first post. And I am setting the e.Effects. I copied your code exactly and your code does this. Also, this code does get run. I played around and was actually able to get it to work using the OnPreviewDragEnter and OnPreviewDragOver events. When I set the effects in those callbacks it works??? I have a really nice flowchart designer in 3.5 using windows forms and it seems like a nightmare to try and rewrite it in WPF. I think Microsoft really made life difficult for those of us wanting to have our own freeform designer. Not to mention all the wasted work I have put into the 3.5 designer. This really is bad. Microsoft should at least provide the ability to subclass the current flowchart designer. How do I recreate the connectors and all that login for instance. This came for free in 3.5 Is there any help for me? ThanksAnonymous
July 06, 2010
From your description it sounds like there is probably a child control of your activity designer which does drag/drop event handling. When you change your handler OnDragEnter to OnPreviewDragEnter, everything starts working because Preview can intercept the routed event before it gets passed to that child control. If so, this is just the normal routed event behavior. TimAnonymous
July 21, 2010
Hi Tim Thanks for the post! I have a question for you... I have rehosted the workflow designer in an app that also contains a tree control. I want to be able to allow users to drag items from the tree control to the workflow designer. The object that I am dragging (ie a node of the tree) will implement IActivityTemplateFactory, so that when it is dropped on the workflow designer, an activity can be returned. Is what I am trying to do possible? If so, what should my DataObject passed to DoDragDrop be? I've tried: DataObject data = new DataObject(System.Activities.Presentation.DragDropHelper.WorkflowItemTypeNameFormat, treeView.SelectedItem); DragDrop.DoDragDrop(this.treeView, data, DragDropEffects.Move); where the 'treeView.SelectedItem' implements IActivityTemplateFactory, but this won't drop onto the WorkflowDesigner. Any help would be much appreciated. Regards NickAnonymous
July 22, 2010
Hi Nick, The way WorkflowItemTypeNameFormat works, it expects the DataObject holds a type name string suitable for passing to Type.GetType(), so you should pass the type name of an IActivityTemplateFactory class, not an instance of the class itself. TimAnonymous
July 22, 2010
Hi Tim Thanks for the response - I get where I am going wrong now! Regards NickAnonymous
August 30, 2010
The comment has been removedAnonymous
December 29, 2010
Hi Tim, I want to do similar things to what nick did, but the difference is the node of the tree represents an Activity object, I suppose I can do this using DragDropHelper.ModelItemDataFormat ? How? what is the DataObject?Anonymous
December 30, 2010
Hi Louis, I think it should work with ModelItemDataFormat. You will want to avoid adding (dragging) the same activity object instance to two different places in the workflow, because that is not valid for runtime. So make sure you 'new up' the activity instance each time. DataObject is just a technology (based on old OLE - object linking embedding) for placing multiple formats of data on the clipboard. Researching 'OLE IDataObject' on bing or google will get you some context. TimAnonymous
December 30, 2010
Hi Tim, Thanks for your reply. I posted my reply in the forums social.msdn.microsoft.com/.../a550aad5-a899-4fc7-a015-eab58900c85fAnonymous
May 24, 2012
Hi Tim, ICompositeView.OnItemMoved is also called when I try to move (=reposition) activities within the CanvasActivity. So I don't think removing it from the model is the right implementation. Best regards, Chris