3

I need to create a menu structure taken from a database table which uses an ID and a ParentID and a Rank which is used to determine the order of the nodes.

Root(ID 1, ParentID 0, Rank 1)
  - Node(ID 2, ParentID 1, Rank 1)
    - Node(ID 3, ParentID 2, Rank 1)
      - Node(ID 4, ParentID 3, Rank 1)
      - Node(ID 5, ParentID 3, Rank 2)
    - Node(ID 6, ParentID 2, Rank 2)
    - Node(ID 7, ParentID 2, Rank 3)
  - Node(ID 8, ParentID 1, Rank 2)
    - Node(ID 9, ParentID 8, Rank 1)
    - Node(ID 10, ParentID 8, Rank 2)

I've attempted to create a function to iterate through the SQL data and create this tree structure but I'm unsure how to handle the depth of the addition. I can add the first layer of Nodes by simply checking whether they have a ParentID, I can add the second layer of nodes in the else condition, however i'm unsure how to add the following layers of the heirarchy.

Iterating through the Database:

using (var command = new SqlCommand(_Query, _Connection))
{
    _Connection.Open();                
    var _Reader = command.ExecuteReader();
    while (_Reader.Read())
    {
        CreateNode((int)_Reader["MenuID"], (int)_Reader["ParentID"], (int)_Reader["Rank"], _Reader["English"].ToString());                    
    }
    _Connection.Close();
}

Creation of the nodes:

private void CreateNode(int id, int parentID, int rank, string text)
{            
    if(parentID == -1)
    {
        TreeNode _Node = new TreeNode(text, id.ToString());
        Root.Nodes.Add(_Node);
    }

    if (parentID != -1)
    {
        foreach (TreeNode _Node in Root.Nodes)
        {                    
            if (_Node.Value == parentID.ToString())
            {
                _Node.ChildNodes.Add(new TreeNode(text, id.ToString()) { ShowCheckBox = true } );
            }
        }
    }
}

Currently this isn't sorting the nodes by rank

I'd expect the output HTML to be something similar to the following:

<ul id="1">
    <li>A</li>
    <li>
        <ul id="2">
            <li>B</li>
            <li>
                <ul id="3">
                    <li>C</li>
                    <li>
                        <ul id="4">
                            <li>D</li>
                        </ul>
                    </li>
                    <li>
                        <ul id="5">
                            <li>E</li>
                        </ul>
                    </li>
                </ul>                
            </li>
            <li>
                <ul id="6">
                    <li>F</li>
                </ul>
            </li>
            <li>
                <ul id="7">
                    <li>G</li>
                </ul>
            </li>            
        </ul>
    </li>        
    <li>
        <ul id="8">
            <li>H</li>
            <ul>
                <li>
                    <ul id="9">
                        <li>I</li>
                    </ul>
                </li>
                <li>
                    <ul id="10">
                        <li>J</li>
                    </ul>
                </li>                
            </ul>
        </ul>
    </li>
</ul>

http://jsfiddle.net/RjE7H/

1
  • updated my answer with some example code, let me know if you need further explanation Commented Sep 27, 2013 at 10:03

3 Answers 3

1

Use a dictionary, that way you don't have to painfully scan your tree for the parent:

Dictionary<int, TreeNode> ParentCache = new Dictionary<int, TreeNode>();
private void CreateNode(int id, int parentID, int rank, string text)
{
    TreeNodeCollection parentNode = root.Nodes;
    if(parentID != 0)
    {
        TreeNode foundParentNode;
        if (!ParentCache.TryGetValue(parentID, out foundParentNode)
            throw new Exception("Given parentID has not been added to the tree yet - " + parentID.ToString());
        parentNode = foundParentNode.ChildNodes;
    }

    TreeNode newNode = new TreeNode(text, id.ToString());
    parentNode.Add(newNode);
    ParentCache.Add(id, newNode);
}

If your data is being received in the order you specified, then the output should be implicitly in rank order. Given that we are always appending to the end of TreeNodeCollections.

If you want to ignore any exception where the node parent is not found, but you still want to attach to the root, make the following changes:

    if(parentID != 0)
    {
        TreeNode foundParentNode;
        //Note: I changed the if logic from, "not TryGetValue" to "TryGetValue"
        if (ParentCache.TryGetValue(parentID, out foundParentNode)
            parentNode = foundParentNode.ChildNodes;
    }
Sign up to request clarification or add additional context in comments.

4 Comments

thanks, I've come up with a solution using your code. Only had to make a minor change to the cache which is to check whether the parentid key exists in the dictionary (otherwise it errors).
@Jack - I updated to include TryGetValue pattern for more descriptive error message.
Thanks @Todd I used: ParentCache.Keys.Contains(parentID). Guess there's not much difference. It's ok for there not to be a parent node so i haven't added an exception as it should just add it to the root node.
@Jack, you could replace the throw with a return; if you don't want an exception. The TryGetValue pattern will only enumerate the dictionary once. If you use Contains, you will also need to look up the Value - two lookups, it would not necessarily be a noticeable performance issue. In your case, I would recommend that a lookup mismatch should be an exception however. In such an exception, the source data is in error, requiring correcting of data or the query.
0

Check this out. This is not database driven but might help you in building the heirarchical tree.

TreeView tv = new TreeView();
private void populateNode()
{

   for(int i=0;i<5;i++)
   {
      var parent = new TreeNode(i,string.Format("Node{0}",i));
      tv.Nodes.Add(parent);
      for(int j=0;j<=3;j++)
      {
        var child = new TreeNode(j,string.Format("childNode{0}",j)
        parent.ChildNodes.Add(child);
        for(int k=0;k<=3;k++)
        {
          var grandchild = new TreeNode(k,string.Format("grandchildNode{0}",k)
          child.ChildNodes.Add(grandchild);

        }
      }
   }

}

Comments

0

Can't you simple

  • create a list of some custom objects which has all the properties you need from your database data (within the reader loop).
  • sort the list by Rank property
  • populate the treeview from the sorted list?

somehow like this?

some structure to store your data

class TempTreeNode
{
    public int MenuID { get; set; }
    public int ParentID { get; set; }
    public int Rank { get; set; }
    public string Lang { get; set; }
}

your code to generate the list:

        var nodeList = new List<TempTreeNode>();

        using (var command = new SqlCommand(_Query, _Connection))
        {
            _Connection.Open();
            var _Reader = command.ExecuteReader();

            while (_Reader.Read())
            {
                var node = new TempTreeNode()
                {
                    MenuID = (int)_Reader["MenuID"],
                    ParentID = (int)_Reader["ParentID"],
                    Rank = (int)_Reader["Rank"],
                    Lang = _Reader["English"].ToString()
                };
                nodeList.Add(node);
            }
            _Connection.Close();
        }
        // sorting
        nodeList.Sort((a, b) => a.Rank.CompareTo(b.Rank));
        // creation
        CreateNodes(nodeList);

The method to generate the nodes... just guessing what you want, so this is not complete...

    private List<TreeNode> CreateNodes(List<TempTreeNode> nodes)
    {
        var rootNodes = new List<TreeNode>();
        foreach (var node in nodes)
        {
            if (node.ParentID == -1)
            {
                TreeNode _Node = new TreeNode(node.Lang, node.MenuID.ToString());
                rootNodes.Add(_Node);
            }
            [...] do whatever...
        }
        return rootNodes;
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.