Click here to Skip to main content
14,740,685 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hello, I want to ask about the use of Tag-property of TreeView component in WinForms, C#.
I´ve used TreeView component previously with RAD-Studio / Delphi.
In there, the Tag was a Pointer.
In C# it is an Object.

What I am having is a record which contains various data.
e.g.
struct myRecord{
      double data1
      double data2
      double data3
      List<double> dataList
      }

Now I´ve added a rootNode and assigned the complete record to the Tag-property of that node.
After that I´ve added a child node and assigned data1 to the Tag-property of that child.
and so on.

The idea was if aNode is edited, simply cast the Tag-property of that node and the data is changed in the record too.
But that does not happen.
If I change the data after casting to a variable and assign it back to the Tag-property of that node in TreeView_after_labelEdit(), the information is only available in that Tag of that specific node.
My assumption was that it acts similar to the TreeView in Delphi and changes the data in the record as it points to it.
So I guess it does not point to the record-field, it creates a copy of the data assigned to the tag-property.


I now worked around that by assining the struct to the topNode.Tag only.
If a subnode is edited I search the corresponding topNode first, cast the Tag to the struct, change it and assign it back.
That seems to work but I want to understand why it is not working in the other way.

So can someone please explain how exactly that Tag works ?
Is there a way to have something like a pointer to the original struct and everytime the data is changed in the subnode it is changed in the "master" too ?

What I have tried:

// this code is just an example of how it tried to do this
// I did not test the code so it may contain some typos and syntaxerrors

struct myRecordStruct{
      double data1
      double data2
      double data3
      List<double> dataList
      } 

myRecordStruct myRecord;


private void AddRecordToTreview(TreeView myTreeView, myRecordType myRecord){
  var topNode = myTreeView.Nodes.Add("TopNode");
  topNode.Tag = myRecord;
  var subNode= topNode.Nodes.Add("DataField#1");
  subNode.Tag=myRecord.data1;
  subNode= topNode.Nodes.Add("DataField#2");
  subNode.Tag=myRecord.data2;
  subNode= topNode.Nodes.Add("DataField#3");
  subNode.Tag=myRecord.data3;  
  subNode= topNode.Nodes.Add("DataList");
  subNode.Tag=myRecord.dataList;
  foreach (var data in myRecord.dataList){
    var aNode=subNode.Nodes.Add(data.toString());
  }
}

private void Tview_AfterLabelEdit(object sender, NodeLabelEditEventArgs e){
  if (e.Node.Level==1 && e.Node.Index < 4){
     double d = double.Parse(e.Label);
     e.Node.Tag = d;     
     // if Tag is read the next time, it contains the changed doublevalue,
     // but myRecord.data has not changed
  }

  if (e.Node.Level==1 && e.Node.Index == 4){
     
     if (e.Node.Tag is List<double>()){
       double d = double.Parse(e.Label);
       var dataList = (List<double>())e.Node.Tag;
       dataList[e.Node.Index] = d;
       e.Node.Tag = dataList;  
       // the value in dataList[e.Node.Index] is changed.
       // the List is casted back from that Node.Tag it contains the changed list
       // but the list in myRecord.dataList is not changed.
     }   
  }


}
Posted
Updated 13-Jan-21 3:58am
v2

   
Have you tried changing the myRecordStruct from a struct to a class?
class myRecordStruct{
      double data1
      double data2
      double data3
      List<double> dataList
}

An issue with using struct is that they are passed-by-value, rather than pass-by-reference. So being passed around between properties and variables, the struct is going to be copied rather than returning the same reference. By using a class you can eliminate this problem since the same reference will be used. Here[^] is a good page demonstrating the effect.

The easiest way to demonstrate this effect is by the below code:
public struct Test {
  public int X;
}

Test test = new Test { X = 1 };
Console.WriteLine($"ReferenceEquals = {ReferenceEquals(test, test)}"); // False
   
v2
Comments
Thomas Kunkel 13-Jan-21 14:51pm
   
I´ve changed the struct to class, now it works. Makes life easier if the tag property can be used directly.

Thank you for this answer.
Quote:
... can someone please explain how exactly that Tag works ?

Well, i'd suggest to read the documentation:
Control.Tag Property (System.Windows.Forms) | Microsoft Docs[^]
TreeView Class (System.Windows.Forms) | Microsoft Docs[^]

As to your data structure and treview...
Usually, treview component is used to display hierarchical data, but i do not see any reference to previous/next object.
I agree with Chris Copeland[^] (solution#2) that you have to use custom class.
So, your struct
struct myRecordStruct{
      double data1
      double data2
      double data3
      List<double> dataList
      } 

can be "converted" to:
public class myRecord
{
      public double data1 {get; set;}
      public double data2 {get; set;}
      public double data3 {get; set;}
      public List<myRecord> dataList {get; set;}
	  public override string ToString()
	  {
	  	return $"myRecord: {{{data1.ToString("F2")} | {data2.ToString("F2")} | {data3.ToString("F2")}}}";
	  }
}


Then, you'll be able to create a list of myRecord with any level\depth of sub-myRecords. :)

So, final procedure/method to add nodes may look like:
private void LoadInitialData()
{
    foreach(myRecord mr in data)  //where data is List<myRecord>()
    {
        TreeNode root = null;
        //PopulateNode(ref root, mr);
        //PopulateNode2(ref root, mr);
        PopulateNode3(ref root, mr);
        oTV.Nodes.Add(root); //oTV is an object of TreeView
    }
    oTV.ExpandAll();
}

//each class member is in new node
private void PopulateNode(ref TreeNode root, myRecord record)
{
    root = new TreeNode("myRecord");
    TreeNode subnode1 = new TreeNode(record.data1.ToString());
    TreeNode subnode2 = new TreeNode(record.data2.ToString());
    TreeNode subnode3 = new TreeNode(record.data3.ToString());
    TreeNode subnode4 = new TreeNode("dataList");
    root.Nodes.AddRange(new TreeNode[]{subnode1, subnode2, subnode3, subnode4});
    if(record.dataList!=null)
    {
        foreach(myRecord mr in record.dataList)
        {
            TreeNode childnode = null;
            PopulateNode(ref childnode, mr);
            subnode4.Nodes.Add(childnode);
        }
    }
}

//two nodes per instance of myRecord: one for main data and second for list
private void PopulateNode2(ref TreeNode root, myRecord record)
{
    root = new TreeNode(record.ToString());
    TreeNode subnode1 = new TreeNode("dataList");
    root.Nodes.Add(subnode1);
    if(record.dataList!=null)
    {
        foreach(myRecord mr in record.dataList)
        {
            TreeNode childnode = null;
            PopulateNode2(ref childnode, mr);
            subnode1.Nodes.Add(childnode);
        }
    }
}

//one node per instance of myRecord
private void PopulateNode3(ref TreeNode root, myRecord record)
{
    root = new TreeNode(record.ToString());
    if(record.dataList!=null)
    {
        foreach(myRecord mr in record.dataList)
        {
            TreeNode childnode = null;
            PopulateNode3(ref childnode, mr);
            root.Nodes.Add(childnode);
        }
    }
}


The way of data presentation depends on you.

Note: truly doubt that you need to use Tag property. Get selected node, find myRecord on the list by using node/subnodes. Change object properties and update selected node.

TreeNode node = oTV.SelectedNode;
if(node!=null)
{
    //note: in below lines data type conversions are needed!
    myRecord mr = data.Where(x=>x.data1 = node.Nodes[0].Text && x.data2 = node.Nodes[1].Text && x.data3 = node.Nodes[2].Text).SingleorDefault();
    mr.data1 = 8888; //new value
    node.Nodes[0] = data1; //visualize change on treeview
   //et voila!
}


See also:
How to: Determine Which TreeView Node Was Clicked - Windows Forms .NET Framework | Microsoft Docs[^]
   

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900