|
Here's the method I use:
public static void AddBranch(TreeNodeCollection nc, string Path, string Description, object o)
{
string[] paths = Path.Split('\\', '/');
TreeNode node;
foreach (string s in paths)
{
if (s == null || s == "")
{
continue;
}
if ((node = FindBranch(nc, s)) == null)
{
node = new TreeNode();
node.Name = Description;
node.Text = s;
node.Tag = o;
nc.Add(node);
}
nc = node.Nodes;
}
}
public static TreeNode FindBranch(TreeNodeCollection nc, string name)
{
if ((name != null) && (name != ""))
{
foreach (TreeNode n in nc)
{
if (n.Text == name)
{
return n;
}
}
}
return null;
}
Comments make it pretty obvious how to use.
No trees were harmed in the sending of this message; however, a significant number of electrons were slightly inconvenienced.
This message is made of fully recyclable Zeros and Ones
|
|
|
|
|
Great, but how am I supposed to generate the path string?? Can you kindly tell me how would you populate my table's data into your tree??
This's how I'm loading the frist two levels to my tree:
private DataTable GenDT(string strSelect)
{
if (con.State != ConnectionState.Open)
con.Open();
DataTable _dt = new DataTable();
try
{
new OracleDataAdapter(strSelect, con).Fill(_dt);
}
catch (Exception) { }
return _dt;
}
private void LoadBudgetLineItems()
{
DataTable dt = GenDT(@"SELECT CODE, NAME, PARENT FROM BUDGETLINES ORDER BY CODE");
int L1,L2;
L1 = L2 = -1;
TreeGridNode node = new TreeGridNode();
foreach (DataRow dr in dt.Rows)
{
if (dr["PARENT"].ToString().Length == 0)
{
node = treeGridView1.Nodes.Add(null, dr["CODE"], dr["NAME"], "");
node.ImageIndex = 0;
node.DefaultCellStyle.Font = boldFont;
node.DefaultCellStyle.BackColor = Color.Black;
node.DefaultCellStyle.ForeColor = Color.White;
L1++;
foreach (DataRow dr2 in dt.Rows)
{
if (dr2["PARENT"].ToString().Equals(dr["CODE"].ToString()))
{
node = treeGridView1.Nodes[L1].Nodes.Add(null, dr2["CODE"], dr2["NAME"], "");
node.ImageIndex = 1;
}
}
}
}
}
Many thanks mate!
|
|
|
|
|
That's not nice! Are you really stuck with that data structure? The reason I ask is that it assumes so many things:
That the Parent ID exists.
That the Parent ID is less than the Code ID.
That the parent ID is not the going to go recursive.
It would not take a lot of user mistakes to bomb the data structure - you are going to need some very good error checking to avoid looping or loosing data.
Honestly? I would change the data and / or process it twice, first pass to assure integrity, second pass to populate.
No trees were harmed in the sending of this message; however, a significant number of electrons were slightly inconvenienced.
This message is made of fully recyclable Zeros and Ones
|
|
|
|
|
You see, this's exactly why I didnt want to post my code at the first place
Ok, ignore the bullshit I'm running and tell me how would you manage it at both ends (Database structure and when it comes to populating the treeView control).. Please, I really need you to shed some light on my messy logic!
|
|
|
|
|
Sounds like a good question. As a first step, let us create a class named Node which will have Id and Name as properties, AddChild and FindNode as methods. Consider the following code,
public sealed class Node
{
readonly List<Node> childNodes = new List<Node>();
Node parent = null;
public Node(int id, string name)
{
this.Id = id;
this.Name = name;
}
public void AddChild(Node node)
{
node.SetParent(this);
childNodes.Add(node);
}
public void SetParent(Node parent)
{
this.parent = parent;
}
public Node FindNode(int id)
{
Node result = childNodes.SingleOrDefault(node => node.Id == id);
if (result == null)
{
foreach (Node node in childNodes)
result = node.FindNode(id);
}
return result;
}
public void PrintTree(Action<Node> callback)
{
callback(this);
foreach (Node node in childNodes)
node.PrintTree(callback);
}
public override string ToString()
{
return string.Format("Id : {0}, Parent Name : {1}, Name : {2}",
Id, parent == null ? "No parent" : parent.Name, Name);
}
public int Id { get; private set; }
public string Name { get; private set; }
} You have done with a recursive tree implementation!
If you are confused, here is the explanation.
1 - Each node holds its children nodes
2 - When a child node is added to a node, it will set the child nodes parent and add it to the children collection.
3 - FindNode() does a recursive search in all its children and tries to find a node with the specified id. If it can't find a node, returns NULL .
4 - PrintTree does a recursive search and ask each node and its children to print itself. The callback supplied will be called and you can handle this callback and do something useful. In this example, I have just printed the name.
Here is how you use it in a console application,
Node baseLine = new Node(0, "BaseLine");
baseLine.AddChild(new Node(1, "A"));
Node aNode = baseLine.FindNode(1);
aNode.AddChild(new Node(2, "A1"));
aNode.AddChild(new Node(3, "A2"));
baseLine.AddChild(new Node(4, "B"));
Node bNode = baseLine.FindNode(4);
bNode.AddChild(new Node(5, "B1"));
bNode.AddChild(new Node(6, "B2"));
baseLine.PrintTree(node => Console.WriteLine(node));
Console.ReadKey(); I wrote a blog post that explains the thought process in writing recursive methods. Read it here[^].
Hope that helps
|
|
|
|
|
Thank you Navaneeth! You're definitely one of the most helpful C# references..
I like your way of wrapping it in a class, the truth is, I'm a bit slow this week and would be greatly thankful if you can show me how would you load it from a dataset to a treeview control.. You know, in an iteration loop like for or foreach..
Here's a reminder of the relevant table structure...
ID Parent Name
------------------------
0 0 BaseLine
1 0 A
2 1 A1
3 1 A2
4 0 B
5 4 B1
6 4 B2
Thanks again mate.. I really appreciate the time you've spent to help me out and hope you soon become a MS MVP as well!
|
|
|
|
|
Here you go
bool firstIteration = true;
Node baseLine = null;
foreach (DataRow row in dt.Rows)
{
if (firstIteration)
{
baseLine = new Node(int.Parse(row["Id"].ToString()),
row["Name"].ToString());
firstIteration = false;
}
else
{
int parentId = int.Parse(row["Parent"].ToString());
Node parent = (baseLine.Id == parentId) ? baseLine : baseLine.FindNode(parentId);
parent.AddChild(new Node(int.Parse(row["Id"].ToString()),
row["Name"].ToString()));
}
}
baseLine.PrintTree(node => Console.WriteLine(node));
Console.ReadKey(); I'd suggest to avoid using DataTable in production code. You could achieve the above with a simple DataReader . DataTable uses reader internally. You may also need to add some sanity check to this code like NULL checking, data validation etc.
|
|
|
|
|
Thank you thank you thank you Navaneeth for having such a temper with me but I still dont get what your PrintTree is for I just want to fill a treeView control on a windows form
|
|
|
|
|
Muammar© wrote: but I still dont get what your PrintTree is for
Its the recursive method. It prints itself and asks children to do so. Each children will repeat this step until there is no children.
Muammar© wrote: I just want to fill a treeView control on a windows form
Well, you could have said this before. Fortunately, our node implementation is pretty well extensible. So here it goes.
You need to add the below property to Node class. This property returns parent node of a node.
public Node Parent { get { return parent; } }
Since our Node class and TreeView 's TreeNode class has different interfaces, we need a builder class that can convert Node instance into equivalent TreeNode . Name it TreeNodeBuilder .
public sealed class TreeNodeBuilder
{
Node root = null;
Dictionary<int, TreeNode> treeNodes = new Dictionary<int, TreeNode>();
public TreeNodeBuilder(Node root)
{
this.root = root;
}
public TreeNode BuildTreeNode()
{
TreeNode rootTreeNode = null;
root.PrintTree(delegate(Node node)
{
if (node.Parent == null)
{
rootTreeNode = new TreeNode(node.Name);
rootTreeNode.Tag = node;
treeNodes.Add(node.Id, rootTreeNode);
}
else
{
if (treeNodes.ContainsKey(node.Parent.Id))
{
TreeNode parent = treeNodes[node.Parent.Id];
TreeNode current = parent.Nodes.Add(node.Name);
current.Tag = node;
treeNodes.Add(node.Id, current);
}
}
});
return rootTreeNode;
}
} All you need to do is to supply your root node (baseline) to this class and call BuildTreeNode() . Here is how you do that,
TreeNodeBuilder nodeBuilder = new TreeNodeBuilder(baseLine);
treeView1.Nodes.Add(nodeBuilder.BuildTreeNode()); Run it and you are done!
|
|
|
|
|
Ok, sorry just saw your post, glad you're still up .. Just give me a minute to try it out.. please dont go anywhere stay with me for a couple of minutes
|
|
|
|
|
N a v a n e e t h wrote: // Code to create node in a loop. Same in the last post
TreeNodeBuilder nodeBuilder = new TreeNodeBuilder(baseLine); // baseline will be root node
treeView1.Nodes.Add(nodeBuilder.BuildTreeNode());
Ok, I told you I feel like a big idiot tonight so please tell me why am I getting only my root node when I write this:
Node baseLine = new Node(0, "BaseLine");
TreeNodeBuilder nodeBuilder = new TreeNodeBuilder(baseLine);
treeView1.Nodes.Add(nodeBuilder.BuildTreeNode());
I have to do some kind of iteration right?? Please show me mate.. I cant thank you enough Naveneeth, you're my hero now
ps. Dont worry, I'm not gay
|
|
|
|
|
Ok, here's what I'm doing now:
private DataTable GenDT(string strSelect)
{
if (con.State != ConnectionState.Open)
con.Open();
DataTable _dt = new DataTable();
try
{
new OracleDataAdapter(strSelect, con).Fill(_dt);
}
catch (Exception) { }
return _dt;
}
private void pop_tree()
{
DataTable dt = GenDT("SELECT ID,PARENT,NAME FROM TT ORDER BY ID");
bool firstIteration = true;
Node baseLine = null;
foreach (DataRow row in dt.Rows)
{
if (firstIteration)
{
baseLine = new Node(int.Parse(row["Id"].ToString()),
row["Name"].ToString());
firstIteration = false;
}
else
{
int parentId = int.Parse(row["Parent"].ToString());
Node parent = (baseLine.Id == parentId) ? baseLine : baseLine.FindNode(parentId);
parent.AddChild(new Node(int.Parse(row["Id"].ToString()),
row["Name"].ToString()));
}
TreeNodeBuilder nodeBuilder = new TreeNodeBuilder(baseLine);
treeView1.Nodes.Add(nodeBuilder.BuildTreeNode());
}
}
private void Form1_Load(object sender, EventArgs e)
{
pop_tree();
}
And I'm getting multiple baseline nodes.. I guess I'm placing your lines in the wrong place of the loop right??
|
|
|
|
|
Just have to move the last two lines outside the loop
You know what?? I love you
5 for all your posts and articles!!
You're one of the most most valuable MVPs and I'm going to didicate my next article to your honor
Thank you Navaneeth.. Your the best!!
|
|
|
|
|
Hey,
I'm sorry for the headache but when adding to A1 or A2 a sub node like:
ID Parent Name
------------------------
0 0 BaseLine
1 0 A
2 1 A1
3 1 A2
4 0 B
5 4 B1
6 4 B2
7 2 A1.1
8 2 A1.2
I get null reference exception here:
parent.AddChild(new Node(int.Parse(row["Id"].ToString()),
row["Name"].ToString()));
Please help
modified on Monday, August 10, 2009 2:43 PM
|
|
|
|
|
Good catch! FindNode method in the Node class was the culprit. It was greedy and not stopping the iteration even it got a successful match. I made the algorithm lazy and it works fine now. Here is the changed FindNode method.
public Node FindNode(int id)
{
Node result = childNodes.SingleOrDefault(node => node.Id == id);
if (result == null)
{
foreach (Node node in childNodes)
{
result = node.FindNode(id);
if (result != null)
break;
}
}
return result;
} Lines in bold are the changed part.
As a best practice, you may need to add sanity checks like checking the parent exist before adding a child. Also do NULL checking before methods are called. Only thing you need to take care is to ensure the parent exist before a child is added.
Thanks for the kind words. All the best
|
|
|
|
|
|
|
|
Your code is not compiling at my end. The third party control you have used is not available on my machine. Oracle is not available as well. I am using LINUX host running windows as a guest OS on a VM. So I don't have much things installed. I'd recommend to try my code first with a normal TreeView . Once you have done that and got it working, using it with your third party tree view will be trivial.
You are so kind to recommend me to MVP program. Thanks
|
|
|
|
|
Hey there Nav,
I've reached a blocked road "again".. You see, in your code bellow, can we add nodes directly to the treeview control instead of building a treenode first..
Please Nav, this's the last of it.. Once done it will solve my problem with the control.. I'm also thinking of writing an article with your member ID included so it can be from both of us, what do you think??
private void pop_GTree()
{
DataTable dt = GenDT("SELECT ID,PARENT,NAME FROM TT ORDER BY ID");
bool firstIteration = true;
Node baseLine = null;
foreach (DataRow row in dt.Rows)
{
if (firstIteration)
{
baseLine = new Node(int.Parse(row["ID"].ToString()), row["NAME"].ToString());
treeView1.Nodes.Add(row["ID"].ToString(), row["NAME"].ToString());
firstIteration = false;
}
else
{
int parentId = int.Parse(row["PARENT"].ToString());
Node parent = (baseLine.Id == parentId) ? baseLine : baseLine.FindNode(parentId);
parent.AddChild(new Node(int.Parse(row["ID"].ToString()),
row["NAME"].ToString()));
treeGridView1.Nodes.Add(parent);
treeView1.Nodes[baseLine.Id].Nodes.Add(row["ID"].ToString(),
row["NAME"].ToString());
}
}
}
|
|
|
|
|
Muammar© wrote: treeView1.Nodes.Add
This method creates a TreeNode internally. So it makes no difference.
|
|
|
|
|
Sorry Nav, I know this's becoming a headache but please take a look at this:
private void pop_GTree()
{
DataTable dt = GenDT("SELECT ID,PARENT,NAME FROM TT ORDER BY ID");
bool firstIteration = true;
Node baseLine = null;
foreach (DataRow row in dt.Rows)
{
if (firstIteration)
{
baseLine = new Node(int.Parse(row["ID"].ToString()), row["NAME"].ToString());
treeView1.Nodes.Add(row["ID"].ToString(), row["NAME"].ToString());
firstIteration = false;
}
else
{
int parentId = int.Parse(row["PARENT"].ToString());
Node parent = (baseLine.Id == parentId) ? baseLine : baseLine.FindNode(parentId);
parent.AddChild(new Node(int.Parse(row["ID"].ToString()), row["NAME"].ToString()));
treeView1.Nodes[baseLine.FindNode(parent.Id).Id].Nodes.Add(row["ID"].ToString(), row["NAME"].ToString());
}
}
TreeNodeBuilder nodeBuilder = new TreeNodeBuilder(baseLine);
}
I'm sorry but I'm totally messed up and I find it difficult to modify your code.. I just need to build it in a treeview directly instead of a seperate node and finally add it and no it's not the same as the control i'm using doesnt support the addition of a treenode with sub nodes, it will just show the first node "the baseline node" while if it's possible to add the nodes one by one in the loop directly to the tree, it will work.. I'll thankful if you can help on this as well dear friend.
|
|
|
|
|
Ok, I think I'm getting somewhere..
After the creation of your treeNode object, we dive in it again in a loop to fetch a node and add it to its parent in the tree.. However, I'm still missing something, please take a look..
foreach (DataRow row in dt.Rows)
{
int parentId = int.Parse(row["PARENT"].ToString());
treeView1.Nodes[baseLine.FindNode(parentId).Id].Nodes.Add(row["ID"].ToString(), row["NAME"].ToString());
}
|
|
|
|
|
Hey there Nav,
Just came up with another way to do it and thought you should know...
private DataTable GenDT(string strSelect)
{
if (con.State != ConnectionState.Open)
con.Open();
DataTable _dt = new DataTable();
try
{
new OracleDataAdapter(strSelect, con).Fill(_dt);
}
catch (Exception) { }
return _dt;
}
private void LoadChilds(string _Parent, TreeNode _Node)
{
DataTable dt = GenDT("SELECT ID,PARENT,NAME FROM TT WHERE PARENT ="+_Parent+" ORDER BY ID");
for (int x = 0; x < dt.Rows.Count;x++ )
{
_Node.Nodes.Add(dt.Rows[x]["ID"].ToString(), dt.Rows[x]["NAME"].ToString());
LoadChilds(dt.Rows[x]["ID"].ToString(), _Node.Nodes[x]);
}
}
private void pop_GTree()
{
DataTable dt;
dt = GenDT("SELECT ID,PARENT,NAME FROM TT WHERE PARENT = 0 ORDER BY ID");
foreach (DataRow row in dt.Rows)
treeView1.Nodes.Add(row["ID"].ToString(), row["NAME"].ToString());
foreach(TreeNode tn in treeView1.Nodes)
LoadChilds(tn.Name, tn);
}
Haven't put it through a heavy test but it looks fine.. Can you please take a look with your sharp logic and let me know if there's any potential run-time problem in the LoadChilds method?? Thank you Nav, and what about the co-article thing?? Are you in?? I suppose we write about this together and I manage the control part and loading the tree using this way, and you handle the linq part.. what do you say?? CPians love co-articles
|
|
|
|
|
This looks OK other than it is requesting each time to database. If you can afford that, this will work. Other alternative is to load the whole data into a DataTable first, and LoadChilds method look into this DataTable instead of querying database. DataTable has a Select() method which can be useful here.
Muammar© wrote: and what about the co-article thing??
I appreciate your offer. But right now I am in a hurry and getting my things packed as I am traveling to US for an official trip. So I guess my schedule in the coming days will be busy and probably not even gets time to check mail.
Also, this is a basic concept and CPians will have pretty good idea about this. Not sure that article will be clicked. You can try to post it as a blog. I found this[^] article to be quite related to the tree view concept. It has got a similar table structure.
|
|
|
|
|