1、运行效果:
2、前端代码
<UserControl x:Class="iPIS.UI.Base.Tree.VideoTreeControl"
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:iPIS.UI.Base.Tree"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="80">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/iPIS.UI.Themes.Black;component/Base/Tree/VideoTreeControlImages.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="TextBlock" x:Key="fontstyle">
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:VideoTreeControl},Path=VideoTreeControlFontColor}"></Setter>
<Setter Property="FontSize" Value="14"></Setter>
<Setter Property="VerticalAlignment" Value="Center"></Setter>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<TreeView x:Name="tree"
AllowDrop="True"
ItemsSource="{Binding DataList}"
Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:VideoTreeControl},Path=VideoTreeControlBackground}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Grid x:Name="grid"
Margin="-1 0 0 0"
Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:VideoTreeControl},Path=VideoTreeControlBackground}"
>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
Cursor="Hand"
Height="40"
VerticalAlignment="Center"
>
<Image x:Name="icon" Width="19" Height="16" VerticalAlignment="Center" Margin="0 0 5 0"></Image>
<TextBlock Text="{Binding Text}" Style="{StaticResource fontstyle}"></TextBlock>
<StackPanel x:Name="cc"
Orientation="Horizontal"
Visibility="Collapsed"
Margin="0 0 -1 0"
Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:VideoTreeControl},Path=VideoTreeControlBackground}"
>
<TextBlock Style="{StaticResource fontstyle}">(</TextBlock>
<TextBlock Text="{Binding FileCount}" Style="{StaticResource fontstyle}"></TextBlock>
<TextBlock Style="{StaticResource fontstyle}">)</TextBlock>
</StackPanel>
</StackPanel>
<!--模拟通行下划线-->
<Canvas>
<Line X1="0" Y1="39" X2="1000" Y2="39" Stroke="#4a4a4a" Margin="-500 0 0 0" StrokeThickness="1"></Line>
</Canvas>
</Grid>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="-1">
<Setter TargetName="icon" Property="Source" Value="{StaticResource icon_default}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="0">
<Setter TargetName="icon" Property="Source" Value="{StaticResource icon_yuan}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="1">
<Setter TargetName="icon" Property="Source" Value="{StaticResource icon_pian}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="2">
<Setter TargetName="icon" Property="Source" Value="{StaticResource icon_xulie}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="-1">
<Setter TargetName="cc" Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</UserControl>
3、控件的后台代码
using iPIS.UI.Base.Model;
using iPIS.UI.Base.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes; namespace iPIS.UI.Base.Tree
{
/// <summary>
/// VideoTreeControl.xaml 的交互逻辑
/// </summary>
public partial class VideoTreeControl : UserControl
{
public VideoTreeControl()
{
InitializeComponent();
this.DataContext = new VideoTreeControlViewModel();
//默认样式
VideoTreeControlBackground = "#36353a";
VideoTreeControlFontColor = "#9a9a9a"; tree.SelectedItemChanged += Tree_SelectedItemChanged;
tree.MouseDoubleClick += Tree_MouseDoubleClick;
} /// <summary>
/// 上下文
/// </summary>
VideoTreeControlViewModel vm
{
get
{
return this.DataContext as VideoTreeControlViewModel;
}
} /// <summary>
/// 背景颜色
/// </summary>
public string VideoTreeControlBackground
{
get { return (string)GetValue(VideoTreeControlBackgroundProperty); }
set { SetValue(VideoTreeControlBackgroundProperty, value); }
} /// <summary>
/// 字体颜色
/// </summary>
public string VideoTreeControlFontColor
{
get { return (string)GetValue(VideoTreeControlFontColorProperty); }
set { SetValue(VideoTreeControlFontColorProperty, value); }
} #region 附加属性 public static readonly DependencyProperty VideoTreeControlFontColorProperty =
DependencyProperty.Register("VideoTreeControlFontColor", typeof(string), typeof(VideoTreeControl), new PropertyMetadata("")); public static readonly DependencyProperty VideoTreeControlBackgroundProperty =
DependencyProperty.Register("VideoTreeControlBackground", typeof(string), typeof(VideoTreeControl), new PropertyMetadata(""));
#endregion #region 公开事件
/// <summary>
/// 双击节点
/// </summary>
public event EventHandler<VideoTreeControlItemModel> DoubleClickTreeItem; /// <summary>
/// 节点选中变更
/// </summary>
public event EventHandler<VideoTreeControlItemModel> SelectedItemChanged;
#endregion /// <summary>
/// 选中时
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Tree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var datamodel = e.NewValue as VideoTreeControlItemModel;
if (datamodel.Type == VideoTreeControlItemType.Root) return;//过滤root
SelectedItemChanged?.Invoke(vm, datamodel);
//获取方式 e.Data.GetData(typeof(Base.Model.VideoTreeControlItemModel))
DragDrop.DoDragDrop(sender as FrameworkElement, datamodel, DragDropEffects.Copy); /*
DragDrop.DoDragDrop(sender as FrameworkElement, new ImageTreeControlImageModel()
{
Text = "我是拖进来的",
Type = ImageTreeControlImageType.AlreadyMatting,
ImageSource = new BitmapImage(new Uri("D:\\上传资源\\xxx.png", UriKind.Absolute))
}, DragDropEffects.Copy);
*/
} /// <summary>
/// 双击节点
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Tree_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var datamodel = tree.SelectedValue as VideoTreeControlItemModel;
if (datamodel == null || datamodel.Type == VideoTreeControlItemType.Root) return;//过滤root
DoubleClickTreeItem?.Invoke(vm, datamodel);
}
}
}
4、控件的datacontext对象
using iPIS.UI.Base.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input; namespace iPIS.UI.Base.ViewModel
{
public class VideoTreeControlViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged; private ObservableCollection<VideoTreeControlItemModel> _DataList; /// <summary>
/// 数据源
/// </summary>
public ObservableCollection<VideoTreeControlItemModel> DataList
{
get => _DataList;
set
{
//检查是否有roo,不存在生成默认root
if (value.Where(c => c.Type == VideoTreeControlItemType.Root).Count() == )
{
_DataList = new ObservableCollection<VideoTreeControlItemModel>() {
new VideoTreeControlItemModel(){
Text = "默认",
Children = value,
Type = VideoTreeControlItemType.Root//标示为root
}
};
}
else
_DataList = value;
PropertyChanged?.Notify(() => this.DataList);
}
}
}
}
5、数据实体
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace iPIS.UI.Base.Model
{
/// <summary>
/// 视频图片树状控件,数据映射实体
/// </summary>
public class VideoTreeControlItemModel
{
private string _id = string.Empty;
/// <summary>
/// 唯一标示符
/// </summary>
public string Id
{
get
{
//如果调用者未显示给入唯一标识符,将默认生成一个唯一标识符
if (string.IsNullOrEmpty(_id)) _id = Guid.NewGuid().ToString();
return _id;
}
set
{
_id = value;
}
} /// <summary>
/// 显示值
/// </summary>
public string Text { get; set; } /// <summary>
/// 绑定值
/// </summary>
public object Value { get; set; } /// <summary>
/// 类型,-1:根;0:原始视频;1:片段
/// </summary>
public VideoTreeControlItemType Type { get; set; } = VideoTreeControlItemType.Original; /// <summary>
/// 子集
/// </summary>
public ObservableCollection<VideoTreeControlItemModel> Children { get; set; } = new ObservableCollection<VideoTreeControlItemModel>(); /// <summary>
/// 文件个数,Type=root时有效
/// </summary>
public int FileCount
{
get
{
if (Type != VideoTreeControlItemType.Root) return ;
List<VideoTreeControlItemModel> outlist = new List<VideoTreeControlItemModel>();
GetFiles(this, outlist);
return outlist.Count;
}
} /// <summary>
/// 获取当前model下所有的文件实体,包含自己
/// </summary>
/// <param name="model">指定的对象</param>
/// <param name="outlist">匹配到的从这里返回</param>
private void GetFiles(VideoTreeControlItemModel model, List<VideoTreeControlItemModel> outlist)
{
if (outlist == null) outlist = new List<VideoTreeControlItemModel>(); if (model.Type != VideoTreeControlItemType.Root)
{
outlist.Add(model);
}
foreach (var item in model.Children)
{
GetFiles(item, outlist);
}
}
} /// <summary>
/// 视频树,子项类型
/// </summary>
public enum VideoTreeControlItemType
{
/// <summary>
/// 根
/// </summary>
Root = -,
/// <summary>
/// 原始视频
/// </summary>
Original = ,
/// <summary>
/// 片段
/// </summary>
Fragment = ,
/// <summary>
/// 序列化
/// </summary>
Sequence =
}
}
上面是完整效果的全部源码,下面我讲介绍我在封装的时候遇到的问题,并附上解决方案
1、如何自定义图标
答:系统内置一些图标在资源里面,通过属性值绑定不同的图片,因为图标总共就3个所有无需调用者指定,直接内置,通过类型区分匹配即可
下面圈注关键代码
2、如何生成通行的分割线
答:通过Canvas来画线,线条尽可能的长,已达到通行的效果,并且还要距左为负数,已抵消数字控件的子集缩进
附上关键代码
3、自定义树的背景效果加上上,发现一个很囧的问题,获得焦点和失去焦点的节点背景很丑,默认获得焦点是蓝色背景,失去焦点是白色背景,欧码噶
答:肯定是要干掉,必须干掉
附上关键代码
4、由于问题3,又新出一个诡异的问题,就是遮挡不完全,会出现一个1-2像素的竖线,经过调试查看发现是节点内置了border导致的,然后这个又没办法重写掉
答:几经周折,发现利用Margin可以抵消,真是大快人心
附上bug图
这是获得焦点
这是失去焦点
解决的关键代码