代码改变世界

四行代码创建复杂(无限级)树

2011-12-09 20:38  鹤冲天  阅读(7532)  评论(16编辑  收藏  举报

最近两三天一直在做树方面的基础工作,碰巧今天在博客园看到一篇文章《C#中一种通用的树的生成方式》,粗略浏览下,感觉有不够强大。

对比而言,感觉自己的方式更好些,只需要四行代码就可以创建一颗复杂的无限级树。

在此分享一下,请大家先看两个运行截图:

image

image

下面,我们来看如何实现,先给出树节点的两个类:

树节点类

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)