views:

284

answers:

2

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 insertUser)
    {
        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.

+1  A: 

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);
}
Hogan
Thanks for the response. Just to clarify, are you saying I need to move my `DataTable` cNoteDT or the `DataRow` rows? I *think* I understand what you are saying but I am not drawing the parallels to my code snippet.
Refracted Paladin
By making a copy of the DataTable you get a copy of the rows for free and a new object to filter -- see my code above.
Hogan
Nope, they are still double posting to the treeview. See my edit for what I have been trying.
Refracted Paladin
+1  A: 

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:

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.

Oliver
Tuple? Can you explain a little on what that is doing. Thank you very much for your time.
Refracted Paladin
@Paladin: Updated my answer for the Tuple question
Oliver
@Oliver: I am still working on getting this implemented for my environment. The issue being that it was decided to use a WPF version instead, which I am new to, and I am trying to figure out the WPF equivalent of TreeNode. Thanks for the answer though.
Refracted Paladin
@Paladin: God thanks, all this WPF stuff doesn't hit me till now. There is enough stuff to learn and use, so i don't have the time to get this beta thing to work (will get better with VS2010 and .Net4).
Oliver
I just, for the heck of it, tried this in the old Winforms and it works great. Thanks
Refracted Paladin