Geeks With Blogs

News Locations of visitors to this page
Brian Genisio's House of Bilz
Shout it kick it on DotNetKicks.com

The Problem

I have been developing with Silverlight for a few months now, and I have really been enjoying myself.  It has been the enabling technology my project and we have been extremely productive in the environment.  Unfortunately, Silverlight is still in version 2.0 and there are some missing capabilities.

One such hole in the framework is "Drag and Drop".  There is no support for it directly.  There are several blog examples on the web, but I have yet to find a fully encapsulated, generic solution to the "Drag and Drop" problem.  For instance, how soon after dragging do you really want to be dragging?  You don't want to drag immediately, as that will affect normal clicking on an element.  How do you handle drags from one distinct control to another that are not aware of each other?  How do you clue the user in that a given control is droppable?  How do you add animation to cue the user that the item is being dropped?

Introducing DragNDrop

To answer these questions, I created the DragNDrop class.  It is a manager of sorts, and watches a "Drag Source" for the mouse down events.  The "Drag Source" implements an interface and the "Drop Spot" implements a complimentary interface.  This allows for the "Drag Source" and "Drop Spot" to be blissfully unaware of each other.

Usage

The assumption made with this class is that there is some sort of a payload.  This payload is picked up from the "Drag Source" and dropped into the "Drop Spot".  The "Drop Spot" can accept a payload of any types that it implements from any "Drag Source" that implements the complementary interface.  Lets take a look at the interfaces:

public class DragNDrop<PayloadType>
{
    public interface IThumbnailDragSource
    {
        FrameworkElement DragCursor { get; }
        PayloadType Payload { get; }
    }
    
    public interface IThumbnailDropSpot
    {
        void DragDropEnter();
        void DragDropExit();  
        void ThumbnailDroping(PayloadType dataContext, FrameworkElement cursor, Point cursorPosition);
    }
}

For instance, if a "Drag Source" wanted to allow dragging with a string payload, it would implement DragNDrop<string>.IThumbnailDragSource.  Any "Drop Spot" that wants to accept a string payload would implement DragNDrop<string>.IThumbnailDropSpot.  The DragNDrop class is then constructed with an instance of the "Drag Source" and the DragNDrop class handles everything else.

In addition to the payload, the "Drag Source" needs to provide the DragNDrop class with the cursor that will be displayed and dragged across the screen.  This can be anything; an image, a user control, a rectangle, etc.

The "Drop Spot" will be notified when the cursor is entering and exiting its space, so it can react appropriately.  It will also be notified when the payload is dropped with the instance of the cursor and the absolute position.  This is necessary in case the "Drop Spot" wants to animate the drop in any way.

The Demo

I created the Twitter Search Tool to prove out this concept.  The idea is that search for a term and it returns a list of tweets. When you find a tweet that you like, you can drop it on to the second list to keep track of it.  It is not the most functional app in the world, but it illustrates the usage of the DragNDrop class nicely.  Take a moment to try it out.

Now that you have seen it in action, lets take a look at how I use the DragNDrop class.  This application uses a small data structure called Tweet.  It contains all of the information about a given tweet (user name, user image, text, etc).  The Tweet is the payload.

In the Silverlight UI, I created a user control to represent each item in the list called ListItem.  The XAML is available in the full source if you are curious.

public partial class ListItem : UserControl, DragNDrop<Tweet>.IThumbnailDragSource
{
    private readonly DragNDrop<Tweet> _dragNDrop;

    public ListItem()
    {
        InitializeComponent();

        _dragNDrop = new DragNDrop<Tweet>(this);
        _dragNDrop.DraggingEnabledDistance = 5.0;
    }

    public FrameworkElement DragCursor
    {
        get
        {
            return new Image
            {
                Width = TweetImage.ActualWidth,
                Height = TweetImage.ActualHeight,
                Source = TweetImage.Source,
                Opacity = 0.5
            };
        }
    }

    public Tweet Payload
    {
        get { return DataContext as Tweet; }
    }
}

This code is pretty straight-forward.  The DragCursor returns a new image with the picture of the user.  The payload is the actual Tweet.  The constructor creates an instance of DragNDrop and passes itself in as the source.  It also sets the DraggingEnabledDistance.  This is the distance (in pixels) that the user must drag before the dragging really begins.  The default is 10 pixels.

Next, lets take a look at the "Drop Spot" code.  It is also a user control, TweetDropList, which includes a ListBox control.  It also includes an opaque canvas that gives the effect of highlighting when it is made visible.

public partial class TweetDropList : DragNDrop<Tweet>.IThumbnailDropSpot
{
    private readonly ObservableCollection<Tweet> _savedTweets = new ObservableCollection<Tweet>();

    public TweetDropList()
    {
        InitializeComponent();
        DropList.ItemsSource = _savedTweets;
    }

    public void DragDropEnter()
    {
        Highlight.Visibility = Visibility.Visible;
    }

    public void DragDropExit()
    {
        Highlight.Visibility = Visibility.Collapsed;
    }

    public void ThumbnailDroping(Tweet dataContext, FrameworkElement cursor, Point cursorPosition)
    {
        DragDropExit();
        AnimateCursor(cursor, cursorPosition, () => _savedTweets.Add(dataContext));
    }
}

I have left out the AnimateCursor code, but it simply generates a storyboard on the cursor and quickly morphs it down to a size of zero.  That code can be found in the source bundle.  The rest of this implementation is extremely simple.  DragDropEnter and DragDropExit simply hides and shows the highlight layer.  ThumbnailDropping will remove the highlighting layer and animate the cursor to give the effect of the item being dropped.  When the animation completes, the new tweet is added to the _savedTweets collection, which will cause the user control to display the new tweet.

Thats It!

I designed the DragNDrop class to be as simple to use as possible.  I have been using a version of this class in my product and I have had great results.  The "Drop Spots" can implement as many versions of IThumbnailDropSpot as it needs to allow different types of items to be dragged.  The DragNDrop class can be instantiated as many times as necessary, for every "Drag Source". 

Please play with this class and give me feedback.  I'd love to hear how it is being used.

DragNDrop Twitter Demo
DragNDrop Twitter Demo Source
DragNDrop.cs
Extensions.cs (a handful of extension methods that DragNDrop uses)

Posted on Tuesday, February 10, 2009 9:49 PM .NET , Silverlight | Back to top


Comments on this post: Drag and Drop with Silverlight

# re: Drag and Drop with Silverlight
Requesting Gravatar...
Excellent Control.
how can we add item to specific position in listbox (where the cusor is ) on drop listbox. it would be great if we can also add a line indication in on drop list. need your response and help in it

kamran gill
Left by kamran on Apr 18, 2009 8:18 AM

# re: Drag and Drop with Silverlight
Requesting Gravatar...
@Kamran:

The two things you are asking for have different solutions. First, dropping to a specific position:

When you get the ThumbnailDropping method called, you are given a point. You might need to do some goofing around with the list control to make it work, but I think it would go something like this...

1. Style the list item as a separate control
2. Implement IThumbnailDropSpot on the item control (not the control that hosts the list box.
3. When the item is dropped, get the listbox parent by traversing up the visual tree.
4. Add the item to the parent list box relative to the item being dropped.

As far as giving a line indication to where the item will be dropped, there is not direct support for this in my drag/drop manager, but it can be done. My code doesn't call when the mouse is moving over a drop spot, only when it enters and leaves. So, assuming you implemented IThumbnailDropSpot on the item, not the higher control, you would attach the MouseMove event handler and do something similar... I am not sure if ListBox has the ability to highlight a line, but so you might have to inject a line element into the ListBox. Either way, you add the visual indicator relative to where the mouse is moving.

I had considered adding DragDropMove to the DragNDrop class, and it would be pretty trivial to implement given the source example I provided. Instead of hooking MouseMove in your code, the clean way to do it would be to implement in DragNDrop so it is reusable.

Good luck. I would be curious to know how this works out for you, and would be happy to help out more. If I get time over then next week or so, I might try modifying my demo to do exactly what you are talking about if you get stuck.

Thanks,
Brian
Left by Brian Genisio on Apr 18, 2009 1:15 PM

# re: Drag and Drop with Silverlight
Requesting Gravatar...
This is just an awesome control. I'm new to Silverlight and am trying to do a much more trivial example than the one you've posted. I simply want to drag-drop 1 item from a ListBox control populated with some text to a second ListBox control.

Would you be kind enough to help me out? I've been scouring the web for the past couple of days and this by far is the most promising drag-drop solution that I've come across.

Many, many thanks in advance,
Luck
Left by Luck on May 21, 2009 3:02 AM

# re: Drag and Drop with Silverlight
Requesting Gravatar...
Heads up!
Your demo page is broken....
Left by mp on Aug 29, 2009 12:19 PM

Your comment:
 (will show your gravatar)


Copyright © Brian Genisio's House Of Bilz | Powered by: GeeksWithBlogs.net