【转】四行代码创建复杂(无限级)树
【http://www.cnblogs.com/ldp615/archive/2011/12/09/simple-create-a-complex-tree.html】
最近两三天一直在做树方面的基础工作,碰巧今天在博客园看到一篇文章《C#中一种通用的树的生成方式》,粗略浏览下,感觉有不够强大。
对比而言,感觉自己的方式更好些,只需要四行代码就可以创建一颗复杂的无限级树。
在此分享一下,请大家先看两个运行截图:
下面,我们来看如何实现,先给出树节点的两个类:
树节点类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class TreeNode { private List<TreeNode> _children; public TreeNode(string text, object value = null) { this.Text = text; this.Value = value; _children = new List<TreeNode>(); } public string Text { get; private set; } public object Value { get; protected set; } public TreeNode Parent { get; set; } public IEnumerable<TreeNode> Children { get { return _children; } } public void Add(TreeNode childNode) { _children.Add(childNode); childNode.Parent = this; } public void Remove(TreeNode childNode) { _children.Remove(childNode); } public override string ToString() { return Text; } } |
一个比较标准的树节点,有父结点和多个子节点。Value 属性用来保存和树结点相关的对象的值。
再加上一个泛型版本的:
1 2 3 4 5 6 | public class TreeNode<T> : TreeNode { public TreeNode(string text, T t) : base(text, t) { } public new T Value { get { return (T)base.Value; } } } |
算是一个强类型的树节点吧。
为了方便树节点的操作,我给 TreeNode 编写了一些扩展方法,如下给出本文要用的一个:
查找所有叶子节点的扩展方法
1 2 3 4 5 6 7 8 9 10 11 12 | public static class TreeNodeExtensions { public static IEnumerable<TreeNode> GetLeafNodes(this TreeNode treeNode) { foreach (var child in treeNode.Children) { if (child.Children.Any()) { foreach (var descendant in GetLeafNodes(child)) yield return descendant; } else yield return child; } } } |
一个迭推调用。
最后到了重点:
TreeBuilder 相关类
TreeBuilder 类:
1 2 3 4 5 6 7 8 9 10 11 | public static class TreeBuilder { public static BuildRootContext Build(string text) { var root = new TreeNode(text); return new BuildRootContext(root); } internal static TreeNode<T> BuildNode<T>(T t, Func<T, string> textSelect = null) { var text = textSelect != null ? textSelect(t) : Convert.ToString(t); var node = new TreeNode<T>(text, t); return node; } } |
BuildRootContext 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class BuildRootContext { private TreeNode _tree; public BuildRootContext(TreeNode tree) { this._tree = tree; } public BuildChildrenContext<T> SetItems<T>(IEnumerable<T> items) { foreach (var item in items) { var node = TreeBuilder.BuildNode(item); _tree.Add(node); } return new BuildChildrenContext<T>(_tree); } public TreeNode Tree { get { return _tree; } } } |
BuildChildrenContext 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class BuildChildrenContext<T> { private TreeNode _tree; public BuildChildrenContext(TreeNode tree) { this._tree = tree; } public TreeNode Tree { get { return _tree; } } public BuildChildrenContext<V> SetItems<V>(Func<T, IEnumerable<V>> itemSelector, Func<V, string> textSelect = null) { var leafNodes = _tree.GetLeafNodes().OfType<TreeNode<T>>(); foreach (var leafNode in leafNodes) { foreach (var child in itemSelector(leafNode.Value)) { var node = TreeBuilder.BuildNode(child, textSelect); leafNode.Add(node); } } return new BuildChildrenContext<V>(_tree); } public BuildChildrenContext<T> SetRecursiveItems(Func<T, IEnumerable<T>> itemSelector, Func<T, string> textSelect = null) { var context = this; while (_tree.GetLeafNodes().OfType<TreeNode<T>>().Any(n => itemSelector(n.Value).Any())) context = context.SetItems<T>(itemSelector, textSelect); return context; } } |
通过这三个类即可达到文首图片中的效果,但实现可不是最佳方式,效率没太考虑,命名也不太好。
不过可以运行,先用上,慢慢改进吧!
简单使用说明
有限级树
像第一张图片中的产品树,只有两级:类别和产品,使用如下代码创建:
1 2 3 4 | var tree = TreeBuilder.Build("产品") .SetItems(categories) .SetItems(category => products.Where(p => p.Category == category)) .Tree; |
第 1 行,构建根结点,传入一个字符串作为根结点的文本。
第 2 行,指定树的第一级,必须传入一个集合,上面传入的是 IEnumerable<Category>。
第 3 行,指定树的第二级,传入的是 Func<Category, IEnumerable<Product>>。
第 4 行,调用 Tree 属性,返回根结点,也是树了。
如果需要更多的级数,可将代码写在调用 Tree属性之前,也就是第 3 行和第 4 行之间。
增加一级,可以写成下面的样子(可没什么实际意义):
1 2 3 4 5 | var tree = TreeBuilder.Build("产品") .SetItems(categories) .SetItems(category => products.Where(p => p.Category == category)) .SetItems(product=>product.Name) .Tree; |
无限级树
第二张图片中的员工树是无限级的,因为员工的下属还可能有下属,是没有限制级数的。使用 SetRecursiveItems 方法构建:
1 2 3 4 | var tree = TreeBuilder.Build("员工树") .SetItems(employees.Where(e => e.ReportsTo == null)) .SetRecursiveItems(e => e.Subordinates) .Tree; |
其代码它部分和有限级树相同。
还可以在员工树增加层次,之上增加部门,之后增加爱好等等,调用 SetItmes 即可。
部门无限然后员工无限,也是可以的。
总结
通过 TreeBuilder 可以让我们极其方便的创建很复杂的树,让我们把更多的精力放在业务逻辑上(而不是程序或界面逻辑)。
文中代码编写仓促,如有 Bug 请在回复中告知。
如果本文对你有帮助或启发,不妨推荐一下让更多的朋友看到。
在线演示:
你可以通过下面两个网址,看到通过本文的代码生成的树:
源码下载:TreeDemo.rar (15KB)