Unique identifier in every treenode?

R

Rob Stevens

Is there some sort of unique identifier in every treenode
that is consistent? I was looking at the handle of every
treenode, but it appears that the handle changes everytime
the tree is built.

I have seen in some databases where they store the treenodes
they are using some serial number of the node. I don't have
a clue what that is, but can someone tell me if there is something
that is unique to every node.


Thanks

Rob
 
N

Nicholas Paldino [.NET/C# MVP]

Rob,

Well, within the confines of the program itself, then the reference
itself is unique, and you should be able to compare object references.

If you are talking about something you can persist, then you need to
look at other options. You need to associate that identifier with the node.

If the full path consisting of the label names represent a unique
identifier, then you can use the FullPath property on the TreeNode instance
to get the path from the root to that node. If not, then you will have to
assign something to the Tag property, creating the identifier and restoring
it when you recreate the tree from whatever underlying storage you are
using.
 
R

Rob Stevens

Thanks Nicholas,

I ask this question because I have been looking at a lot of example code
within the
last week. And it's really confusing the way the tree is being stored to
whatever
storage is being used, and restored with the same exact layout still remains
a
mystery to me. I see no where in anyone's code where they are storing node
names,
levels, nothing. Here is an example I found last night, but even after
debugging and
looking at the values, I still can't see anything at all that shows me the
exact layout of
a tree.

void SaveTreeView(TreeView tree, string filename)
{
ArrayList al = new ArrayList();
foreach (TreeNode tn in tree.Nodes)
{
al.Add(node);
//listBox.Items.Add(node.Text + "," + depth);
}

Stream file = File.Open(filename, FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();
try
{
bf.Serialize(file, al);
}
catch (System.Runtime.Serialization.SerializationException e)
{
MessageBox.Show("Serialization failed : {0}", e.Message);
}
file.Close();
}

void LoadTreeView(TreeView tree, string filename)
{
if (File.Exists(filename))
{
Stream file = File.Open(filename, FileMode.Open);
BinaryFormatter bf = new BinaryFormatter();
object obj = null;
try
{
obj = bf.Deserialize(file);
}
catch (System.Runtime.Serialization.SerializationException
e)
{
MessageBox.Show("De-Serialization failed : {0}",
e.Message);
}
file.Close();

ArrayList nodeList = obj as ArrayList;

// load Root-Nodes
foreach (TreeNode node in nodeList)
{
tree.Nodes.Add(node);
//listBox.Items.Add(node.Text);
}
}
}

I think until I figure out a lot of these underlying things in the code, I
will never understand what
I am looking at. I can't see individual properties being saved, its just a
mystery.

Rob
 
P

Peter Duniho

Is there some sort of unique identifier in every treenode
that is consistent? I was looking at the handle of every
treenode, but it appears that the handle changes everytime
the tree is built.

I have seen in some databases where they store the treenodes
they are using some serial number of the node. I don't have
a clue what that is, but can someone tell me if there is something
that is unique to every node.

Not by default, no. It would be up to the client code (i.e. what you
write) to impose any sort of unique identifier and store it with the
TreeNode (you can either use the TreeNode.Tag property to store something,
or subclass the TreeNode class and put whatever you want in that derived
class).

You may or may not actually need to store a unique identifier. Even in a
regular database you can use the database itself to specify relationships
between records in the database representing nodes, something like XML
provides a natural "containment" semantic that would allow the tree
structure to be represented without explicit references between nodes, and
even a plain text file can be used to reliably store a tree structure as
long as you have _some_ way of describing the structure (for example,
instead of specifying explicit relationships between nodes, simply storing
a given node's depth within the tree along with making sure the nodes are
stored and recovered in a specific order is sufficient...this was even a
recent example in a previous thread in this newsgroup).

Pete
 
P

Peter Duniho

I ask this question because I have been looking at a lot of example code
within the
last week. And it's really confusing the way the tree is being stored
to whatever
storage is being used, and restored with the same exact layout still
remains a
mystery to me. I see no where in anyone's code where they are storing
node names,
levels, nothing. Here is an example I found last night, but even after
debugging and
looking at the values, I still can't see anything at all that shows me
the
exact layout of a tree.

That's because all the important stuff is hidden in the serialization code
for TreeNode.

I'm not really all that familiar with the specifics myself, but the code
you posted simply enumerates the root nodes, serializing each one. Then
in the "Load" method, it deserializes the stored data into an array of
TreeNodes, representing the root nodes, and adds each root node to the
tree.

Since TreeNode implements ISerializable and assuming the code you posted
works, it must include logic for serializing the entire structure under a
given TreeNode. To see specifically how it works, you would have to look
at the implementation for TreeNode itself, and/or the output from the
serializer (but you'd probably want to use a different formatter than
BinaryFormatter, so that you get something that's reasonable
human-readable). But there's nothing magical about it...it just means
that the code you posted is relying on an existing implementation that
already handles the job of relating the nodes somehow.

You could either take advantage of that, or write your own
implementation. Depending on your needs with respect to persisting the
data, either approach could be warranted.

Pete
 
R

Rob Stevens

Peter Duniho said:
You could either take advantage of that, or write your own
implementation. Depending on your needs with respect to persisting the
data, either approach could be warranted.

I found this one today that I had seen before but did not really pay any
attention
to it.

private void button1_Click(object sender, EventArgs e)
{
foreach (TreeNode node in tvMain.Nodes)
{
PrintNodeInformation(node);
}
}
public void PrintNodeInformation(TreeNode node)
{
listBox.Items.Add(node.Text + "," + node.Level);
//Console.WriteLine("Text: " + node.Text);
//Console.WriteLine("Path: " + node.FullPath);
//Console.WriteLine("Tag: " + node.Tag);

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

This code seems really simple and straight to the point, it does pick up
every node in the
tree, this is a good start for what I want to do. I guess I will have to do
as suggested,
save the tree in order, and restore it in the same order. I don't know how
I will be
able to use the tag information for this, I am thinking about storing data
attached to
each node when I attach to a db. And as far as I understand the tag
property is normally
used for storing your structs, that is as far as I have read from many other
posts.

Rob
 
P

Peter Duniho

This code seems really simple and straight to the point, it does pick up
every node in the tree, this is a good start for what I want to do.

The important part about that code is the recursion, which will be a
common element in any implementation that enumerates the tree completely..
I guess I will have to do as suggested,
save the tree in order, and restore it in the same order.

That is one way, yes.
I don't know how I will be
able to use the tag information for this,

You wouldn't. If you are taking advantage of the Tag property, it won't
matter what order the data is stored (though you'll probably still wind up
with a reliably reproducible order...it would be silly to intentionally
randomize the order of the data as stored :) ).

As an example, you could create a new Guid for each TreeNode and assign it
to the Tag property. Then when saving, you'd include for each node the
Guid of the _parent_ of that node (stored in the Tag property of the
parent, of course). Then reconstructing the tree is as simple as looking
at the parent Guid stored with each node's data (not that node's own Guid,
in the Tag property), and using that to find the parent for the node.
I am thinking about storing data attached to each node when I attach to
a db.

I'm no database expert. But my understanding is that you can create a
database where one of the columns is a unique identifier, and optionally
where the unique identifier is automatically generated when you add a
record to the database.

In this case, instead of creating a new unique identifier in the context
of your TreeView with each TreeNode, you could wait until you've writtena
given node to the database to find out what the unique identifier in the
database is for that node, and then store that identifier for each child
of the node as part of the child node's database record (root nodes would,
of course, have to have some sort of null value).

I don't know how the specific database code would look, but in pseudocode
the basic idea would be something like this:

void AddNodesToDatabase(TreeNodeCollection nodes, int DBParentID)
{
foreach (TreeNode node in nodes)
{
int DBNodeID = AddNodeToDatabase(node, DBParentID);

AddNodesToDatabase(node.Nodes, DBNodeID);
}
}

The assumption being that the method "AddNodeToDatabase" will save the
node data along with the parents database ID as a single record in the
database, and then return the database's unique identifier for that
newly-added node. The ID can then be used to add the children nodes for
that node.

In this way, the tree structure is represented in the database. Of
course, you still need to reconstitute the tree from that information
later. That will be a matter of tracking, at least temporarily, the
database ID for each node as they are read out. For example (again,
pseudo-code):

Dictionary<int, TreeNode> NodeLookup = new Dictionary<int, TreeNode>();

void GetNodesFromDatabase(TreeNodeCollection nodes)
{
// Obviously we need to enumerate each record in the database
foreach (TreeNodeRecord record in Database)
{
// These three lines represent the basic functionality you'll
need to figure out
// with respect to the database. From the database record,
you need to be able
// to construct a node, retrieve the ID for that node, as well
as the ID for the
// parent node.
TreeNode node = TreeNodeFromRecord(record);
int DBParentID = ParentIDFromRecord(record);
int DBNodeID = NodeIDFromRecord(record);

if (DBParentID != DBNullID)
{
// If the parent node is in your dictionary, add the new
// node as a child of the parent node
NodeLookup[DBParentID].Nodes.Add(node);
}
else
{
// Otherwise, add it as a root note
nodes.Add(node);
}

// Store the new node in the lookup dictionary, so that its
children
// can be added
NodeLookup.Add(DBNodeID, node);
}
}

The above code assumes that parents are always written before their
children. It's not a requirement that is strictly necessary, but it makes
the code simpler and more efficient and is easy enough to implement (it
comes naturally from the previous code example).
And as far as I understand the tag property is normally
used for storing your structs, that is as far as I have read from many
other posts.

The Tag property can be used for attaching any user-defined data you like
to the TreeNode. That could be run-time specific data, or some kind of
unique identifier for the node, or whatever. It's up to the application
to decide how to use the Tag property.

The previous thread I mentioned, I wrote a simple demo of saving and
restoring a TreeView. The code is all in-memory...it doesn't save
anything to persistent storage. But the basic ideas are going to be the
same. The post in which I included that sample is here:
http://groups.google.com/group/microsoft.public.dotnet.languages.csharp/msg/018a8db8e8a6033e

You may find that post, or even the entire thread, useful. Or not. You
might as well look to see though. :)

Pete
 
R

Rob Stevens

Thanks Pete,

You have given me a lot of great ideas, now I have to put them to use.

Thanks again.

Rob
 

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