I am a big fan of attached properties and I think it is one of the coolest innovations in WPF. Once you understand the basics of using and creating attached properties, lot of the common activities that typically require some code-behind can be pushed to a class that implements an attached property or a set of attached properties. A good example is the PanelLayoutAnimator from Dan Crevier’s blog.
In this post I’ll try to explain how I have used attached properties to encapsulate the Drag and Drop (DnD) behavior. To have DnD functionality you typically have to subscribe to a set of Drag and Drop events, like DragEnter, DragLeave, DragOver, Drop. These events get fired when you invoke the DragDrop.DoDragDrop() API. Marcelo has done a great job explaining how you can use the DnD behavior in your own WPF apps and I would recommend reading his blog posts. It was certainly a starting point for me! But as I read his posts, I realized that most of the DnD plumbing can be encapsulated into a class and one can provide a much simpler model to attach DnD behavior to controls.
The source of the idea
This idea came to me because in my past life I was an Eclipse developer working on some cool stuff like <AcronymnAlert> GEF, EMF and RCP </AcronymnAlert>. GEF, which stands for Graph Editing Framework, had a neat way of creating Graph Editors and also had a great programming model for implementing the Drag and Drop behavior. Clients using GEF never had to worry about DnD events and instead implemented an interface which had more high level methods in it. A top-level manager class would take care of all the DnD plumbing and would internally call the methods on the interface. Using this concept I have a similar implementation using the power of attached properties.
How is it done?
The heart of this implementation sits in the class DragDropManager, which is the top level manager component that I described earlier. It takes care of all the DnD plumbing and exposes two attached properties: DragSourceAdvisor and DropTargetAdvisor.
public static readonly DependencyProperty DragSourceAdvisorProperty =
DependencyProperty.RegisterAttached("DragSourceAdvisor",
typeof(IDragSourceAdvisor), typeof(DragDropManager),
new FrameworkPropertyMetadata(new PropertyChangedCallback(OnDragSourceAdvisorChanged)));
public static readonly DependencyProperty DropTargetAdvisorProperty =
DependencyProperty.RegisterAttached("DropTargetAdvisor",
typeof(IDropTargetAdvisor), typeof(DragDropManager),
new FrameworkPropertyMetadata(new PropertyChangedCallback(OnDropTargetAdvisorChanged)));
All the event registrations happen in the PropertyChangedCallbacks. As you can see, both of these properties point to interfaces: IDragSourceAdvisor and IDropTargetAdvisor. Below I have reproduced these interfaces in their entirety.
public interface IDragSourceAdvisor
{
UIElement SourceUI
{
get;
set;
}
DragDropEffects SupportedEffects
{
get;
}
DataObject GetDataObject(UIElement draggedElt);
UIElement GetVisualFeedback(UIElement draggedElt);
void FinishDrag(UIElement draggedElt, DragDropEffects finalEffects);
bool IsDraggable(UIElement dragElt);
}
public interface IDropTargetAdvisor
{
UIElement TargetUI
{
get;
set;
}
bool IsValidDataObject(IDataObject obj);
void OnDropCompleted(IDataObject obj, Point dropPoint);
}
IDragSourceAdvisor contains methods that queries the source control about the data that needs to be transferred [GetDataObject()], what are the allowed effects [SupportedEffects], the visual feedback [GetVisualFeedback()] and whether the object is draggable [IsDraggable()]. It also contains the method FinishDrag(), which is useful for any cleanup operations on the source-side once the DragDrop operation is completed.
Similarly IDropTargetAdvisor queries the target control whether it
accepts this data object (IsValidDataObject()
) and the final drop
operation (OnDropCompleted()
). For all the pattern enthusiasts out
there, this is basically the strategy pattern for the source and target
advisors.
How do I use it?
Here comes the best part of using these attached properties. Imagine you have a Canvas on which you would like to move things around, something like a WinForms designer. Note that the Canvas acts as both the drag source and the drop target. In order to have the DnD behavior we will have to setup the attached properties, like so:
<Window.Resources>
<local:CanvasDragDropAdvisor x:Key="advisor"/>
</Window.Resources>
<Canvas
Background="White"
dnd:DragDropManager.DragSourceAdvisor="{StaticResource advisor}"
dnd:DragDropManager.DropTargetAdvisor="{StaticResource advisor}"
>
.....
.....
</Canvas>
</Window>
The class CanvasDragDropAdvisor
implements both the IDragSourceAdvisor
and the IDropTargetAdvisor
. All of the event handling is now taken care
by DragDropManager and the Canvas control automatically starts
participating in the DnD operations since we have setup the source and
target advisors.
So I hope you will agree that this simplifies adding DragDrop behavior to your controls. There are certainly many improvements that I intend to add to this implementation…starting with the naming of the methods (using the Framework Design Guidelines). There is also a possibility to remove some of the methods and make the interfaces even simpler!
Since this post is already getting too long, I will add a sequel and post the source and the Canvas example in that.
To Be Continued… ;)