I have implemented drag and drop functionality for a TreeView in WPF.
All works fine but when the scrolls are shown in the tree view, if I select an item in the tree and then and try to scroll (vertical or horizontal) the TreeView tries to perform a drag drop operation.
Here is the source code:
class TreeViewRearranger
{
private TreeView mTreeView;
private int MOVE_TOLERANCE = 10;
private TreeViewItem mDraggedItem = null;
private TreeViewItem mTargetDrop = null;
private DropAdorner mDropAdorner = null;
public void Initialize(TreeView treeView)
{
mTreeView = treeView;
SetupDragDropEvents();
}
private void SetupDragDropEvents()
{
mTreeView.MouseDown += mTreeView_MouseDown;
mTreeView.MouseMove += mTreeView_MouseMove;
mTreeView.DragOver += mTreeView_DragOver;
mTreeView.Drop += mTreeView_Drop;
mTreeView.DragEnter += mTreeView_DragEnter;
mTreeView.DragLeave += mTreeView_DragLeave;
}
void mTreeView_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left)
return;
mLastMouseDown = e.GetPosition(mTreeView);
}
void mTreeView_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
return;
Point currentPosition = e.GetPosition(mTreeView);
if ((Math.Abs(currentPosition.X - mLastMouseDown.X) <= MOVE_TOLERANCE) &&
(Math.Abs(currentPosition.Y - mLastMouseDown.Y) <= MOVE_TOLERANCE))
return;
StartDrag();
}
void mTreeView_DragLeave(object sender, DragEventArgs e)
{
RemoveAdorners();
}
void mTreeView_DragEnter(object sender, DragEventArgs e)
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(mTreeView);
mDropAdorner = new DropAdorner(mTreeView);
layer.Add(mDropAdorner);
}
private Point mLastMouseDown;
private void StartDrag()
{
mDraggedItem = mTreeView.SelectedItem as TreeViewItem;
if (mDraggedItem == null) return;
DragDropEffects finalDropEffect =
DragDrop.DoDragDrop(
mTreeView, mTreeView.SelectedValue, DragDropEffects.Move);
// Check that target is not null and item is
// dragging(moving)
if ((finalDropEffect != DragDropEffects.Move) || (mTargetDrop == null))
return;
// A Move drop was accepted
if (mDraggedItem.Header.ToString().Equals(mTargetDrop.Header.ToString()))
return;
PerformDragDrop(mDraggedItem, mTargetDrop);
mTargetDrop = null;
mDraggedItem = null;
}
private void PerformDragDrop(TreeViewItem source, TreeViewItem destination)
{
if (source == null || destination == null)
return;
OnRearrange(source, destination);
}
private static TreeViewItem GetParentTreeViewItem(DependencyObject item)
{
if (item == null)
return null;
DependencyObject parent = VisualTreeHelper.GetParent(item);
TreeViewItem parentTreeViewItem = parent as TreeViewItem;
return parentTreeViewItem ?? GetParentTreeViewItem(parent);
}
private void OnRearrange(TreeViewItem source, TreeViewItem destination)
{
if (source == null || destination == null)
return;
TreeNode sourceNode = source.Tag as TreeNode;
TreeNode destinationNode = destination.Tag as TreeNode;
TreeNode targetNode = destinationNode;
if (!destination.IsExpanded || destination.Items.Count == 0)
{
TreeViewItem parentItem = GetParentTreeViewItem(destination);
if (parentItem == null)
{
targetNode = mTreeView.Tag as TreeNode;
}
else
{
targetNode = parentItem.Tag as TreeNode;
}
}
int index = targetNode.Children.IndexOf(destinationNode) + 1;
// performed a rearrange from sourceNode to targetNode at index
}
void mTreeView_DragOver(object sender, DragEventArgs e)
{
Point currentPosition = e.GetPosition(mTreeView);
if ((Math.Abs(currentPosition.X - mLastMouseDown.X) <= MOVE_TOLERANCE) &&
(Math.Abs(currentPosition.Y - mLastMouseDown.Y) <= MOVE_TOLERANCE))
return;
// Verify that this is a valid drop and then store the drop target
TreeViewItem item = GetNearestContainer(e.OriginalSource as UIElement);
if (CheckDropTarget(mDraggedItem, item))
{
e.Effects = DragDropEffects.Move;
UpdateDropAdorner(item);
}
else
{
e.Effects = DragDropEffects.None;
UpdateDropAdorner(null);
}
e.Handled = true;
}
void mTreeView_Drop(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.None;
e.Handled = true;
// Verify that this is a valid drop and then store the drop target
TreeViewItem targetItem = GetNearestContainer
(e.OriginalSource as UIElement);
if (targetItem == null || mDraggedItem == null)
return;
mTargetDrop = targetItem;
e.Effects = DragDropEffects.Move;
UpdateDropAdorner(mTargetDrop);
RemoveAdorners();
}
private void UpdateDropAdorner(TreeViewItem targetItem)
{
mDropAdorner.UpdateTargetPosition(targetItem);
}
private TreeViewItem GetNearestContainer(UIElement element)
{
// Walk up the element tree to the nearest tree view item.
TreeViewItem container = element as TreeViewItem;
while ((container == null) && (element != null))
{
element = VisualTreeHelper.GetParent(element) as UIElement;
container = element as TreeViewItem;
}
return container;
}
private bool CheckDropTarget(TreeViewItem sourceItem, TreeViewItem targetItem)
{
if (sourceItem == null || targetItem == null)
return false;
if (sourceItem.Header.ToString().Equals(targetItem.Header.ToString()))
return false;
if (targetItem.IsDescendantOf(sourceItem))
return false;
return true;
}
private void RemoveAdorners()
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(mTreeView);
layer.Remove(mDropAdorner);
mDropAdorner = null;
}
}
You may have to attach yourself to the scrollviewers ScrollChanged event and when that event is triggered you disable drag and drop. I haven't tried it but I think that it might be a starting place to try