简单的通用TreeView(WPF)

      工作中要为很多类创建TreeView, 很多时候仅仅是因为要显示字段不同, 就得Ctrl+C、Ctrl+V复制一份几乎相同的代码, 这难免让人生厌, 于是希望像泛型集合的扩展方法那样, 可以在使用的时候灵活指定要显示哪个字段.

       下面的TreeView要实现这样的逻辑: 父项目 被勾选 或者 取消勾选, 那么它的所有子项目全部改成 被勾选 或者 取消勾选; 只有所有的子项目都被勾选时, 父项目才自发的改成被勾选

  1. 创建基本的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
    
        }
  2. 创建通用的 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;
            }
  3. 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>
  4. 演示效果
    QQ拼音截图未命名

        实例代码下载

        后记: 说实在的, 这个通用TreeView其实一点也不通用, 简单倒是真的, 主要原因是实际业务中TreeView的层次和逻辑千差万别, 就当自己的一点尝试吧.

       

         转载请注明出处: http://www.cnblogs.com/zhouandke/p/6201064.html

 

原文地址:https://www.cnblogs.com/zhouandke/p/6201064.html