If you’ve been searching the web for a good sample of how to drag and drop data from one DataGrid to another in Silverlight, you’ve probably found some great samples out there, showing “how easy” it can be by just surrounding your DataGrid with DataGridDragDropTarget tags.  In fact, if you do that, you can drag and drop data from one grid to the other.  The problem is that most of these demos are about the visual aspect of drag and drop.  But in the real world, there is more work to be done.  Most of the samples that I found did not deal with how to save the data once it was dropped in the other grid.  I’m using the MVVM pattern so basically what I need to do is, once the drop is complete, update my ViewModel.

The tricky part was figuring out what information was actually dropped on my target DataGrid.  It’s not at all obvious how to do that.  The Drop event uses DragEventArgs which has a Data property.  You’d think this would be the data from the other DataGrid, but you’d be wrong.  Data has a GetData() method but you need to provide it a parameter specifying the format of the Data.  Once you figure out how to do that, you’ll find that GetData() doesn’t actually give you the data!  It give you ItemDragEventArgs, which also has a Data property.  Is this Data the data you want?  Not exactly, but you are getting close.  This Data can be cast as a SelectionCollection – that is because a DataGrid supports multiple selections at the same time!  And each Selection in the collection is the data you actually want.  Is it me, or is that pretty confusing? 

I wouldn’t have gotten this far, but searching the web for a while, I stumbled across a post on StackOverflow that was helpful.  The response also references a blog post found here as well. 

Those samples I found were really helpful but it seemed like it could be made easier.  Starting with sample code from the blog post mentioned above, I created a very simple, reusable, Extension Method that uses Generics and does all of the heavy lifting for you!  This should make things pretty easy now.

Here is some of the sample code and a sample solution is attached here.   

First, here is the extension method that gets the underlying data that was dropped on the target:

public static IEnumerable<T> GetData<T>(this DragEventArgs args)
{
    IEnumerable<T> results = null;

    // Get the dropped data from the Data property and cast it to the first format. 
    ItemDragEventArgs dragEventArgs = args.Data.GetData(args.Data.GetFormats()[0]) as ItemDragEventArgs;

    if (dragEventArgs == null)
        return results;

    // Get the collection of items
    SelectionCollection selectionCollection = dragEventArgs.Data as SelectionCollection;
    if (selectionCollection != null)
    {
        // cast each item to what is expected
        results = selectionCollection.Select(selection => selection.Item).OfType<T>();
    }

    return results;
}

Here is the XAML for the two grids.  Note that in this sample (you can change this of course) I’m using AllowedSourceEffects.Copy so that the data remains in the first grid and is copied to the second.

<Toolkit:DataGridDragDropTarget AllowedSourceEffects="Copy"
                                Grid.Column="0" Grid.Row="0">
    <Controls:DataGrid x:Name="Data"
                        ItemsSource="{Binding Data}" 
                        AutoGenerateColumns="False">
        <Controls:DataGrid.Columns>
            <Controls:DataGridTextColumn Header="Id" Binding="{Binding Id}" />
            <Controls:DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" />
            <Controls:DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" />
        </Controls:DataGrid.Columns>
    </Controls:DataGrid>
</Toolkit:DataGridDragDropTarget>

<Toolkit:DataGridDragDropTarget AllowedSourceEffects="Copy"
                                Drop="DataGridDragDropTarget_Drop"
                                Grid.Column="1" Grid.Row="0">
    <Controls:DataGrid x:Name="MoreData"
                        ItemsSource="{Binding MoreData}" 
                        AutoGenerateColumns="False" 
                        AllowDrop="True">
        <Controls:DataGrid.Columns>
            <Controls:DataGridTextColumn Header="Id" Binding="{Binding Id}" />
            <Controls:DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" />
            <Controls:DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" />
        </Controls:DataGrid.Columns>
    </Controls:DataGrid>
</Toolkit:DataGridDragDropTarget>

Lastly, here is the code behind piece that handles the Drop event, get’s the data and passes it to my ViewModel.  Note here that I specify e.Handled = true.  Without that, the DragDropTarget would automatically copy the data again.

private void DataGridDragDropTarget_Drop(object sender, Microsoft.Windows.DragEventArgs e)
{
    //use the extension method to get the data from the DragEventArgs
    var data = e.GetData<Person>();

    foreach (var item in data)
    {
        _viewModel.AddToMoreData(item);
    }
    e.Handled = true;
}

Once again, here is a link to a complete sample project