工作中要为很多类创建TreeView, 很多时候仅仅是因为要显示字段不同, 就得Ctrl+C、Ctrl+V复制一份几乎相同的代码, 这难免让人生厌, 于是希望像泛型集合的扩展方法那样, 可以在使用的时候灵活指定要显示哪个字段.
下面的TreeView要实现这样的逻辑: 父项目 被勾选 或者 取消勾选, 那么它的所有子项目全部改成 被勾选 或者 取消勾选; 只有所有的子项目都被勾选时, 父项目才自发的改成被勾选
- 创建基本的CommonTreeViewItemModel
/// <summary> /// 通用的TreeViewItem模型, 仅包含最基础CheckBox(如果你觉得不好看, 在CommonTreeView.xaml中修改样式), 还包含一个 Tag(包含的对象) /// 业务逻辑: 父项目 被勾选 或者 取消勾选, 那么它的所有子项目全部改成 被勾选 或者 取消勾选; 只有所有的子项目都被勾选时, 父项目才自发的改成被勾选 /// 所有的字段都改为protected, 方便继承修改 /// </summary> public class CommonTreeViewItemModel : INotifyPropertyChanged { #region 属性 public event PropertyChangedEventHandler PropertyChanged; protected object id; /// <summary> /// 唯一性Id /// </summary> public object Id { get { return id; } set { id = value; } } protected string caption; /// <summary> /// 标题 /// </summary> public string Caption { get { return caption; } set { caption = value; if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Caption")); } } protected bool isChecked; /// <summary> /// 是否被勾选 /// </summary> public bool IsChecked { get { return isChecked; } set { isChecked = value; if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsChecked")); SetIsCheckedByParent(value); if (Parent != null) Parent.SetIsCheckedByChild(value); } } protected bool isExpanded; /// <summary> /// 是否被展开 /// </summary> public bool IsExpanded { get { return isExpanded; } set { isExpanded = value; if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsExpanded")); } } protected CommonTreeViewItemModel parent; /// <summary> /// 父项目 /// </summary> public CommonTreeViewItemModel Parent { get { return parent; } set { parent = value; } } protected List<CommonTreeViewItemModel> children = new List<CommonTreeViewItemModel>(); /// <summary> /// 含有的子项目 /// </summary> public List<CommonTreeViewItemModel> Children { get { return children; } set { children = value; } } /// <summary> /// 包含的对象 /// </summary> public object Tag { get; set; } /// <summary> /// 包含对象的类型 /// </summary> public Type TagType { get; set; } #endregion #region 业务逻辑, 如果你需要改成其他逻辑, 要修改的也就是这两行 /// <summary> /// 子项目的isChecked改变了, 通知 是否要跟着改变 isChecked /// </summary> /// <param name="value"></param> public virtual void SetIsCheckedByChild(bool value) { if (this.isChecked == value) { return; } bool isAllChildrenChecked = this.Children.All(c => c.IsChecked == true); this.isChecked = isAllChildrenChecked; if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsChecked")); if (Parent != null) Parent.SetIsCheckedByChild(value); } /// <summary> /// 自己的isChecked改变了, 所有子项目都要跟着改变 /// </summary> /// <param name="value"></param> public virtual void SetIsCheckedByParent(bool value) { this.isChecked = value; if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsChecked")); foreach (var child in Children) { child.SetIsCheckedByParent(value); } } #endregion }
- 创建通用的 CommonTreeView, 这其中最关键的就是泛型方法SetItemsSourceData<TSource, TId>
public partial class CommonTreeView : UserControl { public IList<CommonTreeViewItemModel> ItemsSourceData { get { return (IList<CommonTreeViewItemModel>)innerTree.ItemsSource; } } public CommonTreeView() { InitializeComponent(); } /// <summary> /// 设置数据源, 以及各个字段 /// </summary> /// <typeparam name="TSource">数据源类型</typeparam> /// <typeparam name="TId">主键类型</typeparam> /// <param name="itemsArray">数据源列表</param> /// <param name="captionSelector">指定显示为Caption的属性</param> /// <param name="idSelector">指定主键属性</param> /// <param name="parentIdSelector">指定父项目主键属性</param> public void SetItemsSourceData<TSource, TId>(IEnumerable<TSource> itemsArray, Func<TSource, string> captionSelector, Func<TSource, TId> idSelector, Func<TSource, TId> parentIdSelector) where TId : IEquatable<TId> { var list = new List<CommonTreeViewItemModel>(); foreach (var item in itemsArray.Where(a => object.Equals(default(TId), parentIdSelector(a)))) { var tvi = new CommonTreeViewItemModel(); tvi.Caption = captionSelector(item).ToString(); tvi.Id = idSelector(item); tvi.Tag = item; tvi.TagType = item.GetType(); list.Add(tvi); RecursiveAddChildren(tvi, itemsArray, captionSelector, idSelector, parentIdSelector); } innerTree.ItemsSource = list; return; } /// <summary> /// 递归加载children /// </summary> /// <typeparam name="TSource"></typeparam> /// <typeparam name="TId"></typeparam> /// <param name="parent"></param> /// <param name="itemsArray"></param> /// <param name="captionSelector"></param> /// <param name="idSelector"></param> /// <param name="parentIdSelector"></param> /// <returns></returns> private CommonTreeViewItemModel RecursiveAddChildren<TSource, TId>(CommonTreeViewItemModel parent, IEnumerable<TSource> itemsArray, Func<TSource, string> captionSelector, Func<TSource, TId> idSelector, Func<TSource, TId> parentIdSelector) { foreach (var item in itemsArray.Where(a => parent.Id.Equals(parentIdSelector(a)))) { var tvi = new CommonTreeViewItemModel(); tvi.Caption = captionSelector(item); tvi.Id = idSelector(item); tvi.Tag = item; tvi.TagType = item.GetType(); tvi.Parent = parent; parent.Children.Add(tvi); RecursiveAddChildren(tvi, itemsArray, captionSelector, idSelector, parentIdSelector); } return parent; }
- CommonTreeView 对应的xaml
<UserControl x:Class="WPFCommonTreeView.CommonTreeView" 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:WPFCommonTreeView" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <TreeView Name="innerTree"> <TreeView.ItemContainerStyle> <Style TargetType="TreeViewItem"> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"></Setter> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate DataType="{x:Type local:CommonTreeViewItemModel}" ItemsSource="{Binding Children}"> <CheckBox FontSize="14" FontFamily="微软雅黑" Tag="{Binding Children}" IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Caption}" /> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid> </UserControl>
- 演示效果
后记: 说实在的, 这个通用TreeView其实一点也不通用, 简单倒是真的, 主要原因是实际业务中TreeView的层次和逻辑千差万别, 就当自己的一点尝试吧.
转载请注明出处: http://www.cnblogs.com/zhouandke/p/6201064.html