WPF:TreeView 实现树拖拽

_

这个功能是为另一个小工具作基础实现用的

参考了:

> [WPF树控件实现节点拖动功能](https://blog.csdn.net/run_guo/article/details/100170423)

> [WPF学习- AllowDrop 用户控件启用拖放功能](https://www.cnblogs.com/kuangxiangnice/p/5573869.html)

## 一.使用事件自实现

### 1.先上全部代码

<details>

<summary>Xml代码-点击展开</summary>

```xml

<Window x:Class="WpfApp1.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:local="clr-namespace:WpfApp1"

mc:Ignorable="d"

Title="MainWindow" Height="450" Width="800">

<Window.Resources>

<HierarchicalDataTemplate x:Key="TreeViewDropItemTemplate" ItemsSource="{Binding Childern}">

<Grid MouseLeftButtonDown="mouseDown" MouseLeftButtonUp="mouseUp">

<TextBlock Name="TV_Tb" Grid.Column="1" Height="20" Text="{Binding TextName}" FontSize="16" HorizontalAlignment="Center"/>

<Rectangle Name="Rect"

MouseEnter="mouseEnter"

MouseLeave="mouseLeave"

Height="5" Width="auto" VerticalAlignment="Bottom" Fill="#00292929" Visibility="Visible" />

<Rectangle Name="Rect2" Height="1" Width="auto" VerticalAlignment="Bottom" Fill="#FF5489C9" Visibility="Collapsed" />

</Grid>

<HierarchicalDataTemplate.Triggers>

<DataTrigger Binding="{Binding Path=Add}" Value="true" >

<Setter TargetName="Rect2" Property="Visibility" Value="Visible"/>

</DataTrigger>

</HierarchicalDataTemplate.Triggers>

</HierarchicalDataTemplate>

</Window.Resources>

<Grid Margin="50">

<Border Background="#FFC7C7C7">

<TreeView x:Name="treeView" Width="240" ItemTemplate="{DynamicResource TreeViewDropItemTemplate}" HorizontalAlignment="Left"></TreeView>

</Border>

</Grid>

</Window>

```

</details>

<details>

<summary>CS后台代码-点击展开</summary>

```C#

public class TreeItem : INotifyPropertyChanged

{

public event PropertyChangedEventHandler PropertyChanged;

public string TextName { get; set; }

/// <summary>

/// 父节点

/// </summary>

public TreeItem Parent { get; set; }

/// <summary>

/// 子节点集合(若集合保持Null,则在new后,页面不会更新,所以初始化不为Null)

/// </summary>

public ObservableCollection<TreeItem> Childern { get; set; } = new ObservableCollection<TreeItem>();

bool u_Add;

public bool Add

{

get => u_Add;

set

{

u_Add = value;

if (PropertyChanged != null)

PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Add"));

}

}

}

public partial class MainWindow : Window

{

ObservableCollection<TreeItem> treeItemlist = new ObservableCollection<TreeItem>();

public MainWindow()

{

InitializeComponent();

initTree();

}

TreeItem moveItem = null;

void mouseDown(object sender, MouseEventArgs e)

{

var obj = e.OriginalSource as FrameworkElement;

var data = obj.DataContext as TreeItem;

moveItem = data;

}

void mouseUp(object sender, MouseEventArgs e)

{

var isadd = false;

if (e.OriginalSource is TextBlock) isadd = true;

var obj = e.OriginalSource as FrameworkElement;

var data = obj.DataContext as TreeItem;

if (moveItem.TextName == data.TextName)

return;

var oldParChi = moveItem.Parent?.Childern ?? treeItemlist;

if (oldParChi == null)

throw new Exception("旧子树空异常");

oldParChi.Remove(moveItem);

//判断为兄弟

if (isadd == false)

{

var list = data.Parent?.Childern ?? treeItemlist;

var borIndex = list.IndexOf(data) + 1;

moveItem.Parent = data.Parent;

list.Insert(borIndex, moveItem);

}

//新节点

if (isadd == true)

{

moveItem.Parent = data;

data.Childern.Add(moveItem);

}

moveItem = null;

data.Add = false;

}

void mouseEnter(object sender, MouseEventArgs e)

{

if (moveItem == null)

return;

var obj = e.OriginalSource as FrameworkElement;

var data = obj.DataContext as TreeItem;

if (moveItem.TextName == data.TextName)

return;

data.Add = true;

}

void mouseLeave(object sender, MouseEventArgs e)

{

if (moveItem == null)

return;

var obj = e.OriginalSource as FrameworkElement;

var data = obj.DataContext as TreeItem;

data.Add = false;

}

private void initTree()

{

var root1 = new TreeItem

{

TextName = "root1"

};

var root2 = new TreeItem

{

TextName = "root2"

};

var root3 = new TreeItem

{

TextName = "root3",

Childern = new ObservableCollection<TreeItem> { new TreeItem { TextName = "233" } }

};

foreach (var item in root3.Childern)

{

item.Parent = root3;

}

treeItemlist.Add(root1);

treeItemlist.Add(root2);

treeItemlist.Add(root3);

treeView.ItemsSource = treeItemlist;

}

}

```

</Window>

</details>

### 2.谈谈思路

基于组件TreeView,所以Grid内只有TreeView

并其内容使用自定义的模板TreeViewDropItemTemplate,并设置他们绑定的数TreeViewDropItemTemplateChildern

Triggers是用于绑定Add属性,RectangleVisibility进行是否显示控制

Rectangle分别作为鼠标移动的下划线事件触发,下划线则为加到当前节点下面否则为加到目标节点的中;第二个下划线为在目标节点的下划线样式实现

1.在按下树节点后,记录按下的节点信息,并在鼠标抬起时,获取抬起的目标控件和节点信息

2.移除对应的父节点的自己节点,并将父节点指向新的父节点,在新的父节点中添加自己到其子节点列中

## 二.继承用户组件,重写已有事件

### 1.先上全部代码:

<details>

<summary>MainXml代码-点击展开</summary>

```xml

<Window x:Class="WpfApp1.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:local="clr-namespace:WpfApp1"

mc:Ignorable="d"

Title="MainWindow" Height="450" Width="800">

<Grid Margin="50" >

<TreeView x:Name="tree" AllowDrop="True">

<TreeView.ItemTemplate>

<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding Childern}">

<local:UserControl1 ></local:UserControl1>

</HierarchicalDataTemplate>

</TreeView.ItemTemplate>

</TreeView>

</Grid>

</Window>

```

</details>

<details>

<summary>自定义组件代码-点击展开</summary>

```xml

<UserControl x:Class="WpfApp1.UserControl1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

xmlns:local="clr-namespace:WpfApp1"

mc:Ignorable="d"

d:DesignHeight="450" d:DesignWidth="800" AllowDrop="True">

<Grid >

<TextBlock Grid.Column="1" Height="20" Text="{Binding TextName}" FontSize="16" HorizontalAlignment="Center"/>

<Grid Height="5" VerticalAlignment="Bottom" Background="#00E87474">

<Rectangle Height="1" Width="auto" VerticalAlignment="Bottom" Fill="#FF5489C9">

<Rectangle.Style>

<Style TargetType="Rectangle">

<Setter Property="Visibility" Value="Collapsed" />

<Style.Triggers>

<DataTrigger Binding="{Binding Path=Add}" Value="True" >

<Setter Property="Visibility" Value="Visible"/>

</DataTrigger>

</Style.Triggers>

</Style>

</Rectangle.Style>

</Rectangle>

</Grid>

</Grid>

</UserControl>

```

</details>

<details>

<summary>CS后台代码-点击展开</summary>

```csharp

public partial class UserControl1 : UserControl

{

TreeItem _treeItem;

TreeItem treeItem

{

get

{

if (_treeItem == null)

_treeItem = DataContext as TreeItem;

return _treeItem;

}

}

public UserControl1()

{

InitializeComponent();

}

protected override void OnDragEnter(DragEventArgs e)

{

base.OnDragEnter(e);

var data = e.Data.GetData("TreeItemModel") as TreeItem;

var tagElement = e.OriginalSource as FrameworkElement;

var tagData = tagElement.DataContext as TreeItem;

var isAdd = e.OriginalSource is Rectangle || e.OriginalSource is Grid;

if (isAdd && IsAddChi(data, tagData) == false)

treeItem.Add = true;

e.Effects = DragDropEffects.Move;

if (IsAddChi(data, tagData))

e.Effects = DragDropEffects.None;

e.Handled = true;

}

protected override void OnDragLeave(DragEventArgs e)

{

base.OnDragLeave(e);

treeItem.Add = false;

var data = e.Data.GetData("TreeItemModel") as TreeItem;

var tagElement = e.OriginalSource as FrameworkElement;

var tagData = tagElement.DataContext as TreeItem;

e.Effects = DragDropEffects.Move;

if (IsAddChi(data, tagData))

e.Effects = DragDropEffects.None;

e.Handled = true;

}

protected override void OnDrop(DragEventArgs e)

{

var data = e.Data.GetData("TreeItemModel") as TreeItem;

var tagElement = e.OriginalSource as FrameworkElement;

var tagData = tagElement.DataContext as TreeItem;

if (IsAddChi(data, tagData))

return;

var isAddIn = false;

if (e.OriginalSource is TextBlock)

isAddIn = true;

var oldParChi = data.Parent?.Childern ?? data.Root;

if (oldParChi == null)

throw new Exception("旧子树空异常");

oldParChi.Remove(data);

//判断为兄弟

if (isAddIn == false)

{

var list = tagData.Parent?.Childern ?? tagData.Root;

var borIndex = list.IndexOf(tagData) + 1;

data.Parent = tagData.Parent;

list.Insert(borIndex, data);

}

//新节点

if (isAddIn == true)

{

data.Parent = tagData;

tagData.Childern.Add(data);

}

if (data.Parent == null)

data.Root = tagData.Root;

treeItem.Add = false;

}

protected override void OnDragOver(DragEventArgs e)

{

var isAdd = e.OriginalSource is Rectangle || e.OriginalSource is Grid;

if (isAdd == true)

e.Effects = DragDropEffects.Move;

else

e.Effects = DragDropEffects.Copy;

var data = e.Data.GetData("TreeItemModel") as TreeItem;

var tagElement = e.OriginalSource as FrameworkElement;

var tagData = tagElement.DataContext as TreeItem;

if (IsAddChi(data, tagData))

e.Effects = DragDropEffects.None;

e.Handled = true;

}

protected override void OnMouseDown(MouseButtonEventArgs e)

{

base.OnMouseDown(e);

var obj = e.OriginalSource as FrameworkElement;

var data = obj.DataContext as TreeItem;

DataObject dragData = new DataObject();

dragData.SetData("TreeItemModel", data);

dragData.SetData("Object", this);

DragDrop.DoDragDrop(this, dragData, DragDropEffects.Copy | DragDropEffects.Move);

}

bool IsAddChi(TreeItem old, TreeItem @new)

{

while (@new.Parent != null)

{

if (old.TextName == @new.TextName)

return true;

@new = @new.Parent;

}

return false;

}

}

```

</details>

### 2.谈谈思路

第二种方式主要是利用了自带AllowDrop拖拽方法,因为我们主要是实现拖拽,所以用Drop的好处是只需要重写事件就行,不需要在前台额外声明事件函数,二是能实现鼠标的形态变化(拖拽文件出现的移动和复制样式)

其他的思路,基本与第一种方式一样

用这种方式,需要注意的是:

1AllowDrop属性需要打开

2.注意子组件Triggers写法结构

3.打开拖拽需要执DragDrop.DoDragDrop(),不然没有拖拽效果

.Net Core 3.1 Json序列号 时间、emoji格式相关问题 2021-08-18