Bookmark and Share Share...    Subscribe to this feed Feed   About me...


Drag and Drop in WPF

Introduction

Drag&Drop can drasticly improve the productiviy and user experience of a software. But only a few programmers provide drag and drop functionality in their applications, because they think its much more dificult than it really is. This article shows how simple drag and drop can be implemented in WPF.

Drag&Drop in 6 Steps

  1. Detect a drag as a combinatination of MouseMove and MouseLeftButtonDown
  2. Find the data you want to drag and create a DataObject that contains the format, the data and the allowed effects.
  3. Initiate the dragging by calling DoDragDrop()
  4. Set the AllowDrop property to True on the elements you want to allow dropping.
  5. Register a handler to the DragEnter event to detect a dragging over the drop location. Check the format and the data by calling GetDataPresent() on the event args. If the data can be dropped, set the Effect property on the event args to display the appropriate mouse cursor.
  6. When the user releases the mouse button the DragDrop event is called. Get the data by calling the GetData() method on the Data object provided in the event args.

...and that's all the magic.

Drag

To start the drag operation, we have to detect a mouse move while the left mouse button is pressed. To do this we have to hook up handlers on the PreviewMouseMove and PreviewMouseLeftButtonDown events.

To prevent occasionally drags, its a good design to not start the drag operation until the user has moved the mouse cursor by a couple of pixels. WPF provides a constant that contains the amount of pixel that Windows uses.

When the drag is initiated, we need to specify the data we want to drag. In our case its the data of the ListViewItem we dragged. We find the ListViewItem in the OriginalSource of the mouse event args. By calling ItemContainerGenerator.ItemFromContainer we get the data behind the ListViewItem.

Create a DataObject to transport the data to the drop location. The constructor takes two arguments. A string that describes the format and the data we want to drag.


 
<ListView x:Name="DragList" 
          PreviewMouseLeftButtonDown="List_PreviewMouseLeftButtonDown" 
          PreviewMouseMove="List_MouseMove"/>
 
 
 
private void List_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    // Store the mouse position
    startPoint = e.GetPosition(null);
}
 
 
 
private void List_MouseMove(object sender, MouseEventArgs e)
{
    // Get the current mouse position
    Point mousePos = e.GetPosition(null);
    Vector diff = startPoint - mousePos;
 
    if (e.LeftButton == MouseButtonState.Pressed &&
        Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
        Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance )
    {
        // Get the dragged ListViewItem
        ListView listView = sender as ListView;
        ListViewItem listViewItem = 
            FindAnchestor<ListViewItem>((DependencyObject)e.OriginalSource);
 
        // Find the data behind the ListViewItem
        Contact contact = (Contact)listView.ItemContainerGenerator.
            ItemFromContainer(listViewItem);
 
        // Initialize the drag & drop operation
        DataObject dragData = new DataObject("myFormat", contact );
        DragDrop.DoDragDrop(listViewItem, dragData, DragDropEffects.Move);
    } 
}
 
 
 
// Helper to search up the VisualTree
private static T FindAnchestor<T>(DependencyObject current)
    where T : DependencyObject
{
    do
    {
        if( current is T )
        {
            return (T)current;
        }
        current = VisualTreeHelper.GetParent(current);
    }
    while (current != null);
    return null;
}
 
 

Drop

To make an element be a drop location, set the AllowDrop property to true. When the user drags an item over the element, the DragEnter event is called. In this event you can analyze the data and decide if a drop is allowed or not.

When the user releases the mouse button the Drop event is called. The data is available in the DataObject provided in the DragEventArgs.


 
<ListView x:Name="DropList" 
          Drop="DropList_Drop" 
          DragEnter="DropList_DragEnter" 
          AllowDrop="True" />
 
 
 
private void List_DragEnter(object sender, DragEventArgs e)
{
    if (!e.Data.GetDataPresent("contact") ||
        sender == e.Source)
    {
        e.Effects = DragDropEffects.None;
    }
}
 
 
 
private void List_Drop(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent("myFormat"))
    {
        Contact contact = e.Data.GetData("myFormat") as Contact;
        ListView listView = sender as ListView;
        listView.Items.Add(contact);
    }
}
 
 



 Comments on this article

Show all comments
gen
Commented on 14.May 2010
oops sorry wasn't able to read the whole code snippet before I commented... was so excited didn't even notice the definition below...
John
Commented on 14.May 2010
has anyone tried to drag and drop the treeviewitem? it seems to be a bug here. i called TreeView.ItemContainerGenerator.ItemFromContainer(treeViewItem), i got null.now i do not know jet how to resolve this problem.SOS
John
Commented on 14.May 2010
private void TreeView_Drop(object sender, DragEventArgs e)
{
TreeView tree = sender as TreeView;
DependencyObject dpo=VisualTreeHelper.HitTest(tv,e.GetPosition(tv)).VisualHit as DependencyObject;
while(dpo.GetType()!=typeof(TreeViewItem)) {dpo=VisualTreeHelper.GetParent(dpo);}
TreeViewItem tvi=dpo as TreeViewItem; //tvi ok
MyItemData mid=tree.ItemContainerGenerator.ItemFromContainer(tvi) as MyItemData ; //false! mid=null
//in the fact, tree.ItemContainerGenerator.ItemFromContainer(tvi).GetType() is MS.Internal.NamedObject
......
}
John
Commented on 14.May 2010
i want just to change the branch structure of a treeview with the help of drag-and-drop. it goes on wheels with WindowForms programming.But here with WPF it comes hard.so it is sure that i am not familiar enough with WPF.
John
Commented on 14.May 2010
ha,TreeViewItem.DataContex !
john
Commented on 14.May 2010
thanks,i have got inspiration from your site!
John
Commented on 14.May 2010
Please help me! after drag-n-drop of one node some of the nodes disappear weirdly. it drive me mad. i can not understand this. here is all the code. i beg for your help.
public class TreeNode
{
BindingList<TreeNode> lst = new BindingList<TreeNode>();
Guid id = Guid.NewGuid();
public Guid ID { get { return id; } }
public TreeNode Parent { get; set; }
public BindingList<TreeNode> Children { get { return lst; } }
}
public class TreeDataSource
{
public TreeDataSource()
{
TreeNode node = new TreeNode();
lst.Add(node);
for (int i = 0; i < 5; i++)
{
node.Children.Add(new TreeNode() { Parent = node });
}
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
node.Children[i].Children.Add(new TreeNode() { Parent = node.Children[i] });
}
}
}
BindingList<TreeNode> lst = new BindingList<TreeNode>();
public BindingList<TreeNode> Children { get { return lst; } }
}
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type this:TreeNode}" ItemsSource = "{Binding Path=Children}">
<TextBlock Text="{Binding Path=ID}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView Grid.Row="0" ItemsSource="{Binding Path=Children}" AllowDrop="True" Name="tree"/>
</Grid>
</Window>
public partial class Window1 : Window
{
const string myData = "myData";
public Window1()
{
InitializeComponent();

DataContext = new TreeDataSource();

tree.AddHandler(TreeView.MouseLeftButtonDownEvent, new MouseButtonEventHandler(TreeView_MouseLeftButtonDown), true);
tree.AddHandler(TreeView.MouseLeftButtonUpEvent, new MouseButtonEventHandler(tree_MouseLeftButtonUp), true);
tree.AddHandler(TreeView.MouseMoveEvent, new MouseEventHandler(tree_MouseMove), true);
tree.AddHandler(TreeView.DropEvent, new DragEventHandler(TreeView_Drop), true);
}

Rect rectMouseDown = Rect.Empty;
void tree_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
rectMouseDown = Rect.Empty;
}
void tree_MouseMove(object sender, MouseEventArgs e)
{
Point p=e.GetPosition(null);
if (rectMouseDown != Rect.Empty && rectMouseDown.Contains(p) == false)
{
TreeView tree = sender as TreeView;
TreeNode node = GetNodeData(tree, p);
if (node != null)
{
DataObject data = new DataObject(myData, node);
DragDrop.DoDragDrop(tree, data, DragDropEffects.All);
}
}
}
private void TreeView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
TreeView tree = sender as TreeView;
Point p = e.GetPosition(null);
TreeNode node = GetNodeData(tree, p);
rectMouseDown = node == null ? Rect.Empty : new Rect() { X = p.X - 2, Y = p.Y - 2, Width = 4, Height = 4 };
}
private void TreeView_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(myData))
{
TreeNode beDragged = e.Data.GetData(myData) as TreeNode;

TreeView tree = sender as TreeView;
TreeNode newFather = GetNodeData(tree, e.GetPosition(tree));

if (newFather != null)
{
TreeNode father = beDragged.Parent;
father.Children.Remove(beDragged);
beDragged.Parent = newFather;
newFather.Children.Add(beDragged);
}
}
}
TreeNode GetNodeData(TreeView tree, Point p)
{
TreeNode node = null;
DependencyObject dpo = VisualTreeHelper.HitTest(tree, p).VisualHit as DependencyObject;
if (dpo != null)
{
while (dpo != null && dpo.GetType() != typeof(TreeViewItem)) dpo = VisualTreeHelper.GetParent(dpo);
if (dpo != null) node = (dpo as TreeViewItem).DataContext as TreeNode;
}
return node;
}
}
Carlos Oviedo
Commented on 14.May 2010
Please!! in VB .NET
Carlos Oviedo
Commented on 14.May 2010
Please!! in VB .NET
Kobe Bryant
Commented on 15.May 2010
I want to drag and drop an object with multipoint sdk. How must I do?
Mick
Commented on 19.May 2010
Just reading through your sample, I noticed && should probably be ||, or else the user can drag horizontally/vertically forever without calling DoDragDrop.

Aside from that, it was very clean and easy to understand. Thank you.
Scrut
Commented on 19.May 2010
If you want to disallow dropping in certain areas, WPF requires you to implement the DragOver and DragLeave as well: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/38231053-01d4-4b58-b994-7bf3d2e4a73e

Good work
Brian Finn
Commented on 26.May 2010
If you use a Canvas as droptarget, you'll have to set the Background-Property to let it work.
Barak (Israel)
Commented on 6.June 2010
I think you should replace the && with ||:

replace: Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance )

with: Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance )

Mark
Commented on 7.June 2010
Awesome, superb, incredible. In an immense OCEAN of disinformation and convoluted examples, you have provided a rare treasure of clear and concise information. Bravo !!! Thank you so much ! Note, though. My Lisview is bound to a collection so I will have to do additional work to get this working.
Mayank
Commented on 12.June 2010
Cannot find CONTACT class and startpoint refrence. Please help
krishna
Commented on 16.June 2010
Hi all,
Am totally new to Wpf ,i tried to execute this code but it is not working..
The Events are not firing at all...
Wat did i missed???
Can any one send me a complete source code....
Thanks in Advance...
krishna
Commented on 16.June 2010
Hi all,
Am totally new to Wpf ,i tried to execute this code but it is not working..
The Events are not firing at all...
Wat did i missed???
Can any one send me a complete source code....
Thanks in Advance...
shafqat ali
Commented on 21.June 2010
Nice article. But i can't find reference classe. Please send me completed source code.
Thanks, Shafqat.
Kandhal Rohit
Commented on 29.June 2010
http://www.codeproject.com/KB/WPF/WpfDragAndDropSmorgasbord.aspx

Check this link. It has everything you would ever dream about Drag and Drop functionality.
Florian
Commented on 8.July 2010
Hej!
I have the same problem like Mayank. Can someone help us with this problem?
Msh
Commented on 12.July 2010
it is ok
LandR
Commented on 14.July 2010
I have a tree view bound to hierarchical data that I've created in Blend 4. I want to be able to do this:

Let's say the tree breaks down fruit and vegetables. Under the expanded veggies tree, I want to be able to see all vegetables - fruit the same. But say tomato is under vegetables, and I want it under fruit, in the same tree (different branch). I want to be able to click on "tomato" and drag and drop it on "fruit" and have it move that entry to "fruit" and update my database (2 way link).

+fruit

1. apples
2. oranges

+ vegetables

1. onion
2. spinach
3. tomato

(drag and drop tomato)

+fruit

1. apples
2. oranges
3. tomato

+ vegetables

1. onion
2. spinach

Any thoughts?

Thanks!
NONE OF THIS...
Commented on 23.August 2010
COPYRIGHT INFRINGEMENT AT ITS FINEST!!!!!!!!!

MOSER IS GREASY AS HELL
C-MOSE
Commented on 23.August 2010
I apprieciate your feedback but I cant explain it anymore in depth because I just copy and pasted this from another website

Name
E-Mail (optional)
Comment
About Christian Moser