本文代码基本参考WPF Drag and Drop Using Behavior
拖放操作通常涉及两个参与方:拖动对象所源自的拖动源和接收放置对象的拖放目标。 拖动源和放置目标可能是相同应用程序或不同应用程序中的 UI 元素。
Drag就是拖动源
public interface IDragable
{
Type DataType { get; }
object Data { get;}
}
这里主要是在按住鼠标同时移动鼠标也就是拖动操作。这里最关键的就是拖动源通过调用静态 DragDrop.DoDragDrop
方法和向其传递传输的数据来启动拖放操作。
public class FrameworkElementDragBehavior : Behavior
{
private bool isMouseClicked = false;
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
this.AssociatedObject.MouseLeftButtonUp += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
this.AssociatedObject.MouseLeave += new MouseEventHandler(AssociatedObject_MouseMove);
}
void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
isMouseClicked = true;
}
void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
isMouseClicked = false;
}
void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseClicked)
{
isMouseClicked = false;
//set the item's DataContext as the data to be transferred
IDragable dragObject = this.AssociatedObject.DataContext as IDragable;
if (dragObject != null)
{
DataObject data = new DataObject();
data.SetData(dragObject.DataType, dragObject.Data);
System.Windows.DragDrop.DoDragDrop(this.AssociatedObject, data, DragDropEffects.All);
}
}
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.MouseLeftButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
this.AssociatedObject.MouseLeftButtonUp -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
this.AssociatedObject.MouseLeave -= new MouseEventHandler(AssociatedObject_MouseMove);
}
}
Drop就是拖入的目标,IDropable接口就是实现目标拖入到列表中的操作。
public interface IDropable
{
///
/// 拖入的数据类型
///
Type DataType { get; }
///
/// 拖入到列表中
///
/// 被拖入的data
void Drop(object data);
///
/// 拖入到列表中
///
/// 被拖入的data
void DropIn(object data, object above, object below);
///
/// 是否允许拖入
///
///
///
bool CanDrop(object data);
}
public class FrameworkElementDropBehavior : Behavior
{
private Type dataType; //the type of the data that can be dropped into this control
//private FrameworkElementAdorner adorner;
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.AllowDrop = true;
this.AssociatedObject.DragEnter += new DragEventHandler(AssociatedObject_DragEnter);
this.AssociatedObject.DragOver += new DragEventHandler(AssociatedObject_DragOver);
this.AssociatedObject.DragLeave += new DragEventHandler(AssociatedObject_DragLeave);
this.AssociatedObject.Drop += new DragEventHandler(AssociatedObject_Drop);
}
void AssociatedObject_Drop(object sender, DragEventArgs e)
{
if (dataType != null)
{
//drop the data
if (CanDrop(e))
{
IDropable dropObject = this.AssociatedObject.DataContext as IDropable;
dropObject.Drop(e.Data.GetData(dataType));
}
}
//if (this.adorner != null)
// this.adorner.Remove();
e.Handled = true;
return;
}
void AssociatedObject_DragLeave(object sender, DragEventArgs e)
{
//if (this.adorner != null)
// this.adorner.Remove();
e.Handled = true;
}
void AssociatedObject_DragOver(object sender, DragEventArgs e)
{
if (dataType != null)
{
if (CanDrop(e))
{
//draw the dots
//if (this.adorner != null)
// this.adorner.Update();
}
}
e.Handled = true;
}
void AssociatedObject_DragEnter(object sender, DragEventArgs e)
{
Debug.WriteLine($"{this.AssociatedObject.DataContext.GetType()}");
//if the DataContext implements IDropable, record the data type that can be dropped
if (dataType == null)
{
if (this.AssociatedObject.DataContext != null)
{
IDropable dropObject = this.AssociatedObject.DataContext as IDropable;
if (dropObject != null)
{
dataType = dropObject.DataType;
}
}
}
//this.adorner = new FrameworkElementAdorner(sender as UIElement);
e.Handled = true;
}
private bool CanDrop(DragEventArgs e)
{
IDropable dropObject = this.AssociatedObject.DataContext as IDropable;
if (dropObject.CanDrop(e.Data))
{
e.Effects = DragDropEffects.All;
return true;
}
else
{
e.Effects = DragDropEffects.None; //default to None
return false;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.DragEnter -= new DragEventHandler(AssociatedObject_DragEnter);
this.AssociatedObject.DragOver -= new DragEventHandler(AssociatedObject_DragOver);
this.AssociatedObject.DragLeave -= new DragEventHandler(AssociatedObject_DragLeave);
this.AssociatedObject.Drop -= new DragEventHandler(AssociatedObject_Drop);
}
}
这里的话,我是根据插入的鼠标位置上下的元素的Y轴,找的上下两个TreeViewItem,然后在这两个中间插入一个新的TreeViewItem。具体的增加操作放在接口的DropIn方法内部实现。
public class TreeViewDropBehavior : Behavior
{
private Type dataType; //the type of the data that can be dropped into this control
private TreeViewAdornerManager insertAdornerManager;
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.AllowDrop = true;
this.AssociatedObject.DragEnter += new DragEventHandler(AssociatedObject_DragEnter);
this.AssociatedObject.DragOver += new DragEventHandler(AssociatedObject_DragOver);
this.AssociatedObject.DragLeave += new DragEventHandler(AssociatedObject_DragLeave);
this.AssociatedObject.Drop += new DragEventHandler(AssociatedObject_Drop);
}
void AssociatedObject_Drop(object sender, DragEventArgs e)
{
//if the data type can be dropped
if (this.dataType != null)
{
if (e.Data.GetDataPresent(dataType))
{
//first find the UIElement that it was dropped over, then we determine if it's
//dropped above or under the UIElement, then insert at the correct index.
ItemsControl dropContainer = sender as ItemsControl;
//get the UIElement that was dropped over
var point = e.GetPosition(dropContainer);
// 根据鼠标位置找到上一个item跟下一个item
TreeViewItem droppedOverItem1 = GetElementFromPoint(dropContainer, new Point(point.X, point.Y - 10));
TreeViewItem droppedOverItem2 = GetElementFromPoint(dropContainer, new Point(point.X, point.Y + 10));
//drop the data
IDropable target = this.AssociatedObject.DataContext as IDropable;
target.DropIn(e.Data.GetData(dataType), droppedOverItem1?.DataContext as IDropable, droppedOverItem2?.DataContext as IDropable);
}
}
if (this.insertAdornerManager != null)
this.insertAdornerManager.Clear();
e.Handled = true;
return;
}
void AssociatedObject_DragLeave(object sender, DragEventArgs e)
{
if (this.insertAdornerManager != null)
this.insertAdornerManager.Clear();
e.Handled = true;
}
void AssociatedObject_DragOver(object sender, DragEventArgs e)
{
if (this.dataType != null)
{
if (e.Data.GetDataPresent(dataType))
{
this.SetDragDropEffects(e);
if (this.insertAdornerManager != null)
{
ItemsControl dropContainer = sender as ItemsControl;
var point = e.GetPosition(dropContainer);
// 根据鼠标位置找到上一个item跟下一个item
TreeViewItem droppedOverItem1 = GetElementFromPoint(dropContainer, new Point(point.X, point.Y - 10));
TreeViewItem droppedOverItem2 = GetElementFromPoint(dropContainer, new Point(point.X, point.Y + 10));
if (droppedOverItem2 != null)
this.insertAdornerManager.Update(droppedOverItem2, true);
else if(droppedOverItem2 == null && droppedOverItem1 != null)
this.insertAdornerManager.Update(droppedOverItem1, false);
else if (droppedOverItem2 == null && droppedOverItem1 == null)
this.insertAdornerManager.Update(dropContainer, false);
}
}
e.Handled = true;
}
}
void AssociatedObject_DragEnter(object sender, DragEventArgs e)
{
if (this.dataType == null)
{
//if the DataContext implements IDropable, record the data type that can be dropped
if (this.AssociatedObject.DataContext != null)
{
if (this.AssociatedObject.DataContext as IDropable != null)
{
this.dataType = ((IDropable)this.AssociatedObject.DataContext).DataType;
}
}
}
//initialize adorner manager with the adorner layer of the itemsControl
if (this.insertAdornerManager == null)
this.insertAdornerManager = new TreeViewAdornerManager(AdornerLayer.GetAdornerLayer(sender as ItemsControl));
e.Handled = true;
}
///
/// Provides feedback on if the data can be dropped
///
///
void SetDragDropEffects(DragEventArgs e)
{
e.Effects = DragDropEffects.None; //default to None
//if the data type can be dropped
if (e.Data.GetDataPresent(dataType))
{
e.Effects = DragDropEffects.Move;
}
}
T GetElementFromPoint(ItemsControl itemsControl, Point point) where T : class
{
UIElement element = itemsControl.InputHitTest(point) as UIElement;
while (element != null)
{
if (element == itemsControl)
return default(T);
//object item = itemsControl.ItemContainerGenerator.ItemFromContainer(element);
//if (!item.Equals(DependencyProperty.UnsetValue))
// return item as T;
if (element is T)
{
return element as T;
}
element = (UIElement)VisualTreeHelper.GetParent(element);
}
return default(T);
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.DragEnter -= new DragEventHandler(AssociatedObject_DragEnter);
this.AssociatedObject.DragOver -= new DragEventHandler(AssociatedObject_DragOver);
this.AssociatedObject.DragLeave -= new DragEventHandler(AssociatedObject_DragLeave);
this.AssociatedObject.Drop -= new DragEventHandler(AssociatedObject_Drop);
}
}
上面都是前端的东西,具体的接口实现就是业务逻辑了。这边先举个例子,比如我有一个歌曲Song可以被拖到专辑Album中,专辑Album可以被拖到Album的列表中。
这个比较简单,需要拖动的东西的DataContext继承IDragable就行了。
public class Song:IDragable
{
#region IDragable
public Type DataType => typeof(Song);
public object Data => this;
#endregion
}
public class Album:IDragable
{
#region IDragable
public Type DataType => typeof(Album);
public object Data => this;
#endregion
}
注意,因为我有往Treeview拖动的行为TreeViewDropBehavior,因此,Treeview的DataContext也需要继承IDropable接口。而正常的拖动行为FrameworkElementDropBehavior,在Treeview中是往TreeviewItem上放,因此TreeviewItem的DataContext也需要继承IDropable接口。
public class AlbumViewModel : IDropable
{
public List AlbumList = new List();
public Type DataType
{
get { return typeof(Album); }
}
public void Drop(object data)
{
// TODO
if (data is Album)
{
var album = (Album)data;
AlbumList.Add(album);
}
}
public void DropIn(object data, object above, object below)
{
//如果上下都没,添加到最下面
if (above == null && below == null)
Drop(data);
// 如果上面有,下面没有
// 这个其实跟↑很像,但是稍微有点区别,就把新来的放到上面同级
if (above != null && below == null)
{
// 先暂时跟↑一样
Drop(data);
}
// 如果上面没有,下面有,这个就不允许他插入了
//if (above == null && below != null)
//{
// return;
//}
// 如果上下都有,就要判断上下的类型了
if (above != null && below != null)
{
if (above is Album && below is Song)
{
// 插入到主程序下面的第一个
(above as Album).DropIn(data, above, below);
}
if (above is Song && below is Song)
{
(above as Song).DropIn(data, above, below);
}
}
}
public bool CanDrop(object data)
{
if (data is Album)
return true;
return false;
}
}
public class Album : IDropable
{
public List SongList = new List();
public Type DataType
{
get { return typeof(Song); }
}
public void Drop(object data)
{
if(data is Song)
{
var song = (Song)data;
SongList.Add(song);
}
}
public void DropIn(object data, object above, object below)
{
// 插入到列表下面的第一个
if (data is ActionBase)
{
var song = (Song)data;
SongList.Insert(0, song);
}
}
public bool CanDrop(object data)
{
return true;
}
}