TreeView的Drag和Drop

本文代码基本参考WPF Drag and Drop Using Behavior

拖放

拖放操作通常涉及两个参与方:拖动对象所源自的拖动源和接收放置对象的拖放目标。 拖动源和放置目标可能是相同应用程序或不同应用程序中的 UI 元素。

Drag

Drag就是拖动源

public interface IDragable
{
    Type DataType { get; }
    object Data { get;}
}

Drag的Behavior

这里主要是在按住鼠标同时移动鼠标也就是拖动操作。这里最关键的就是拖动源通过调用静态 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

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);
}

Drop到子项的Behavior

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);
    }
}

Drop到Treeview的Behavior

这里的话,我是根据插入的鼠标位置上下的元素的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的列表中。

IDragable接口实现

这个比较简单,需要拖动的东西的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
}

IDropable接口实现

注意,因为我有往Treeview拖动的行为TreeViewDropBehavior,因此,Treeview的DataContext也需要继承IDropable接口。而正常的拖动行为FrameworkElementDropBehavior,在Treeview中是往TreeviewItem上放,因此TreeviewItem的DataContext也需要继承IDropable接口。

Treeview的DataContext
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;
    }
}
TreeviewItem的DataContext
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;
    }
}

你可能感兴趣的:(C#,WPF,wpf)