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

9 thoughts on “Silverlight 4: Drag and Drop DataGrids with Access to the Data Being Dropped

  1. Venkat….

    private void DataGridDragDropTarget_Drop(object sender, Microsoft.Windows.DragEventArgs e)

    This method can’t be called in .cs page please help me…………….

    Reply
  2. wproctor,

    Yes, I have done this. Here are some of the steps. This blog doesn’t permit formatting text in comments so pasting my code here is not a great option :( . If you include your email I can send you some code…

    1. I used a mouseover event on the target grid.
    2. I check to see if we are currently in "drag" mode.
    3. Using VisualTreeHelper.FindElementsInHostCoordinates, I get the DataGridRow that is getting the "drop". I set that row as the "target". Based on mouse position I determine if the user is dropping above or below the row.
    4. I apply some margins above or below that DataGridRow as an indicator of where the dropped item will land.

    Now you know where the items is being dropped. Within your viewmodel you can insert the dropped item into a known position in your list, resort items, etc to meet your business needs.

    I hope this helps.

    -Andy

    Reply
    • Bart, GREAT Video!Thanks for posting this. You have no idea how long I have been tiryng to get this to work and had just about given up hope.One idea for a future video is related to something I’m tiryng to do and that is this:How about a video doing the same programmatic binding of a combo box in a datagrid, but doing it where the combo box in each row is bound to a different collection? It’s an interesting challenge and not that trivial but will be much moreso using your technique above.You might ask why someone would want to do this? I’ll give an example:Suppose your application allows the user to create new assets about a building walls, windows, roofs, carpet, etc.For each asset that is created, you may want to have the user pick from pre-defined sets of questions/attributes. If the user selects a roof, he may want to pick the type, material, and slope .each of these guys having 4 or 5 possible values. If the user is creating a window, he may need to select the type (single-pane, double-pane), whether it is insulated or not, and 2 or 3 other attributes.In other words, based on an asset type, we want to pick a series of questions and possible answers to display in comboboxes in a grid. Then we want to persist and display the user’s selections back to the database along with that main asset object.Sometimes when we developers see very detailed business objects like I describe, it helps to illustrate the technology better than just seeing a more rudimentary example like you show above. Again, thanks for posting this video because it will hopefully allow me to solve the problem I list above. Thanks again.

      Reply
  3. Andy –

    Is there any way to find the target index of the datagrid using DDT? in the Drop event I would like to Insert into the ItemsSource instead of the default action which is to Add. Not sure how to get the index of the row where the item is being dropped.

    Thanks in advance.

    Reply
  4. Hi Andy,

    Thanks for the post. Have you ever run across a requirement to re-organize the data in the same data grid? Essentially I need to be able to drag the rows around to change steps (think of it like a process flow . . . move step 5 to step 3 and re-number).

    Assuming each step is a row which is coming from an observable collection I’m assuming you would need to insert and remove from it. The remove should be easy because you can get the object from the ItemDragEventArgs and search for it. How do you determine where to insert based on the drop and drag graphically?

    Thanks in advanced.

    Reply
  5. Ajay,

    I’m sorry, that requirement is fairly complicated and I don’t think there is a simple way to accomplish this task. I’m sure it can be done however. After all, this is Silverlight! But I guess you’d need to draw out some lines between the items in each listbox. I’ve never attempted it so I have no code samples to share. If I weren’t so busy with my work, I might actually try to implement that to help. I think it would be a fun experiment. But I believe that would take some time to play around and make it work.

    Best of luck to you.
    Andy

    Reply
  6. Nice article.

    Apart from this, I have one requirement like how to show lines between source to destination when we copy/drag one item from ListBox1 to Listbox2. Here what actually I am looking for is, after placing item from source to destinaion, one line is to be connected between source to destinaion.

    eg. In Sql server (Database Designer) mode, when we mapped any table fields with another (one-many relationship) there is one line drawn between source to destination.

    Can you plz help me about this? How can we do this stuff?

    Waiting for your comments.

    Regards,
    Ajay.

    Reply
  7. Shanu,
    I haven’t tried to do what you need to do. But it seems that you could use the same pattern I demonstrated above. Then you can take the value returned from the GetData<T>() method and do whatever you want with it, including casting it as something else or converting it into a different object completely. If you are following the MVVM pattern, it may however make more sense to do the cast/conversion within your viewmodel, not the code behind, making it easier to test.
    Good Luck.

    Reply
  8. Thank you very much. Very useful article. helped me a lot for Drag n Drop between DataGrids. Now, I am trying to drag from DataGrid and drop onto StackPanel. Where do I have to make changes to be able to cast the dragged item to my required item?

    Reply

Leave a reply

required

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>