开发者

Populate a TreeView from an object

I am having a problem with a treeview in my WinForm app. I created a TreeViewItem class that holds the data. There are only 5 fields: CaseNoteID, ContactDate, ParentNoteID, InsertUser, ContactDetails.

public class TreeItem
{
    public Guid CaseNoteID;
    public Guid? ParentNoteID;
    public string ContactDate;
    public string InsertUser;
    public string ContactDetails;

    public TreeItem(Guid caseNoteID, Guid? parentNoteID, string contactDate, string contactDetails, string insertUs开发者_如何学Cer)
    {
        CaseNoteID = caseNoteID;
        ParentNoteID = parentNoteID;
        ContactDate = contactDate;
        ContactDetails = contactDetails;
        InsertUser = insertUser;
    }
}

The plan was to show relationships of the notes by showing a note under it's parent as determined by the ParentNoteID field. Pretty simplistic really. Unfortunately, all my attempts so far have put a "child" note, one with a ParentNoteID, in both positions. The first level AND under it's appropriate Parent.

When I step through my code my data is coming back accurately.

 List<TreeItem> items = BLLMatrix.GetTreeViewData(HUD.PersonId);
        PopulateTree(tvwCaseNotes,items);

I just don't know how to take that and populate my TreeView accurately with it. This is what I started but now I am stuck.

  public static void PopulateTree(TreeView tree, ICollection<TreeItem> items)

I just don't seem able to wrap my head around it. Do I need to split my data call up and first return all entrys with ParentNoteID = null and then go get the rest and somehow join the two?

@Hogan: I apologize for the drastic change in the question. It was evident from your response that I hadn't approached this from a good angle in the first place. In the second place, the original method still did not work.


Maybe i completely misunderstood you, but you have a flat hierarchy where each element knows its parent. Now you have to create each element and afterwards built up the hierarchy. Here is a first quick shot of such an implementation (missing cyclic checks, error handling, etc.):

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        PopulateTreeView(treeView, SampleData());
    }

    private IEnumerable<Item> SampleData()
    {
        yield return new Item { CaseID = "1" };
        yield return new Item { CaseID = "2" };
        yield return new Item { CaseID = "3" };
        yield return new Item { CaseID = "4", ParentID = "5" };
        yield return new Item { CaseID = "5", ParentID = "3" };
        yield return new Item { CaseID = "6" };
        yield return new Item { CaseID = "7", ParentID = "1" };
        yield return new Item { CaseID = "8", ParentID = "1" };
    }

    private void PopulateTreeView(TreeView tree, IEnumerable<Item> items)
    {
        Dictionary<string, Tuple<Item, TreeNode>> allNodes = new Dictionary<string, Tuple<Item, TreeNode>>();

        foreach (var item in items)
        {
            var node = CreateTreeNode(item);
            allNodes.Add(item.CaseID, Tuple.New(item, node));
        }

        foreach (var kvp in allNodes)
        {
            if (kvp.Value.First.ParentID != null)
            {
                allNodes[kvp.Value.First.ParentID].Second.Nodes.Add(kvp.Value.Second);
            }
            else
            {
                tree.Nodes.Add(kvp.Value.Second);
            }
        }
    }

    private TreeNode CreateTreeNode(Item item)
    {
        var node = new TreeNode();
        node.Text = item.CaseID;

        return node;
    }
}

public class Item
{
    public string CaseID { get; set; }
    public string ParentID { get; set; }
}

public class Tuple<T>
{
    public Tuple(T first)
    {
        First = first;
    }

    public T First { get; set; }
}

public class Tuple<T, T2> : Tuple<T>
{
    public Tuple(T first, T2 second)
        : base(first)
    {
        Second = second;
    }

    public T2 Second { get; set; }
}

public static class Tuple
{
    //Allows Tuple.New(1, "2") instead of new Tuple<int, string>(1, "2")
    public static Tuple<T1> New<T1>(T1 t1)
    {
        return new Tuple<T1>(t1);
    }

    public static Tuple<T1, T2> New<T1, T2>(T1 t1, T2 t2)
    {
        return new Tuple<T1, T2>(t1, t2);
    }
}

What is a Tuple?

Just to answer the question in the comment:

  • Take a look at Wikipedia.
  • Take a look at this StackOverflow question.

It is a simple container object holding two other objects. That's it.

I used it, cause in my Dictionary is a unqiue identifier (string CaseID) which references on two objects (TreeNode and Item). Another approach would be to make two Dictionaries as Dictionary<string, TreeNode> and Dictionary<string, Item>, but because there is a 1:1 relationship i took the tuple approach.

Maybe rename it to ItemTreeNodeContainer and it will get more clearer for you what it means in the concrete situation.


The basic idea of recursion is you use the stack as a temporary store for variables on each call. However, you are referencing a global variable in your recursive call. When you change it (via the filter function) it will invalidate all prior calls in the recursion. You need to remove the recursion or push a new copy (and not a reference like you are doing) of the control variable (the rows) on the stack.

edit based on comment

I hate putting code out there without being able to test it, but I believe something like this should work to solved the problem I described.

Here is the problem area:

// using the Find method uses a Predicate generic delegate.
if (nodeList.Find(FindNode) == null)
{
  var tmpCNoteID = dr["CaseNoteID"].ToString();
  var filter = "ParentNote='" + tmpCNoteID + "'";

  DataRow[] childRows = cNoteDT.Select(filter);

  if (childRows.Length > 0)
  {
    // Recursively call this function for all childRows
    TreeNode[] childNodes = RecurseRows(childRows);

    // Add all childnodes to this node
    node.Nodes.AddRange(childNodes);
  }

  // Mark this noteID as dirty (already added)
  nodeList.Add(node);
}

Something like this should fix the problem I see (note: this is not elegant or good code, it is just a fix to the problem I describe above, I would never put my name to this code). Also, without being able to test the code I can't even be sure this is the problem.

// using the Find method uses a Predicate generic delegate.
if (nodeList.Find(FindNode) == null)
{
  var tmpCNoteID = dr["CaseNoteID"].ToString();
  var filter = "ParentNote='" + tmpCNoteID + "'";

  DataTable DTCopy = cNoteDT.Copy()

  DataRow[] childRows = DTCopy.Select(filter);

  if (childRows.Length > 0)
  {
    // Recursively call this function for all childRows
    TreeNode[] childNodes = RecurseRows(childRows);

    // Add all childnodes to this node
    node.Nodes.AddRange(childNodes);
  }

  // Mark this noteID as dirty (already added)
  nodeList.Add(node);
}


Solved my problem using Oliver's solution. Just refactored it using Tuple that part of .Net 4.0

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        PopulateTreeView(treeView1, SampleData());
    }

    private IEnumerable<Item> SampleData()
    {
        yield return new Item { CaseID = "1" };
        yield return new Item { CaseID = "2" };
        yield return new Item { CaseID = "3" };
        yield return new Item { CaseID = "4", ParentID = "5" };
        yield return new Item { CaseID = "5", ParentID = "3" };
        yield return new Item { CaseID = "6" };
        yield return new Item { CaseID = "7", ParentID = "1" };
        yield return new Item { CaseID = "8", ParentID = "1" };
    }

    private void PopulateTreeView(TreeView tree, IEnumerable<Item> items)
    {
        Dictionary<string, Tuple<Item, TreeNode>> allNodes = new Dictionary<string, Tuple<Item, TreeNode>>();

        foreach (var item in items)
        {
            var node = CreateTreeNode(item);
            allNodes.Add(item.CaseID, Tuple.Create(item, node));
        }

        foreach (var kvp in allNodes)
        {
            if (kvp.Value.Item1.ParentID != null)
            {
                allNodes[kvp.Value.Item1.ParentID].Item2.Nodes.Add(kvp.Value.Item2);
            }
            else
            {
                tree.Nodes.Add(kvp.Value.Item2);
            }
        }
    }

    private TreeNode CreateTreeNode(Item item)
    {
        var node = new TreeNode();
        node.Text = item.CaseID;

        return node;
    }

}

public class Item
{
    public string CaseID { get; set; }
    public string ParentID { get; set; }
}

Tuple Help on MSDN:

Tuple Class

In my case I'm passing data source from entity framework: Entities.Categories and replaced Item class with Category class generated by entity framework.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜