Moving WPF DataGrid Rows using Drag and Drop
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:
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
Thank you so much for your work, I was searching this for very long time !!
Thank you, keep going like this
Thanks, anto – happy coding 🙂
Have you tried this code on the free Xceed grid?
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.
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.
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.
Awesome tutorial. If people were wondering, this is also easily adapted to using a ListBox rather than a DataGrid.
Many thanks for this great tutorial !
Saved me many hours !
Thank you.
This code is working for xceed grid also but you have to add the style
Do you have any suggestions if I would like to implement a drag and drop operation on the detailrow of the wpf datagrid?
Thanks for this great tutorial!
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.
Thanks a lot. This helped me out quite a lot in my freelance project
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
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++;
@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
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).
This is aweeeeesomely good. helped me fixed a bug.
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
good job, thanks.
I need to change background color of the row when I move a row up or down. Could you please help me on this?
@Caleb
THANK YOU!!
Am I missing something? The MouseMove function is not described
Note that the source code snippets in the article above are outdated. Just be better than me and directly grab the download… 😉