Implementing Simple drag-and-drop operations on a WPF treeview

Last week I was trying to get a drag and drop treeview to work in WPF. Creating the basic structure was rather pleasing work to do because setting up databinding and getting the items to render correctly is a breeze with the new layout and databinding capabilities. However, getting items dragged from one point in the tree to another point in the tree is a whole different story. As it turns out there's a problem with the mouse eventhandling.
When you issue a DragDrop.DoDragDrop operation within a mousemove eventhandler, the mousemove event no longer gets fired, until you drop the item. I tried different variations on this, but to no avail. Appearantly I wasn't the only person who noticed this weird behaviour, because Josh Smith tried to do something similar[1] and had to write a wrapper for the Win32 mouse api to get the correct result.
After using this utility I had the whole thing working pretty quickly. It's as simple as implementing three eventhandlers: MouseMove, DragOver and Drop. In the MouseMove eventhandler you have to add code similar to the following snippet:
void treeView_MouseMove(object sender,MouseEventArgs e)
{
if(!_isDragging && e.LeftButton == MouseButtonState.Pressed)
{
_isDragging = true;
DragDrop.DoDragDrop(treeView,treeView.SelectedValue,
DragDropEffects.Move);
}
}
This issues the drag-drop movement and ensures that the user can't start the same operation a second time. Next you need to check if the user is doing the correct thing in the DragOver eventhandler, you can manipulate the behaviour of the drag-drop movement by setting the Effects property of the passed DragEventArgs object. The code here will look something like this:
void treeView_DragOver(object sender,DragEventArgs e)
{
if(e.Data.GetDataPresent(typeof(Task)))
{
e.Effects = DragDropEffects.Move;
}
else
{
e.Effects = DragDropEffects.None;
}
}
Last you need to finish the drag-drop movement in the Drop eventhandler. This is done by moving the dragged item to another position in the tree and setting the isDragging flag to false again, so that the user can issue another drag-drop movement. In this eventhandler I had another problem, how to get the target item to drop the source item on. Because WPF has a separated visual tree, this can't be done using normal methods. The drop method is shown below.
void treeView_Drop(object sender,DragEventArgs e)
{
if(e.Data.GetDataPresent(typeof(Task)))
{
Task sourceTask = (Task)e.Data.GetData(typeof(Task));
Task targetTask = GetItemAtLocation(MouseUtilities.GetMousePosition());
// Code to move the item in the model is placed here...
}
}
I discovered that if you do a VisualTreeHelper.HitTest(reference,location) call, a visual gets returned that represents the control you clicked on. This can be anything, from a TextBlock element to a Border element or even controls you didn't creat but were added during runtime by WPF itself. This proved to be really helpful, because now you can cast it to something a bit more meaninful and extract the DataContext from there. Depending on how you configured the DataContext, you will get the item that is bound to the element, in my case a task. The code to get a visual from the current mouse location is shown below.
T GetItemAtLocation(Point location)
{
T foundItem = default(T);
HitTestResult hitTestResults = VisualTreeHelper.HitTest(treeView,location);
if(hitTestResults.VisualHit is FrameworkElement)
{
object dataObject = (hitTestResults.VisualHit as
FrameworkElement).DataContext;
if(dataObject is T)
{
foundItem = (T)dataObject;
}
}
return foundItem;
}
It's pretty logical that Microsoft didn't include the method GetItemAtLocation in the treeview, because that would pretty much break the whole idea of having a separate visual tree. However what I don't understand at this point is why WPF doesn't fire the MouseMove event anymore when you are performing a drag-drop operation. Ooh well, the MouseUtilties class Josh created is a good solution, but I sure hope Microsoft is going to fix this problem in the next version of WPF.
[1] Drag and Drop items in a WPF listview – http://www.codeproject.com/WPF/ListViewDragDropManager.asp