Populating a TreeView from a list, without root nodes whose children will not have children

StackOverflow https://stackoverflow.com/questions/22836282

سؤال

Using .NET 4 WinForms.
Let's say we have a class defined as:

public class Item
{
    public int ID { get; set; }
    public int Key { get; set; }
    public int Value { get; set; }
}

and a list:

List<Item> items;

which contains the following items:

Key    Value
---    -----
 1       1
 1       2
 1       3
 4       4
 4       5
 2       6
 2       7
 7       8

I populate a TreeView from this list such that root Tree nodes are those who have identical Key and Value. This is how I do it:

var roots = items.Where(x => x.Key == x.Value);
foreach (var root in roots)
{
    AddNode(treeView1.Nodes, root, items);
}

The function AddNode is a recursive function defined like this:

void AddNode(TreeNodeCollection nodes, Item current, IList<Item> items)
    {
        TreeNode treenode = nodes.Add(current.Value.ToString(), current.Value.ToString());

        var children = items.Where(x => x.Key == current.Value && x.ID != current.ID);
        foreach (var child in children)
        {
            AddNode(treenode.Nodes, child, items);
        }
    }

This will produce the following TreeView structure:

_1
 |_2
 | |_6
 | |_7
 |   |_8
 |
 |_3

_4
 |_5

However, what I want is to create a structure which contains only those root nodes that have at least one child which has at least one child of it's own. In this case, root item with Value "1" has a child with Value "2" which again has at least one child. Root value "4" has got one child with value "5" but that child doesn't have children so I want to omit it. The resulting TreeView would then look like this:

_1
 |_2
 | |_6
 | |_7
 |   |_8
 |
 |_3

For even more clarification, this is the code I use to clean up the TreeView after it's populated:

List<TreeNode> ToRemove = new List<TreeNode>();
foreach (TreeNode n in treeView1.Nodes)
{
    if (n.Nodes.Count == 0)
        ToRemove.Add(n);
    else if (!n.Nodes.Cast<TreeNode>().Any(x => x.Nodes.Count > 0))
        ToRemove.Add(n);
}
ToRemove.ForEach(x => treeView1.Nodes.Remove(x)));

How to perform this kind of clean up before populating the TreeView?

EDIT

Based on Aaron's suggestion, I modified the code like this:

var roots = items.Where(x => x.Key == x.Value);
foreach (var root in roots)
{
    Add2ndLevelNode(treeView1.Nodes, root, items);
}

The function "Add2ndLevelNode" looks like this:

    void Add2ndLevelNode(TreeNodeCollection nodes, Item current, IList<Item> items)
    {
        var children = items.Where(x => x.Key == current.Value && x.ID != current.ID);
        bool doIt = false;
        foreach (var child in children)
        {
            if (items.Any(x => x.Key == child.Value))
                doIt = true;
        }
        if (doIt)
        {
            TreeNode treenode = nodes.Add(current.Value.ToString(), current.Value.ToString());

            foreach (var child in children)
            {
                AddNode(treenode.Nodes, child, items);
            }
        }
    }

Is there any other, more elegant way? This way it needs to do a for-each loop over children twice.

هل كانت مفيدة؟

المحلول 2

Apparently this can't be done without looping over the children of 2nd level nodes twice in order to check if any of them have children of their own. So, based on Aaron's suggestion, I modified the code like this:

var roots = items.Where(x => x.Key == x.Value);
foreach (var root in roots)
{
    Add2ndLevelNode(treeView1.Nodes, root, items);
}

The function "Add2ndLevelNode" looks like this:

    void Add2ndLevelNode(TreeNodeCollection nodes, Item current, IList<Item> items)
    {
        var children = items.Where(x => x.Key == current.Value && x.ID != current.ID);
        bool doIt = false;
        foreach (var child in children)
        {
            if (items.Any(x => x.Key == child.Value))
                doIt = true;
        }
        if (doIt)
        {
            TreeNode treenode = nodes.Add(current.Value.ToString(), current.Value.ToString());

            foreach (var child in children)
            {
                AddNode(treenode.Nodes, child, items);
            }
        }
    }

نصائح أخرى

check before going to second level if child has count more than 1, this snippet is raw but it could be something like this

void AddNode(TreeNodeCollection nodes, Item current, IList<Item> items)
    {
        TreeNode treenode = nodes.Add(current.Value.ToString(), current.Value.ToString());

        var children = items.Where(x => x.Key == current.Value && x.ID != current.ID);
        foreach (var child in children)
        {
            if(items.Where(x => x.Key == child.Value).Count() > 1)
                AddNode(treenode.Nodes, child, items);
        }
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top