Last try at this treeview

J

John Rogers

I had given up on this for a while, but I am attempting it again.

This is what i am using to store my treeview layout into a db, I
am only using a few fields to make it easier to understand while
I am attempting to restore it back into the tree.

Text "Parent0", Level 0
Text "Child0", Level 1
Text "SubChild0", Level 2

Now I have read many posts where others are saying to store a unique
id
of the parent. I am having a bit of trouble trying to store an id
somewhere
and retrieve it on the next loop.

This is what I am looking at in the debugger, I am looking for
something that
I can use that is unique. This is a multilayer, many roots and many
nested nodes
in the treeview. This is just a sample I am posting.

//-------------------- Debugger values ------------------------------
+ Parent null System.Windows.Forms.TreeNode
Text "Parent0" string
Level 0 int
+ FirstNode {Text = "Child0"} System.Windows.Forms.TreeNode
+ NextNode {Text = "Parent1"} System.Windows.Forms.TreeNode
+ NextVisibleNode {Text = "Child0"} System.Windows.Forms.TreeNode

+ Parent {Text = "Parent0"} System.Windows.Forms.TreeNode
Text "Child0" string
Level 1 int
+ FirstNode {Text = "SubChild0"} System.Windows.Forms.TreeNode
+ NextNode null System.Windows.Forms.TreeNode
+ NextVisibleNode {Text = "SubChild0"} System.Windows.Forms.TreeNode

+ Parent {Text = "Child0"} System.Windows.Forms.TreeNode
Text "SubChild0" string
Level 2 int
+ FirstNode {Text = "SubSubChild0"} System.Windows.Forms.TreeNode
+ NextNode null System.Windows.Forms.TreeNode
+ NextVisibleNode {Text = "SubSubChild0"}
System.Windows.Forms.TreeNode

//-------------------- Debugger values ------------------------------

Lets say I start the loop, my first node will be "Parent0" it does not
have a parent, its a
rootnode, the ParentID for this will be NULL. On th next round I come
up with "Child0"
and that node has a parent which is "Parent0". Now how would I
generate a GUID for
Parent0, and tell Child0 who its parent is? Even if I create an
instance of my struct and store
the information in the struct. That information will be lost on my
next round of the loop because
I have to create a new instance of the struct again.

I am pretty confused right now, maybe I am over analyzing everything
and thats why i cant
get anything to work. Here is some sample code that leads to nowhere.


SaveTreeViewRecords SaveRecords = new
SaveTreeViewRecords();
foreach (TreeNode node in TreeView1.Nodes)
{
SaveRecords.SaveDBRecords(node);
}

class SaveTreeViewRecords
{
public void SaveDBRecords(TreeNode node)
{
// create dataset connections here

String parentID = Guid.NewGuid().ToString();
String nodeText = node.Text;
int nodeLevel = node.Level;

// store information to db here

foreach (TreeNode childNode in node.Nodes)
{
SaveDBRecords(childNode);
}
}
}

My class is longer than that, but I just want to keep this simple
since I am confused already.

This is what my sample tree look like until I can get this thing
working.

for (int x = 0; x < 2; ++x)
{
TreeNode ParentNode = tvMain.Nodes.Add("Parent" +
x.ToString());
TreeNode ChildNode = ParentNode.Nodes.Add("Child" +
x.ToString());
TreeNode SubChildNode = ChildNode.Nodes.Add("SubChild"
+ x.ToString());
TreeNode SubSubChildNode =
SubChildNode.Nodes.Add("SubSubChild" + x.ToString());
}

Once I can get the storing of the information to the DB, then I can
worry about loading it back
in. But thats another headache I am not eady to tackle just yet.

Thanks

John
 
P

Peter Duniho

[...]
Lets say I start the loop, my first node will be "Parent0" it does not
have a parent, its a
rootnode, the ParentID for this will be NULL. On th next round I come
up with "Child0"
and that node has a parent which is "Parent0". Now how would I
generate a GUID for
Parent0, and tell Child0 who its parent is?

You don't need to tell Child0 who its parent is, but even if you did it's
as easy as doing so in the loop where you enumerate the children of the
parent.

In your own example, I think the main thing you need to do is add a
parameter to the SaveDBRecords() method that takes the Guid you created.
Pass null for the parameter at the top level, and then pass the parent
Guid when looping for each child. The parent Guid would be written along
with the rest of each node's information (handling null as appropriate to
your database, of course). Of course, the node's information should
include its own Guid as well, so for each node record you'll be writing
two Guids.

I should say: you don't seem all that familiar with recursion, and you've
picked a somewhat challenging task with which to introduce yourself to
recursion. You might want to review some more basic examples and try to
become more comfortable with recursive algorithms before trying to get
this one done. It might make your life easier.

Pete
 
J

John Rogers

Thanks for the tip Pete.


I should say: you don't seem all that familiar with recursion, and
you've picked a somewhat challenging task with which to introduce
yourself to

Actually I hate recursion. Even when I coded in c++builder I always
had problems with
recursion. Just as you are helping me here, it was the same there,
others helped me out
until I got the hang of it.

But really, the treeview is really the only control that I have
problems with. It takes a lot
of though on how to handle the recursion. I actually do have it
working, its just a matter
of writing a guid for each record which is not a problem. But i was
thinking too technical
and that causes me more problems than I would normally have.

I will implement what you said and see how much further I can get with
the tree structure.


John
 
B

bob

I had given up on this for a while, but I am attempting it again.

This is what i am using to store my treeview layout into a db, I
am only using a few fields to make it easier to understand while
I am attempting to restore it back into the tree.

Text "Parent0", Level 0
Text "Child0", Level 1
Text "SubChild0", Level 2
<Stuff Deleted>

Hi John,
How about deriving a class from treenode that has a 'NodeId' and a
'PrevNodeId' property.

Also have a int that can provide a sequence of Ids during the
creation phase.

Everytime you create a node you assign the next value of the int to
the NodeId.


I take it your business logic decides who the parent is of any node.
This logic assigns the PrevNodeId field.

Roots are detectable because their PrevNodeId is 0
hth
Bob
 
J

John Rogers

Hi John,
How about deriving a class from treenode that has a 'NodeId' and a
'PrevNodeId' property.

Hi Bob,

Thats exactly what I have. I thought I had it figured out a little
while ago, but when I
changed the structure of the tree, I realized that I am still lost.
This is my code so far.

Don't laugh, my code may look funny but I code like this to get things
working. Then when
its working I start fixing up the code to make it more efficient.

I am not using anything in the struct yet, I am just using some global
variables for now.

// layout of my tree
struct NodeLayout
{
public string node_id;
public string parent_id;
public string node_text;
public int node_level;
}
// this will store some temp variables.
public class tmpvar
{
public static String ParentGUID = "";
public static String NodeGUID = "";
public static String NodeParent = "";
}

class SaveTreeViewRecords
{
public void SaveDBRecords(TreeNode node)
{
// create a connection to the db stuff here

// setup some temp variables to store in the struct
String nodeText = node.Text;
int nodeLevel = node.Level;
int imageIndex = node.ImageIndex;
bool nodeExpanded = node.IsExpanded;

if (node.Parent == null)
tmpvar.NodeParent = "";
else
tmpvar.NodeParent = node.Parent.Text;

// root nodes
if (node.Level == 0)
{
tmpvar.NodeGUID = Guid.NewGuid().ToString();
tmpvar.ParentGUID = "";
}
else // child nodes
{
if (node.Parent.Text == tmpvar.NodeParent)
{
tmpvar.ParentGUID = tmpvar.NodeGUID;
tmpvar.NodeGUID = Guid.NewGuid().ToString();
}
}

drNewRow["NodeGUID"] = tmpvar.NodeGUID;
drNewRow["ParentGUID"] = tmpvar.ParentGUID;
drNewRow["NodeText"] = node.Text;
drNewRow["NodeLevel"] = node.Level;

// apply my db changes here

// loop again
foreach (TreeNode childNode in node.Nodes)
{
SaveDBRecords(childNode);
}
}
}

// I create some temp nodes with this code for testing
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
for (int x = 0; x < 3; ++x)
{
TreeNode ParentNode = treeView1.Nodes.Add("Parent" +
x.ToString());
TreeNode ChildNode = ParentNode.Nodes.Add("Child" +
x.ToString());
TreeNode SubChildNode = ChildNode.Nodes.Add("SubChild"
+ x.ToString());
TreeNode TestNode = ParentNode.Nodes.Add("TestChild" +
x.ToString());
TreeNode SubSubChildNode =
SubChildNode.Nodes.Add("SubSubChild" + x.ToString());
}
treeView1.ExpandAll();
treeView1.EndUpdate();

You see Pete thinks the recursion is killing me, but it's not. Its
trying to get these unique id's into the db
and being able to tell who belongs to who. I threw a wrench into the
code when I added in two nodes
with the same parent, that just ruined it for me.

To be honest with you, I really don't care how the tree layout is
stored to the database. But in order to
store the layout into a db, you need the values of every single node
that you are going to insert into the db.

And when I get ready to restore it from the db back to the tree, my
next problem will be telling the node
where to add the child. Adding the roots are easy, but this syntax is
throwing me a curve. If I knew
delphi it would be so much easier to get used to this syntax.


John
 
P

Peter Duniho

[...]
// layout of my tree
struct NodeLayout
{
public string node_id;
public string parent_id;
public string node_text;
public int node_level;
}

Where would you ever use this struct? It's not required for the basic
problem description you've offered.
// this will store some temp variables.
public class tmpvar
{
public static String ParentGUID = "";
public static String NodeGUID = "";
public static String NodeParent = "";
}

Static variables and recursion are not typically things that go with each
other. Very rarely, you might need to keep some global state, but the
point of recursion is to keep the problem isolated in way that treats each
iteration of recursion individually. You can't do this with static
variables, because deeper iterations of the recursion affect the data in
use later by the shallower iterations. The only way this would be okay is
if your recursion is strictly tail recursion, and in that case you
wouldn't need recursion anyway.

The fact that you state that the recursion isn't part of your difficulty
is contrary to the code you've posted as well as to your questions. IMHO,
as long as you believe that understanding the recursion isn't part of the
problem, you won't make much progress to really understanding the problem.

And in fact, in the code you posted, these static variables and your
apparent misunderstanding regarding how they botch up the recursion are in
fact the entire cause of the problem. So, looking at that code you
posted...
class SaveTreeViewRecords
{
public void SaveDBRecords(TreeNode node)
{
// create a connection to the db stuff here

// setup some temp variables to store in the struct
String nodeText = node.Text;
int nodeLevel = node.Level;
int imageIndex = node.ImageIndex;
bool nodeExpanded = node.IsExpanded;

Since you don't use the struct, obviously the above is superfluous.
if (node.Parent == null)
tmpvar.NodeParent = "";
else
tmpvar.NodeParent = node.Parent.Text;

What's the point of this? This is the sort of thing I'm talking about: in
a proper recursive implementation, there would be no need to store any of
the state of the recursion in a static variable. And indeed, looking at
the rest of the implementation...
// root nodes
if (node.Level == 0)
{
tmpvar.NodeGUID = Guid.NewGuid().ToString();
tmpvar.ParentGUID = "";
}
else // child nodes
{
if (node.Parent.Text == tmpvar.NodeParent)
{
tmpvar.ParentGUID = tmpvar.NodeGUID;
tmpvar.NodeGUID = Guid.NewGuid().ToString();
}
}

What's up with this last if() statement? Shouldn't tmpvar.NodeParent
always be the empty string when node.Level is 0, and set to
node.Parent.Text when it's not? Given that, shouldn't the condition in
the if() statement always be true? And, heaven forbid, you've got a bug
in which it's not, you've got code that can get all the way to saving the
node information in a database without having either the parent or node
Guid initialized.

Because tmpvar.NodeParent is always initialized just before it's checked,
the fact that it's a static variable should not cause any problems in this
particular case. But it also means that it could just as easily be a
local variable.

Of course, really when you look at the whole thing what it means is that
you don't need that variable at all.

Of greater concern is the line that assigns "tmpvar.ParentGUID" for
non-root nodes. This is where using a static variable is screwing you
up. You need for every sibling node to have the same parent, by
definition, and for this to be accurate recorded in your database.

But only the first sibling node at any level of the tree will get the
correct parent Guid because when you process the children for that first
sibling, the algorithm overwrites the parent Guid value you're using. The
next sibling will get the Guid for the _last_ node you wrote to the
database, not the actual parent of that node.

You should remove the code that assigns anything to tmpvar.NodeParent (and
the variable itself, along with all of the other static variables, for
that matter), and remove the conditional around the initialization of the
Guids for non-root nodes. As for the big problem of using
"tmpvar.ParentGUID" (that is, the error that's really causing your
problems), see my comments later in this post.
drNewRow["NodeGUID"] = tmpvar.NodeGUID;
drNewRow["ParentGUID"] = tmpvar.ParentGUID;
drNewRow["NodeText"] = node.Text;
drNewRow["NodeLevel"] = node.Level;

This part looks well enough to me. Though, as I pointed out before: you
need to store either the depth of the node ("node.Level") _or_ the parent
of the node ("NodeGUID"), but there's no need to store both. Not only is
it wasteful, it just creates new opportunities for bugs.
// apply my db changes here

// loop again
foreach (TreeNode childNode in node.Nodes)
{
SaveDBRecords(childNode);
}

See here? As I explained before, you need to pass the parent Guid so that
it can be used when writing the child nodes. Doing it that way will
provide for a local variable (the parameter itself) that always has the
correct, current Guid for the parent of the currently-processed nodes.

So, instead of keeping the parent Guid in a static variable, just pass it
as a parameter. Then each node will always have an accurate parent Guid
when it's written to the database.
}
}

[...]
You see Pete thinks the recursion is killing me, but it's not.

Yes, it is.
Its trying to get these unique id's into the db
and being able to tell who belongs to who.

If you were doing the recursion correctly, this would happen naturally.
Because you aren't doing the recursion correctly, you aren't able to put
the correct data into your database.
I threw a wrench into the code when I added in two nodes
with the same parent, that just ruined it for me.

Yup, it would. Because the recursion isn't correct.
To be honest with you, I really don't care how the tree layout is
stored to the database. But in order to
store the layout into a db, you need the values of every single node
that you are going to insert into the db.

No, you don't. You only need the pertinent information for each group of
siblings, and this is easy to provide with a very simple recursive
algorithm. When done correctly.

When it's not done correctly, yes...it can be quite a lot of trouble to
get it right.
And when I get ready to restore it from the db back to the tree, my
next problem will be telling the node
where to add the child.

Again, if the recursion is done correctly, this is very easy. When you
read a new node from the database, you add it to a dictionary indexed by
the node's Guid. Then to find the node's parent, you retrieve the parent
node from that same dictionary, using the _parent_ Guid that was stored
with the child.

(You could actually take advantage of knowing the order in which the nodes
are written to the database in order to find the parent without the
dictionary, but that would complicate your code and completely miss the
point of using a Guid or other unique ID for each node in the database in
the first place. If you're going to rely on the order the nodes are put
into the database, you might as well just store the depth for each node,
rather than having an explicit reference to the parent).
Adding the roots are easy, but this syntax is
throwing me a curve. If I knew
delphi it would be so much easier to get used to this syntax.

It's not the syntax. It's the basic structure of the algorithm. The
errors you've made would be the same in any similar language (i.e. one
that requires that you actually understand the recursive nature of your
data structure, unlike the apparent behavior of the C++Builder you're used
to).

As long as you keep thinking this is a simple syntax problem, you will
continue to have problems.

Pete
 
B

bob

Hi Bob,

Thats exactly what I have. I thought I had it figured out a little
while ago, but when I
changed the structure of the tree, I realized that I am still lost.
This is my code so far.
<Stuff Deleted>

Hi John,
Had a bit of play with your code.
I think the problem is more a conceptual one rather than a coding one.
You are creating and placing the nodes on the tree then trying to set
the node ids etc as you are writing to the database.

The following snippet shows what I was getting at;
1) Create nodes and add them to a collection (Don't try and place
them)
2) Use business logic to derive the ParentID for each node in the
collection (Ok I have simulated the business logic in the node
creation but you could iterate through the collection doing the same
thing)

3) Use a recursive routine to place the nodes on the tree.

This also simplifies the database side.
You write the collection to the database.
You retrieve the collection from the database and feed it to the
Placenodes routine.

There are probably more elegant ways of doing this but
it works...
Code follow for;
Form
clsMyTreeNode

hth

Bob
P.S. Don't forget you can hang objects on the tag property of the
treenodes (allowing full separation of the data from the tree
structure) i.e. Database tree table and linked DataTables that provide
the Data.


****Form Code***
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
myCol = new List<clsMyTreeNode>();
}
private IEnumerable<clsMyTreeNode> myCol;
private void button1_Click(object sender, EventArgs e)
{
//treeView1.BeginUpdate();
treeView1.Nodes.Clear();
for (int x = 1; x < 7; ++x)
{
clsMyTreeNode node = MakeNode(x);
((List<clsMyTreeNode>)myCol).Add(node);
}
//treeView1.ExpandAll();
//treeView1.EndUpdate();
MessageBox.Show("Nodes Made");

}
clsMyTreeNode MakeNode(int x)
{
clsMyTreeNode node = new clsMyTreeNode(x);
switch(x)//'Business Logic' Simulation
{
case 1://root node
break;
case 2:
node.ParentNodeId = 1;
break;
case 3:
node.ParentNodeId = 2;
break;
case 4://root node
break;
case 5:
node.ParentNodeId = 4;
break;
case 6:
node.ParentNodeId = 5;
break;
}
return node;
}

private void button2_Click(object sender, EventArgs e)
{
clsMyTreeNode root = new clsMyTreeNode(0);
this.treeView1.Nodes.Add((TreeNode)root);
PlaceNodes(ref root,(List<clsMyTreeNode>)myCol);
this.treeView1.Refresh();
}
void PlaceNodes(ref clsMyTreeNode root,List<clsMyTreeNode>
nodes)
{
try
{
bool lglPlaced = false;
for (; !lglPlaced; )
{
lglPlaced = true;
foreach (clsMyTreeNode c in nodes)
{
if (!c.Placed)
{

// Place node and set status

CallRecursive(((clsMyTreeNode)(root)), c);

// debug.Writeline("CallRecursive " &
c.Text & " " & "Status " & c.Status.ToString)
}
}
foreach (clsMyTreeNode c in nodes)//if nodes not
placed around we go again
{
if (!c.Placed)
{
lglPlaced = false;
}
}
}
}
catch (Exception ex)
{

MessageBox.Show("PlaceNodes " + ex.Message);
}
}
private void CallRecursive(clsMyTreeNode Location,
clsMyTreeNode placement) {
// Called by LoadTree
// Depth First traversal of tree, placing nodes and setting
statuses
//clsMyTreeNode n;
try {
if (placement.Placed) {
return;
}
foreach (clsMyTreeNode n in Location.Nodes) {
// PlaceIt(n, placement)
if (placement.Placed) {return;}
CallRecursive(n, placement);
//return;

}
//Debug.Assert((placement.NodeId > 0));
if (((Location.NodeId == placement.ParentNodeId)
&& !placement.Placed)) {
// Ready to place
//placement.Text = placement.Name;
Location.Nodes.Add(placement);
placement.Placed = true;

}
}
catch (ApplicationException ex) {
MessageBox.Show("CallRecursive " + ex.Message);
}
}



}
}
*****Derived Treenode Class *******
class clsMyTreeNode:TreeNode
{

public clsMyTreeNode()
{

}
public clsMyTreeNode(int iNodeId)
{

myNodeId = iNodeId;
myParentNodeId = 0;

}
private int myParentNodeId;
public int ParentNodeId
{
get { return myParentNodeId; }
set { myParentNodeId = value; }
}
private int myNodeId;

public int NodeId
{
get { return myNodeId; }
set { myNodeId = value; }
}
public override string ToString()
{
return "MyId is " + myNodeId + "Parent is " +
myParentNodeId;
}
private bool mlPlaced;

public bool Placed
{
get { return mlPlaced; }
set { mlPlaced = value;
this.Text = "MyId is " + myNodeId + "Parent is " +
myParentNodeId;
}
public override object Clone()
{
clsMyTreeNode clone = (clsMyTreeNode)base.Clone();
clone.NodeId = this.NodeId;
clone.ParentNodeId = this.ParentNodeId;
return clone;
}

}
 
J

John Rogers

bob said:
On Sun, 6 Jan 2008 22:53:23 -0600, "John Rogers"

Hi John,
Had a bit of play with your code.
I think the problem is more a conceptual one rather than a coding
one.
You are creating and placing the nodes on the tree then trying to
set
the node ids etc as you are writing to the database.

The following snippet shows what I was getting at;

Thanks for the help Bob, I really do appreciate you helping me like
this.
Not being a professional programmer or writing code for a living makes
it difficult at times when you want to write something. But thank God
for
the internet and for helpful people who help each other.

Thanks again.

John
 
B

bob

Thanks for the help Bob, I really do appreciate you helping me like
this.
Not being a professional programmer or writing code for a living makes
it difficult at times when you want to write something. But thank God
for
the internet and for helpful people who help each other.

Thanks again.

John
Your Welcome
regards
bob
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top