Reputation: 379
I'm trying to drag and drop files in my treeview but I have no idea why it's breaking down if I run it and try dragging a file.
The code below is what I tried. Please help.
private void TreeViewItem_Drop( object sender, DragEventArgs e)
{
TreeViewItem treeViewItem = e.Source as TreeViewItem;
TreeViewItem obj = e.Data.GetData(typeof(TreeViewItem)) as TreeViewItem;
if ((obj.Parent as TreeViewItem) != null)
{
(obj.Parent as TreeViewItem).Items.Remove(obj);
}
else
{
treeViewItem.Items.Remove(obj);
treeViewItem.Items.Insert(0, obj);
e.Handled = true;
}
}
private void TreeViewItem_MouseLeftButtonDown( object sender,MouseButtonEventArgs e)
{
DependencyObject dependencyObject = _treeview.InputHitTest(e.GetPosition(_treeview)) as DependencyObject;
Debug.Write(e.Source.GetType().ToString());
if (dependencyObject is TextBlock)
{
TreeViewItem treeviewItem = e.Source as TreeViewItem;
DragDrop.DoDragDrop(_treeview, _treeview.SelectedValue, DragDropEffects.Move);
e.Handled = true;
}
}
Upvotes: 19
Views: 40104
Reputation: 144
The following code can be used as is, if you can add an IDragDrop interface to your objects that represent the data being dragged. If not, then this should still serve as a concise starting point.
Drag and drop functionality is actually implemented by Windows as opposed to Wpf or .Net, allowing for dragging and dropping across applications. As a result, initiating the drag and drop operation, and handling the potential drop are two completely separate matters. This code breaks things down into two helper classes, one for the source from which something is dragged, and the other for the potential destination where some things can be dropped.
First, the IDragDrop interface and all other relevant code aside from the helper classes (e.g., the using statements at the beginning of the file).
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Input;
namespace YourNamespaceHere;
public interface IDragDrop
{
bool CanDrag { get; }
bool CanDrop( IDragDrop draggedObject );
bool Drop( IDragDrop draggedObject );
}
The CanDrag property allows for a heterogenous tree view in which all items derive from a base class which implements IDragDrop, but subclasses that do not represent draggable data objects use public bool CanDrag => false;
. In a file browser, for example, the data type used to represent a drive might define CanDrag this way, while folders and files that can be dragged would use public bool CanDrag => true;
.
CanDrop answers whether or not a given IDragDrop object can be dropped onto the instance on which the method is called. For example, a drive or folder might return true if the IDragDrop is a file that is small enough to fit in the remaining space on the drive, or false otherwise. A file might always return false. The Drop method executes the move.
The following helper class handles initiating a drag and drop operation.
public class DragDropSourceHelper
{
private Rectangle dragBoxFromMouseDown = Rectangle.Empty;
private object mouseDownOriginalSource;
public DependencyObject DragSource { get; init; }
public Func<object, IDragDrop> GetDraggedObject { get; init; }
public void DragSource_MouseDown( object sender, MouseEventArgs e )
{
if( e.LeftButton != MouseButtonState.Pressed )
return;
var position = e.GetPosition( null );
mouseDownOriginalSource = e.OriginalSource;
dragBoxFromMouseDown = new Rectangle(
(int)(position.X - SystemParameters.MinimumHorizontalDragDistance),
(int)(position.Y - SystemParameters.MinimumVerticalDragDistance),
(int)(SystemParameters.MinimumHorizontalDragDistance * 2),
(int)(SystemParameters.MinimumVerticalDragDistance * 2) );
}
public void DragSource_MouseUp( object sender, MouseEventArgs e )
{
if( e.LeftButton != MouseButtonState.Released )
return;
dragBoxFromMouseDown = Rectangle.Empty;
}
public void DragSource_MouseMove( object sender, MouseEventArgs e )
{
if( e.LeftButton != MouseButtonState.Pressed )
return;
var position = e.GetPosition( null );
if( dragBoxFromMouseDown == Rectangle.Empty
|| dragBoxFromMouseDown.Contains( (int)position.X, (int)position.Y )
)
return;
var draggedObject = GetDraggedObject.Invoke( mouseDownOriginalSource );
if( draggedObject is null )
return;
DragDropEffects finalDropEffect = DragDrop.DoDragDrop(
DragSource,
new DataObject( typeof(IDragDrop), draggedObject),
DragDropEffects.Move );
//If the source needed to act on a completed drop action,
//it would use finalDropEffect to do so.
}
}
The public methods need to be subscribed to the events on the source control (e.g., the tree view). The DragSource object needs to be the dependency object representing the source control. The func receives the element on which the user pressed down the left mouse button, and needs to produce the IDragDrop object representing the data object to be dragged. In a tree view, this is usually accomplished by searching the visual tree up from the object parameter until finding a TreeViewItem object, and then using its DataContext as the IDragDrop return value.
One task that the above helper class handles is wrapping the dragged object in a DataObject (MS Docs), which is the wrapper that allows for data to be dragged back and forth between applications. The helper class below unwraps the dragged object, so you don't need to worry about it in this simple example. The DoDragDrop( ) method's signature allows for any object and attempts to wrap other types of objects in a DataObject itself, but this automatic wrapping did not work when I tried it here. Finding information about the DoDragDrop( ) method (MS Docs) can be difficult because it is a static method on a class that shares a name with the DragDrop event from Windows Forms.
The following helper class handles enabling the control to have some items dropped on it, except that you must still set the AllowDrop property on the target control to true yourself.
public class DragDropTargetHelper
{
public Func<object, IDragDrop, bool> CanDrop { get; init; }
public Func<object, IDragDrop, bool> Drop { get; init; }
public void DragTarget_DragOver( object sender, DragEventArgs e )
{
var draggedObject = e.Data.GetData( typeof(IDragDrop) ) as IDragDrop;
if( draggedObject is null || !CanDrop( e.OriginalSource, draggedObject ) )
e.Effects = DragDropEffects.None;
else
e.Effects = DragDropEffects.Move;
e.Handled = true;
}
public void DragTarget_Drop( object sender, DragEventArgs e )
{
var draggedObject = e.Data.GetData( typeof(IDragDrop) ) as IDragDrop;
if( Drop( e.OriginalSource, draggedObject ) )
e.Handled = true;
}
}
The CanDrop func takes as input the object over which a drop is being considered and the IDragDrop object being dragged, and determines whether the IDragDrop can be dropped on that particular object. Drop is the same, except that it actually executes the drop operation. It returns true when the drop was successfully completed, or false when the draggable object should still be considered "being dragged" because it was not successfully dropped. As with the func in the source helper, if you are using a tree view you probably want to cast the source objects to dependency objects and search up the visual tree until finding a TreeViewItem.
As with the source helper, connect the public methods to the events on the target control, and do not forget to set its AllowDrop property to true (e.g., treeview.AllowDrop = true;
). When moving tree view items around within a tree view, both the source and target controls are the Wpf TreeView control.
Done.
String the above three code blocks together into one file, and you have a self-contained set of drag and drop helper classes. You'll need to expand on this work using the e.Effects and DragDropEffects enum if you want to distinguish between different kinds of drag and drop operations, such as move versus copy.
The following code shows my application's use of these helper classes. Do.ValidateAs<Type>( object )
is essentially a cast.
using Wpf = System.Windows.Controls;
private void ConfigureDragDrop( Wpf.TreeView treeView )
{
var dragHelper = new DragDropSourceHelper
{
DragSource = treeView,
GetDraggedObject = source =>
{
var sourceDependency = Do.ValidateAs<DependencyObject>( source );
var treeViewItem = VisualUpwardSearch<Wpf.TreeViewItem>( sourceDependency );
return treeViewItem.DataContext as IDragDrop;
},
};
treeView.PreviewMouseDown += dragHelper.DragSource_MouseDown;
treeView.MouseMove += dragHelper.DragSource_MouseMove;
treeView.MouseUp += dragHelper.DragSource_MouseUp;
var dropHelper = new DragDropTargetHelper
{
CanDrop = ( source, draggedObject ) =>
{
var sourceDependency = Do.ValidateAs<DependencyObject>( source );
var treeViewItem = VisualUpwardSearch<Wpf.TreeViewItem>( sourceDependency );
if( treeViewItem?.DataContext is IDragDrop dragDrop )
return dragDrop.CanDrop( draggedObject );
else
return false;
},
Drop = ( source, draggedObject ) =>
{
var sourceDependency = Do.ValidateAs<DependencyObject>( source );
var treeViewItem = VisualUpwardSearch<Wpf.TreeViewItem>( sourceDependency );
if( treeViewItem?.DataContext is IDragDrop dragDrop )
return dragDrop.Drop( draggedObject );
else
return false;
},
};
treeView.DragOver += dropHelper.DragTarget_DragOver;
treeView.Drop += dropHelper.DragTarget_Drop;
treeView.AllowDrop = true;
}
The method you are more likely to actually want to copy that not part of the helper classes, which handles the visual tree search that I mentioned above, is as follows:
private static TFind VisualUpwardSearch<TFind>( DependencyObject source )
where TFind : class
{
while ( source != null && !(source is TFind) )
source = VisualTreeHelper.GetParent( source );
return source as TFind;
}
Upvotes: 3
Reputation: 2427
This article is very helpful. Drag drop wpf
This code may be of use to you as well.
Point _startPoint;
bool _IsDragging = false;
void TemplateTreeView_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed ||
e.RightButton == MouseButtonState.Pressed && !_IsDragging)
{
Point position = e.GetPosition(null);
if (Math.Abs(position.X - _startPoint.X) >
SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(position.Y - _startPoint.Y) >
SystemParameters.MinimumVerticalDragDistance)
{
StartDrag(e);
}
}
}
void TemplateTreeView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_startPoint = e.GetPosition(null);
}
private void StartDrag(MouseEventArgs e)
{
_IsDragging = true;
object temp = this.TemplateTreeView.SelectedItem;
DataObject data = null;
data = new DataObject("inadt", temp);
if (data != null)
{
DragDropEffects dde = DragDropEffects.Move;
if (e.RightButton == MouseButtonState.Pressed)
{
dde = DragDropEffects.All;
}
DragDropEffects de = DragDrop.DoDragDrop(this.TemplateTreeView, data, dde);
}
_IsDragging = false;
}
Upvotes: 53