One New Thing a Week #2: Drag and Drop Helper in WPF

OK, so the title is a little bit disingenuous, but I’m trying to get back in the swing of things.  One New thing a Quarter would be more accurate, but to make up for it, there’s more than one new thing in this week.

I’ve gotten started with WPF.  So far, it’s quite awesome, but I haven’t really done that much UI work yet.  Instead, I got distracted by build a Drag and Drop helper class.

If you haven’t done Drag and Drop in WPF, it’s basically the same as WinForms, which is to say: a bit of a pain.  You need a bunch of mouse events around just to get things working.  I decided I wanted a class to handle that part for me.

Enter the DragAndDropHelper static class.  By setting an attached property on your UI element (e.g. a ListBox), it will wire up all the normal mouse events it needs (Up, Down, Move and sometimes Leave) and do the “Heavy Lifting” for you. No more tracking mouse co-ordinates!

In order to allow individual controls to behave differently, it makes use of 3 Routable Events which get raised in this order:

    1. LoadDragData is raised on the source control when the helper wants your control to put some data up for Drag and Drop.  It’s up to your control to figure out the selected item or whatever and put it on the clipboard in a reasonable format
    2. ReceiveDragData is raised on the target when data is dropped onto it.  Examine the DataObject that is passed in, pick your format and do something with it! 
    3. SourceDragComplete is raised on the source control to let it know that data has been dragged and dropped somewhere else.  If it was a Move, you should probably remove the content from your control.  If it was a copy, you can probably do nothing.

To get your ListBox working with this helper, you just need to assign the DependencyProperty that says “me too!” and then provide handlers for those 3 Routable Events.  In XAML, it winds up looking like this:

<ListBox Name="outerList" osl:DragAndDropHelper.DragHelperEnabled="True"
                 osl:DragAndDropHelper.LoadDragData="OuterList_LoadDragData" 
                 osl:DragAndDropHelper.ReceiveDragData="OuterList_ReceiveDragData"
                 osl:DragAndDropHelper.SourceDragComplete="OuterList_SourceDragComplete" 

I’ve put together a quick little “Test Harness” that loads up the .jpg files in the directory specified (app.config) and puts them into the 2 right hand boxes (same collection, so the contents are linked).  The left hand box gets 2 random top-level items each with 2 random children.  You can drag things from the right hand lists to each other or to the left hand list or to the child lists in the left hand list.

Things that were tricky:

Since DragAndDropHelper is entirely static, I couldn’t declare a public event to allow XAML to do the assignment of helpers to the class.  Turns out, there’s a convention that XAML recognizes for the add and remove static functions to make this happen.  (For some reason I don’t understand yet, the XAML editor doesn’t like it.  It still works, though.)  This is what the code winds up looking like:

public static void AddLoadDragDataHandler( DependencyObject uiElement, RoutedEventHandler value )
{
    if( uiElement as UIElement == null )
        throw new ArgumentException() ;

    (uiElement as UIElement).AddHandler(LoadDragDataEvent, value);
}

public static void RemoveLoadDragDataHandler(DependencyObject uiElement, RoutedEventHandler value)
{
    if (uiElement as UIElement == null)
        throw new ArgumentException();

    (uiElement as UIElement).RemoveHandler(LoadDragDataEvent, value);
}

Limitations:


At the moment, I’ve only tested this with ListBoxes filled with Images, but I have done nested lists, which covers the use case I was working on (see, it’s all about me, isn’t it?).  It should be easily extensible even if there are differences for other control types, but from what I’ve seen, ListBox is pretty widely used in WPF for just about everything.

Oh, and this IS the first thing I’ve done in WPF, so it’s probably not optimal.  There are other people with similar solutions to the same problem… they’re probably better.

Code Download

Once I find a decent file upload plugin for LiveWriter, I’ll put the project file up for everyone to Share and Enjoy.

#1 Clair on 4.05.2009 at 2:39 PM

my bad, I didn't know I had to pass NERD 401 with an A++ in order to understand my brother's webpage. Shesh.