WPF中TreeView的拖拽实现

28 篇文章 3 订阅

本文代码基本参考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<FrameworkElement>
{
    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
{
    /// <summary>
    /// 拖入的数据类型
    /// </summary>
    Type DataType { get; }

    /// <summary>
    /// 拖入到列表中
    /// </summary>
    /// <param name="data">被拖入的data</param>
    void Drop(object data);
    /// <summary>
    /// 拖入到列表中
    /// </summary>
    /// <param name="data">被拖入的data</param>
    void DropIn(object data, object above, object below);
    /// <summary>
    /// 是否允许拖入
    /// </summary>
    /// <param name="dataObject"></param>
    /// <returns></returns>
    bool CanDrop(object data);
}

Drop到子项的Behavior

public class FrameworkElementDropBehavior : Behavior<FrameworkElement>
{
    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<ItemsControl>
{
    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<TreeViewItem>(dropContainer, new Point(point.X, point.Y - 10));
                TreeViewItem droppedOverItem2 = GetElementFromPoint<TreeViewItem>(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<TreeViewItem>(dropContainer, new Point(point.X, point.Y - 10));
                    TreeViewItem droppedOverItem2 = GetElementFromPoint<TreeViewItem>(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;
    }

    /// <summary>
    /// Provides feedback on if the data can be dropped
    /// </summary>
    /// <param name="e"></param>
    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<T>(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<Album> AlbumList = new List<Album>();
	
    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<Song> SongList = new List<Song>();
	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;
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WPF,可以使用TreeView控件来实现浏览文件夹的功能。下面是一种简单的方法来实现这个功能: 1. 首先,在XAML创建一个TreeView控件,用于显示文件夹和文件的层次结构。例如: ```xaml <TreeView Name="folderTreeView" /> ``` 2. 在代码,可以使用以下步骤来动态加载文件夹的层次结构: (1)获取根文件夹的路径。 (2)创建一个TreeViewItem节点,并设置其Header文本为根文件夹的名称。 (3)获取根文件夹下的所有子文件夹和文件的路径。 (4)递归地为每个子文件夹创建TreeViewItem节点,并将其添加到父节点的Items集合。 (5)在每个文件夹节点下添加叶子节点,用于显示文件。 下面是一个简化的例子: ```csharp private void AddFolderToTreeView(string folderPath, TreeViewItem parentNode) { // 获取文件夹的名称 string folderName = Path.GetFileName(folderPath); // 创建一个TreeViewItem节点并设置Header属性 TreeViewItem folderNode = new TreeViewItem(); folderNode.Header = folderName; // 添加节点到父节点的Items集合 parentNode.Items.Add(folderNode); try { // 获取文件夹下的所有子文件夹和文件 string[] subDirectories = Directory.GetDirectories(folderPath); string[] files = Directory.GetFiles(folderPath); // 递归添加子文件夹节点 foreach (string subDirectory in subDirectories) { AddFolderToTreeView(subDirectory, folderNode); } // 添加文件节点 foreach (string file in files) { TreeViewItem fileNode = new TreeViewItem(); fileNode.Header = Path.GetFileName(file); folderNode.Items.Add(fileNode); } } catch (UnauthorizedAccessException) { // 处理无权限访问的异常情况 } } ``` 3. 最后,在适当的地方调用这个函数,将根文件夹的路径和TreeView的根节点传递给它。例如: ```csharp string rootFolderPath = @"C:\"; AddFolderToTreeView(rootFolderPath, folderTreeView.Items); ``` 这样,TreeView就会显示根文件夹及其子文件夹和文件的层次结构,用户可以通过展开节点来浏览文件夹。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值