This site is moving to The Zen Source Library
Please Update your bookmarks


TTreeNode.Data


Each TreeNode has a .Data property. This property is useful, in that it can be used to associate additional data with a TTreeNode. You could for instance associate each TreeNode's data property with a Form, and display the associated form when the TreeNode is clicked, or whatever...

TTreeNode.Data is an untyped pointer, which means it can point to anything. There are a few ways you can use this Data property. I'll demonstrate 3 of them.


The first method is very simple, but can only be used when you want to keep an additional integer value with each TTreeNode. Here is a short example

      {Make Node.Data contain the integer value 1234}
   Node.Data := pointer(1234);


      {Get the integer value}   
   iValue := integer(Node.Data);


All I'm doing here is treating the pointer as an integer value. It is extremely important that you don't try use the TTreeNode.Data as a real pointer in this case. You will undoubtedly crash your application...
In the example above the .Data property contains the integer value 1234. If you try use Node.Data as a pointer...... who knows? After all you have no idea what is at memory address 1234 ($4d2). So just don't do it!


The second method uses classes, this is the method that most people will probably use. Its easy and flexible and there are no pointers to worry about (3rd method).

The reason this method works so well is that Delphi conceals the fact that a class instance is actually a pointer.


var
   MyClass : TMyClass
begin
   MyClass := TMyClass.Create;

   MyClass.Free;
end;

The MyClass variable is actually a pointer! But thanks to Delphi you never need to worry about that. So how does this help? Well look at this example


   Node.Data := TMyClass.Create;
   TMyClass(Node.Data).Free;

The first line creates an instance of TMyClass and assigns it to the TTreeNode.Data property. The second line type-casts the pointer and frees it. The reason you need to type-cast is that the TTreeNode.Data property is an untyped pointer. Delphi has no idea that you want it to store a TMyClass instance.

The following source is modified from example 1. Here each node has an associated TNodeData class. This class stores that time that the node was created as a string. This is a pretty useless example, but demonstrates how this method works.

Control Caption Name
TButton Add but_Add
TButton Remove but_Remove
TTreeView   tv_eg1
TEdit   ed_Time


type
  TNodeData = class
     sText : string;
  end;



   procedure TForm1.but_AddClick(Sender: TObject);
   var
      sText : string;
   begin
         {If nothing is selected}
      if(  tv_eg1.Selected = nil  ) then
      begin
            {Does a root node already exist?}
         if(  tv_eg1.Items.Count = 0  ) then
         begin
               {Add the root node}
            with tv_eg1.Items.AddFirst(  nil,  'Root'  ) do
            begin
               Selected := true;

                  {Create the data class}
               Data := TNodeData.Create;
                  {Set the nodes date time}
               TNodeData(Data).sText := FormatDateTime(  'hh:nn:ss',  now  );
            end;
         end
         else begin
               {There is a root, so user must first select a node}
            MessageBeep(  -1  );
            ShowMessage(  'Select a parent node'  );
            Exit;
         end;
      end
      else begin
            {Get a name for the new node}
         InputQuery(  'New Node',  'Caption ?',  sText  );

            {Add the node as a child of the selected node}
         with tv_eg1.Items.AddChild(  tv_eg1.Selected,  sText  ) do
         begin
               {Create the data class}
            Data := TNodeData.Create;
               {Set the nodes date time}
            TNodeData(Data).sText := FormatDateTime(  'hh:nn:ss',  now  );

            MakeVisible;
         end;
      end;
   end;


procedure TForm1.but_RemoveClick(Sender: TObject);
begin
      {Make sure somthing is selected, before trying to
        delete it}
   if(  tv_eg1.Selected = nil  ) then
   begin
      MessageBeep(  -1  );
      ShowMessage(  'Nothing selected'  );
      Exit;
   end;

      {don't allow user to delete the root node}
   if(  tv_eg1.Selected.Level = 0  ) then
   begin
      MessageBeep(  -1  );
      ShowMessage(  'Cant delete the root node'  );
      Exit;
   end;

      {Free the class}
   if(  tv_eg1.Selected.Data <> nil  ) then
      TNodeData(tv_eg1.Selected.Data).Free;

      {Delete the node}
   tv_eg1.Selected.Delete;
end;


There are a few important lines in the source above


  TNodeData = class
     sText : string;
  end;
Define the class that is going to be associated with each TTreeNode.


   Data := TNodeData.Create;   
Create the TNodeData class and assign it to the Data property


   TNodeData(Data).sText := FormatDateTime(  'hh:nn:ss',  now  ); 
Set the TNodeData's creation time.


   if(  tv_eg1.Selected.Data <> nil  ) then
      TNodeData(tv_eg1.Selected.Data).Free;
Free the memory used by the TNodeData class when it is no longer needed



   procedure FreeAll_callback(  Node : TTreeNode;  pData : pointer  );
   begin
      if(  Node.Data <> nil  ) then
      begin
            {Free the memory used by the node}
         TNodeData(Node.Data).Free;
         Node.Data := nil;
      end;
   end;


   procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
   begin
         {If there are no nodes, exit}
      if(  tv_eg1.Items.Count <= 0  ) then
         Exit;

         {Call FreeAll_callback once for every node in the tree}
      EnumNodes(  tv_eg1.Items[ 0 ],
                  FreeAll_callback,
                  nil,
                  false
                );
   end;

It is important to remember to free all nodes when the application is closed. In the code above the EnumNodes procedure is used to enumerate all the nodes. See Pre-Ordal and Post-Ordal recursive tree traversals for info on this procedure.



Finally method 3. This method is very similar to the above one, however instead of using a class I'll use a record. I'll just show the difference between this and method 2...


     {The record pointed to by each Node's .data property}
  prNodeData = ^rNodeData;
  rNodeData = record
     sText : string;
  end;
Instead of a class declare a record. Also declare a pointer to the record.


      {Allocate memory for the record}
   Data := New(  prNodeData  );
      {Set the nodes date time}
   prNodeData(Data)^.sText := FormatDateTime(  'hh:nn:ss',  now  );
First allocate memory for the record. Then set the sText field. Remember to dereferance ( ^ ) the pointer.


      {Free the memory used by the record}
   if(  tv_eg1.Selected.Data <> nil  ) then
      Dispose(  prNodeData(tv_eg1.Selected.Data)  );
Free the memory used by the record.



   procedure FreeAll_callback(  Node : TTreeNode;  pData : pointer  );
   begin
      if(  Node.Data <> nil  ) then
      begin
            {Free the memory used by the node}
         Dispose(  prNodeData(Node.Data)  );
         Node.Data := nil;
      end;
   end;


   procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
   begin
         {If there are no nodes, exit}
      if(  tv_eg1.Items.Count <= 0  ) then
         Exit;

         {Call FreeAll_callback once for every node in the tree}
      EnumNodes(  tv_eg1.Items[ 0 ],
                  FreeAll_callback,
                  nil,
                  false
                );
   end;

Again remember to free all memory used when the application is closed. (See Pre-Ordal and Post-Ordal recursive tree traversals)






All information on these www pages is copyright (©) 1997 Andre .v.d. Merwe And may not be copied or mirrored without my permission.