Home > DataGrid > Moving WPF DataGrid Rows using Drag and Drop

Moving WPF DataGrid Rows using Drag and Drop

March 24th, 2009

For my upcoming NetDrives tool (will be released shortly) I wanted to enable the user to reorder managed network shares using drag and drop using a preview of the dragged row:

image

 

As it turned out, it’s not too hard to implement, but it took my a while to find all pieces of the puzzle, so I compiled a short sample. You can find the sample link at the end of the article.

 

Drag Indicator Popup

I used a popup as an drag indicator, which I bound to the item that was currently dragged (DraggedItem dependency property):

<!-- the popup that is displayed if user moves rows -->
<Popup
  x:Name="popup1"
  IsHitTestVisible="False"
  Placement="RelativePoint"
  PlacementTarget="{Binding ElementName=me}"
  AllowsTransparency="True">
  <Border
    BorderBrush="{DynamicResource CellBorderBrush}"
    BorderThickness="2"
    Background="White"
    Opacity="0.75">
    <StackPanel
      Orientation="Horizontal"
      Margin="4,3,8,3">
      <Image
        Source="/Shared/Images/DragInsert.png"
        Width="16"
        Height="16" />
      <TextBlock
        Style="{DynamicResource DefaultLabel}"
        FontWeight="Bold"
        VerticalAlignment="Center"
        Text="{Binding ElementName=me, Path=DraggedItem.Name}"
        Margin="8,0,0,0" />
    </StackPanel>
  </Border>
</Popup>

 

Disabling Drag and Drop in Edit Mode

I didn’t want to enable drag and drop if the grid was in edit mode. Accordingly, I registered two event listeners on the grid:

<dg:DataGrid
  BeginningEdit="OnBeginEdit"
  CellEditEnding="OnEndEdit"
  .. />

 

The corresponding event listeners just set the IsEditing flag, which is evaluated when handling mouse events:

/// <summary>
/// State flag which indicates whether the grid is in edit
/// mode or not.
/// </summary>
public bool IsEditing { get; set; }

private void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e)
{
  IsEditing = true;
  //in case we are in the middle of a drag/drop operation, cancel it...
  if (IsDragging) ResetDragDrop();
}

private void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
  IsEditing = false;
}

 

Listening to Mouse Events

In order to display and move the popup with the mouse, I registered listeners for the following three mouse events:

  • PreviewMouseLeftButtonDown (on the datagrid)
  • MouseLeftButtonUp (directly on the layout root)
  • MouseMove (directly on the layout root)

 

Note: I started with listeners on the grid only, which caused some side effects. Apparently, the datagrid (current March release) not always fires the mouse events properly. This caused choppy animations when hovering over certain cells. Fortunately, this is not an issue with the MouseMove event of the layout root.

 

Starting Drag and Drop

DnD is started as soon as the user presses the left mouse button on the datagrid. I had to use the PreviewLeftMouseButton event in order to get the notification, and I needed to determine the clicked row based on the mouse position. I blogged about finding an element under the mouse a while ago here, but the UIHelpers class is part of the sample project here.

My mouse button event listener basically does the following:

  • Check if the mouse is being placed over a grid row.
  • Set the IsDragging flag to true.
  • Store the dragged item in the DraggedItem dependency property (used by the popup to display the name).

 

private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
  //exit if in edit mode
  if (IsEditing) return;

  //find the clicked row
  var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement) sender,
                                                    e.GetPosition(shareGrid));
  if (row == null) return;

  //set flag that indicates we're capturing mouse movements
  IsDragging = true;
  DraggedItem = (IShareConfiguration) row.Item;
}

 

Moving the Popup

I registered a listener for the MouseMove event directly on the layout root (not on the datagrid). Basically, the event listener just moves the popup to the current mouse location along with a few minor tasks:

  • If the popup has not been opened yet, display it.
  • Set the grid to read-only.
  • Reposition the popup by setting the PlacementRectangle property.
  • Make sure the grid row under the mouse is being selected. Once again, this didn’t work reliably if I relied on the datagrid to do it by itself.

 

/// <summary>
/// Completes a drag/drop operation.
/// </summary>
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  if (!IsDragging || IsEditing)
  {
    return;
  }

  //get the target item
  ShareConfiguration targetItem = (ShareConfiguration) shareGrid.SelectedItem;

  if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem))
  {
    //remove the source from the list
    ShareList.Remove(DraggedItem);

    //get target index
    var targetIndex = ShareList.IndexOf(targetItem);

    //move source at the target's location
    ShareList.Insert(targetIndex, DraggedItem);

    //select the dropped item
    shareGrid.SelectedItem = DraggedItem;
  }

  //reset
  ResetDragDrop();
}

 

Finishing Drag and Drop

Once the user releases the mouse button, I need to perform the actual drop operation. I already had the dragged item (DraggedItem property, was set when the operation started) so all I needed was the drop target. My target is the currently selected row.

/// <summary>
/// Completes a drag/drop operation.
/// </summary>
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  if (!IsDragging || IsEditing || shareGrid.SelectedItem == null)
  {
    return;
  }
  
  //get the target item
  IShareConfiguration targetItem = (IShareConfiguration)shareGrid.SelectedItem;

  if (!ReferenceEquals(DraggedItem, targetItem))
  {
    //the actual business logic that works on source and target
  }

  //reset
  ResetDragDrop();
}

 

You might also want to check whether the mouse is currently over the grid (or a grid row) or not – in that case just use the TryFindFromPoint method from the UIHelpers class.

 

Cleaning Up

The ResetDragDrop method just performs a cleanup of the code by closing the popup and adjusting a few properties:

/// <summary>
/// Closes the popup and resets the
/// grid to read-enabled mode.
/// </summary>
private void ResetDragDrop()
{
  IsDragging = false;
  popup1.IsOpen = false;
  shareGrid.IsReadOnly = false;      
}

 

Download Sample: http://blog.hardcodet.net/wp-content/uploads/2009/03/datagrid_dragdrop.zip


Author: Categories: DataGrid Tags: ,
  1. anto
    May 15th, 2009 at 13:25 | #1

    Thank you so much for your work, I was searching this for very long time !!

    Thank you, keep going like this

  2. June 17th, 2009 at 20:40 | #3

    Have you tried this code on the free Xceed grid?

  3. June 17th, 2009 at 20:47 | #4

    Maxime,

    Nope, this sample was built on and for Microsoft’s grid only. However, I’m pretty sure you’ll be able to get a snippet in their forum.

    …I guess I should point that out in the article.

  4. Rahul
    August 14th, 2009 at 21:46 | #5

    Hey,
    Thank you so much for your example code.It was really helpful.
    I am trying to use datagrid to crate a formula builder.
    There are 3 columns in it: 1)Variable name 2)Input (can contain constants or a formula based on previous variables) 3) Value(to show the calculated result of this particular input).

    Is there a way to find out where in a particular cell you have dropped the item.
    for example: if you have d=a+b-c; but then you think the formula is d=a+b-e+c.
    i.e. i want to drag ‘e’ and put it in between ‘b’ and ‘c’.
    Do u have any ideas for the same?

    Thanks.

  5. Rahul
    August 18th, 2009 at 17:57 | #6

    I had one more question. I have 2 user controls: one for the data grid and another for a listbox.

    Using the pop-up in this example i can only drag in the area of the datagrid. what changes do i have to make to show the pop-up drag to the other user control(i.e. drag into the list box).
    Thanks.

  6. December 23rd, 2009 at 17:29 | #7

    Awesome tutorial. If people were wondering, this is also easily adapted to using a ListBox rather than a DataGrid.

  7. Miky
    February 21st, 2010 at 01:30 | #8

    Many thanks for this great tutorial !
    Saved me many hours !

    Thank you.

  8. sachin Jain
    November 9th, 2010 at 04:10 | #9

    This code is working for xceed grid also but you have to add the style

  9. vinahamp
    January 18th, 2011 at 20:22 | #10

    Do you have any suggestions if I would like to implement a drag and drop operation on the detailrow of the wpf datagrid?

  10. Vincent
    March 15th, 2011 at 22:37 | #11

    Thanks for this great tutorial!

  11. David
    June 23rd, 2011 at 01:39 | #12

    Thanks for the tutorial.How about the scrolling? I have a lot of items in my grid. When I move the pop up from bottom to top, the grid scrollbar is not scrolling.

  12. iresha
    August 13th, 2011 at 08:21 | #13

    Thanks a lot. This helped me out quite a lot in my freelance project

  13. peter
    August 31st, 2011 at 12:23 | #14

    This is really good.
    However I need move a record from one data grid to another.
    I should be able to leverage somewhat of what you have demod.
    Thank you

  14. eggi
    December 1st, 2011 at 06:03 | #15

    There’s a bug when I drag down. Try this.

    bool addOne = false;
    if (!IsDragging || IsEditing)
    {
    return;
    }

    //get the target item
    Verbundarbeit targetItem = (Verbundarbeit)lvUmfasst.SelectedItem;

    if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem))
    {
    //get scr index
    var srcIndex = _umfasstList.IndexOf(DraggedItem);
    var targetIndex = _umfasstList.IndexOf(targetItem);
    if (srcIndex < targetIndex)
    addOne = true;

    //remove the source from the list
    _umfasstList.Remove(DraggedItem);

    //get target index
    targetIndex = _umfasstList.IndexOf(targetItem);
    if (addOne)
    targetIndex++;

  15. Rama
    June 16th, 2012 at 18:42 | #16

    @peter

    Hi Peter i was just check the same implementation as your like moving rows from grid to another. were you able to get throught it.

    Can i have your suggestion or code on this please

  16. Caleb
    July 11th, 2012 at 22:42 | #17

    This is very helpful, thank you very much!
    Just a note: in the OnMouseLeftButtonUp method, it works better if you get the target index *before* you remove the source row from the list, otherwise the insert behaviour is different when you drag a row down than it is when you drag a row up (dragging up moves it to the position of the highlighted row, as would be expected, but dragging down moves it to the position 1 row *above* the highlighted row).

  17. Jeffrey
    November 26th, 2012 at 15:40 | #18

    This is aweeeeesomely good. helped me fixed a bug.

  18. Tadej
    September 21st, 2013 at 18:23 | #19

    I’ve noticed that if I drag a row on to datagrids header, the drag and drop icon is displayed and action is not completed until I click any other element of the window. You can see the behavior here: http://screencast.com/t/NcuUhcbbeJ71

  1. No trackbacks yet.