重写TreeView,自定义图标,生成通行的下划线,取消默认获得焦点失去焦点的效果,并支持拖拽节点到外界
程序员文章站
2022-04-16 20:49:24
1、运行效果: 2、前端代码 1
View Code
View Code
View Code
View Code
1、运行效果:
2、前端代码
1 <UserControl x:Class="iPIS.UI.Base.Tree.VideoTreeControl" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 5 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 6 xmlns:local="clr-namespace:iPIS.UI.Base.Tree" 7 mc:Ignorable="d" 8 d:DesignHeight="50" d:DesignWidth="80"> 9 <UserControl.Resources> 10 <ResourceDictionary> 11 <ResourceDictionary.MergedDictionaries> 12 <ResourceDictionary Source="/iPIS.UI.Themes.Black;component/Base/Tree/VideoTreeControlImages.xaml"/> 13 </ResourceDictionary.MergedDictionaries> 14 <Style TargetType="TextBlock" x:Key="fontstyle"> 15 <Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:VideoTreeControl},Path=VideoTreeControlFontColor}"></Setter> 16 <Setter Property="FontSize" Value="14"></Setter> 17 <Setter Property="VerticalAlignment" Value="Center"></Setter> 18 </Style> 19 </ResourceDictionary> 20 </UserControl.Resources> 21 <Grid> 22 <TreeView x:Name="tree" 23 AllowDrop="True" 24 ItemsSource="{Binding DataList}" 25 Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:VideoTreeControl},Path=VideoTreeControlBackground}" 26 > 27 <TreeView.ItemTemplate> 28 <HierarchicalDataTemplate ItemsSource="{Binding Children}"> 29 <Grid x:Name="grid" 30 Margin="-1 0 0 0" 31 Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:VideoTreeControl},Path=VideoTreeControlBackground}" 32 > 33 <StackPanel Grid.Column="1" 34 Orientation="Horizontal" 35 Cursor="Hand" 36 Height="40" 37 VerticalAlignment="Center" 38 > 39 <Image x:Name="icon" Width="19" Height="16" VerticalAlignment="Center" Margin="0 0 5 0"></Image> 40 <TextBlock Text="{Binding Text}" Style="{StaticResource fontstyle}"></TextBlock> 41 <StackPanel x:Name="cc" 42 Orientation="Horizontal" 43 Visibility="Collapsed" 44 Margin="0 0 -1 0" 45 Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:VideoTreeControl},Path=VideoTreeControlBackground}" 46 > 47 <TextBlock Style="{StaticResource fontstyle}">(</TextBlock> 48 <TextBlock Text="{Binding FileCount}" Style="{StaticResource fontstyle}"></TextBlock> 49 <TextBlock Style="{StaticResource fontstyle}">)</TextBlock> 50 </StackPanel> 51 </StackPanel> 52 <!--模拟通行下划线--> 53 <Canvas> 54 <Line X1="0" Y1="39" X2="1000" Y2="39" Stroke="#4a4a4a" Margin="-500 0 0 0" StrokeThickness="1"></Line> 55 </Canvas> 56 </Grid> 57 <HierarchicalDataTemplate.Triggers> 58 <DataTrigger Binding="{Binding Type}" Value="-1"> 59 <Setter TargetName="icon" Property="Source" Value="{StaticResource icon_default}"></Setter> 60 </DataTrigger> 61 <DataTrigger Binding="{Binding Type}" Value="0"> 62 <Setter TargetName="icon" Property="Source" Value="{StaticResource icon_yuan}"></Setter> 63 </DataTrigger> 64 <DataTrigger Binding="{Binding Type}" Value="1"> 65 <Setter TargetName="icon" Property="Source" Value="{StaticResource icon_pian}"></Setter> 66 </DataTrigger> 67 <DataTrigger Binding="{Binding Type}" Value="2"> 68 <Setter TargetName="icon" Property="Source" Value="{StaticResource icon_xulie}"></Setter> 69 </DataTrigger> 70 <DataTrigger Binding="{Binding Type}" Value="-1"> 71 <Setter TargetName="cc" Property="Visibility" Value="Visible"></Setter> 72 </DataTrigger> 73 </HierarchicalDataTemplate.Triggers> 74 </HierarchicalDataTemplate> 75 </TreeView.ItemTemplate> 76 </TreeView> 77 </Grid> 78 </UserControl>
3、控件的后台代码
1 using iPIS.UI.Base.Model; 2 using iPIS.UI.Base.ViewModel; 3 using System; 4 using System.Collections.Generic; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 using System.Windows; 9 using System.Windows.Controls; 10 using System.Windows.Data; 11 using System.Windows.Documents; 12 using System.Windows.Input; 13 using System.Windows.Media; 14 using System.Windows.Media.Imaging; 15 using System.Windows.Navigation; 16 using System.Windows.Shapes; 17 18 namespace iPIS.UI.Base.Tree 19 { 20 /// <summary> 21 /// VideoTreeControl.xaml 的交互逻辑 22 /// </summary> 23 public partial class VideoTreeControl : UserControl 24 { 25 public VideoTreeControl() 26 { 27 InitializeComponent(); 28 this.DataContext = new VideoTreeControlViewModel(); 29 //默认样式 30 VideoTreeControlBackground = "#36353a"; 31 VideoTreeControlFontColor = "#9a9a9a"; 32 33 tree.SelectedItemChanged += Tree_SelectedItemChanged; 34 tree.MouseDoubleClick += Tree_MouseDoubleClick; 35 } 36 37 /// <summary> 38 /// 上下文 39 /// </summary> 40 VideoTreeControlViewModel vm 41 { 42 get 43 { 44 return this.DataContext as VideoTreeControlViewModel; 45 } 46 } 47 48 /// <summary> 49 /// 背景颜色 50 /// </summary> 51 public string VideoTreeControlBackground 52 { 53 get { return (string)GetValue(VideoTreeControlBackgroundProperty); } 54 set { SetValue(VideoTreeControlBackgroundProperty, value); } 55 } 56 57 /// <summary> 58 /// 字体颜色 59 /// </summary> 60 public string VideoTreeControlFontColor 61 { 62 get { return (string)GetValue(VideoTreeControlFontColorProperty); } 63 set { SetValue(VideoTreeControlFontColorProperty, value); } 64 } 65 66 #region 附加属性 67 68 public static readonly DependencyProperty VideoTreeControlFontColorProperty = 69 DependencyProperty.Register("VideoTreeControlFontColor", typeof(string), typeof(VideoTreeControl), new PropertyMetadata("")); 70 71 public static readonly DependencyProperty VideoTreeControlBackgroundProperty = 72 DependencyProperty.Register("VideoTreeControlBackground", typeof(string), typeof(VideoTreeControl), new PropertyMetadata("")); 73 #endregion 74 75 #region 公开事件 76 /// <summary> 77 /// 双击节点 78 /// </summary> 79 public event EventHandler<VideoTreeControlItemModel> DoubleClickTreeItem; 80 81 /// <summary> 82 /// 节点选中变更 83 /// </summary> 84 public event EventHandler<VideoTreeControlItemModel> SelectedItemChanged; 85 #endregion 86 87 /// <summary> 88 /// 选中时 89 /// </summary> 90 /// <param name="sender"></param> 91 /// <param name="e"></param> 92 private void Tree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) 93 { 94 var datamodel = e.NewValue as VideoTreeControlItemModel; 95 if (datamodel.Type == VideoTreeControlItemType.Root) return;//过滤root 96 SelectedItemChanged?.Invoke(vm, datamodel); 97 //获取方式 e.Data.GetData(typeof(Base.Model.VideoTreeControlItemModel)) 98 DragDrop.DoDragDrop(sender as FrameworkElement, datamodel, DragDropEffects.Copy); 99 100 /* 101 DragDrop.DoDragDrop(sender as FrameworkElement, new ImageTreeControlImageModel() 102 { 103 Text = "我是拖进来的", 104 Type = ImageTreeControlImageType.AlreadyMatting, 105 ImageSource = new BitmapImage(new Uri("D:\\上传资源\\xxx.png", UriKind.Absolute)) 106 }, DragDropEffects.Copy); 107 */ 108 } 109 110 /// <summary> 111 /// 双击节点 112 /// </summary> 113 /// <param name="sender"></param> 114 /// <param name="e"></param> 115 private void Tree_MouseDoubleClick(object sender, MouseButtonEventArgs e) 116 { 117 var datamodel = tree.SelectedValue as VideoTreeControlItemModel; 118 if (datamodel == null || datamodel.Type == VideoTreeControlItemType.Root) return;//过滤root 119 DoubleClickTreeItem?.Invoke(vm, datamodel); 120 } 121 } 122 }
4、控件的datacontext对象
1 using iPIS.UI.Base.Model; 2 using System; 3 using System.Collections.Generic; 4 using System.Collections.ObjectModel; 5 using System.ComponentModel; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Input; 10 11 namespace iPIS.UI.Base.ViewModel 12 { 13 public class VideoTreeControlViewModel : INotifyPropertyChanged 14 { 15 public event PropertyChangedEventHandler PropertyChanged; 16 17 private ObservableCollection<VideoTreeControlItemModel> _DataList; 18 19 /// <summary> 20 /// 数据源 21 /// </summary> 22 public ObservableCollection<VideoTreeControlItemModel> DataList 23 { 24 get => _DataList; 25 set 26 { 27 //检查是否有roo,不存在生成默认root 28 if (value.Where(c => c.Type == VideoTreeControlItemType.Root).Count() == 0) 29 { 30 _DataList = new ObservableCollection<VideoTreeControlItemModel>() { 31 new VideoTreeControlItemModel(){ 32 Text = "默认", 33 Children = value, 34 Type = VideoTreeControlItemType.Root//标示为root 35 } 36 }; 37 } 38 else 39 _DataList = value; 40 PropertyChanged?.Notify(() => this.DataList); 41 } 42 } 43 } 44 }
5、数据实体
1 using System; 2 using System.Collections.Generic; 3 using System.Collections.ObjectModel; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace iPIS.UI.Base.Model 9 { 10 /// <summary> 11 /// 视频图片树状控件,数据映射实体 12 /// </summary> 13 public class VideoTreeControlItemModel 14 { 15 private string _id = string.Empty; 16 /// <summary> 17 /// 唯一标示符 18 /// </summary> 19 public string Id 20 { 21 get 22 { 23 //如果调用者未显示给入唯一标识符,将默认生成一个唯一标识符 24 if (string.IsNullOrEmpty(_id)) _id = Guid.NewGuid().ToString(); 25 return _id; 26 } 27 set 28 { 29 _id = value; 30 } 31 } 32 33 /// <summary> 34 /// 显示值 35 /// </summary> 36 public string Text { get; set; } 37 38 /// <summary> 39 /// 绑定值 40 /// </summary> 41 public object Value { get; set; } 42 43 /// <summary> 44 /// 类型,-1:根;0:原始视频;1:片段 45 /// </summary> 46 public VideoTreeControlItemType Type { get; set; } = VideoTreeControlItemType.Original; 47 48 /// <summary> 49 /// 子集 50 /// </summary> 51 public ObservableCollection<VideoTreeControlItemModel> Children { get; set; } = new ObservableCollection<VideoTreeControlItemModel>(); 52 53 /// <summary> 54 /// 文件个数,Type=root时有效 55 /// </summary> 56 public int FileCount 57 { 58 get 59 { 60 if (Type != VideoTreeControlItemType.Root) return 0; 61 List<VideoTreeControlItemModel> outlist = new List<VideoTreeControlItemModel>(); 62 GetFiles(this, outlist); 63 return outlist.Count; 64 } 65 } 66 67 /// <summary> 68 /// 获取当前model下所有的文件实体,包含自己 69 /// </summary> 70 /// <param name="model">指定的对象</param> 71 /// <param name="outlist">匹配到的从这里返回</param> 72 private void GetFiles(VideoTreeControlItemModel model, List<VideoTreeControlItemModel> outlist) 73 { 74 if (outlist == null) outlist = new List<VideoTreeControlItemModel>(); 75 76 if (model.Type != VideoTreeControlItemType.Root) 77 { 78 outlist.Add(model); 79 } 80 foreach (var item in model.Children) 81 { 82 GetFiles(item, outlist); 83 } 84 } 85 } 86 87 /// <summary> 88 /// 视频树,子项类型 89 /// </summary> 90 public enum VideoTreeControlItemType 91 { 92 /// <summary> 93 /// 根 94 /// </summary> 95 Root = -1, 96 /// <summary> 97 /// 原始视频 98 /// </summary> 99 Original = 0, 100 /// <summary> 101 /// 片段 102 /// </summary> 103 Fragment = 1, 104 /// <summary> 105 /// 序列化 106 /// </summary> 107 Sequence = 2 108 } 109 }
上面是完整效果的全部源码,下面我讲介绍我在封装的时候遇到的问题,并附上解决方案
1、如何自定义图标
答:系统内置一些图标在资源里面,通过属性值绑定不同的图片,因为图标总共就3个所有无需调用者指定,直接内置,通过类型区分匹配即可
下面圈注关键代码
2、如何生成通行的分割线
答:通过Canvas来画线,线条尽可能的长,已达到通行的效果,并且还要距左为负数,已抵消数字控件的子集缩进
附上关键代码
3、自定义树的背景效果加上上,发现一个很囧的问题,获得焦点和失去焦点的节点背景很丑,默认获得焦点是蓝色背景,失去焦点是白色背景,欧码噶
答:肯定是要干掉,必须干掉
附上关键代码
4、由于问题3,又新出一个诡异的问题,就是遮挡不完全,会出现一个1-2像素的竖线,经过调试查看发现是节点内置了border导致的,然后这个又没办法重写掉
答:几经周折,发现利用Margin可以抵消,真是大快人心
附上bug图
这是获得焦点
这是失去焦点
解决的关键代码