November 1998

Mastering TTreeView component, part 1

by Bill Whitney

You can download our sample files as part of the file nov98.zip. Visit www.zdjournals.com/cpb and click on the Source Code hyperlink.

This article is the first in a two-part series covering the TTreeView component. If you aren't familiar with what this component can do for you, you can see its behavior by experimenting with the left pane in the Windows Explorer application. In this pane, the TTreeView represents system elements, such as disk drives, the Recycle Bin, the Printers folder, and the Control Panel.

While TTreeView is a powerful tool for visualizing hierarchical relationships, it's somewhat difficult to take advantage of as a data structure. We'll try to take the pain out of implementing TTreeView by looking at its basic elements and discussing their relationships. Then, we'll examine the methods and attributes used to build and maneuver within a TTreeView.

Defining TTreeView

TTreeView is a container, which means you can use it to store and manipulate other objects. The primary difference between TTreeView and most other containers is its hierarchical arrangement. Figure A shows a partially expanded TTreeView containing all the leagues, divisions, teams, and players in Major League Baseball. Throughout this article, we'll refer back to Figure A.

Figure A: Our sample TTreeView displays information about Major League Baseball.
[ Figure A ]

Basic relationships

TTreeView wouldn't exist without TTreeNodes and TTreeNode. TTreeView maintains a visual representation of the individual TTreeNode objects and handles events affecting that representation. The Items property of TTreeView is of type TTreeNodes, and it maintains the list of individual TTreeNode objects contained in a TTreeView. You'll work directly with TTreeView to handle events, with TTreeNodes to insert and delete nodes, and with TTreeNode to delete nodes and determine relationships between individual nodes.

The heart of the matter: TTreeNode

If you've worked with data structures such as linked lists or balanced trees in the past, then TTreeNode shouldn't be a mystery. It serves as the basic element (node) in a TTreeView container. In Figure A, each league, division, team, position, and player is represented by its own individual TTreeNode. TTreeNode has two very useful properties that we'll cover in more detail as we go. The first is a Text attribute that identifies the node in a TTreeView (its name). The second is a void* that can point to any object you choose (a structure, an object, or even another character string).

TTreeNode relationships

Since TTreeView is hierarchical, an individual TTreeNode can have more than one type of relationship to other nodes. It can be a parent, a child, or a sibling (or, in many cases, all three). The parent/child relationship is best described by a look back at Figure A. East, Central, and West are children of the American League node--children are indented immediately below the parent. Since Boston is a child of East, East is the parent to Boston. Notice we say "the" parent--a node can have only one parent. When one or more children have the same parent, they are siblings.

Root nodes and node levels

Every TTreeView implementation will contain at least one root node. The Windows 98 Explorer, for example, shows Desktop as the solitary root node. Figure A contains two root nodes: American League and National League. The number of root nodes is entirely up to you, and should reflect the needs of your application. A node's level reflects its distance from the root node (where the root level is 0). For instance, you can find the names of the members of Boston's pitching staff on level 4.

Selected nodes

The selected node is the node in relation to which all adding and deleting will take place (it really makes sense only if you're looking at TTreeView). If you allow users to insert and delete based on the node selections they make (as you'll see later , you can use the TTreeView Selected property), then your job is a little easier. If you're building a TTreeView programmatically, you either must know where you are at all times or how to select the correct node when necessary. In the programmatic sense, selected node refers to the existing node you pass as an argument to insert and delete operations. Let's take a look at the methods you use to insert and delete nodes.

Programming TTreeView

Regardless of the eventual breadth and depth of your tree, you must start by inserting one or more root nodes. To do so, you can use one of two methods: Add or AddFirst. Both methods take a pointer to an existing TTreeNode and an AnsiString as arguments, and both return a pointer to the newly created TTreeNode. Here's a look at Add's signature:
TTreeNode* __fastcall Add(TTreeNode* Node, 
  const System::AnsiString S);
Any time the Node parameter is NULL, you add the node to level 0 (a root node). The AnsiString argument is simply the text that will appear in the TTreeView representation of the new node (such as Boston). Add appends the new node to the end of the current group of siblings, whereas AddFirst inserts it at the beginning. Here's some code to insert the two root nodes shown in Figure A:
TreeView1->Items->Add(NULL, "American League");
TreeView1->Items->Add(NULL, "National League");
All the methods that add nodes to the TTreeView take the same arguments as Add--an existing TTreeNode and an AnsiString. Later, we'll cover some exceptions (such as assigning something to the new TTreeNode object's void*).

Add and AddFirst operate on the current group of siblings--they take a TTreeNode pointer to any existing node, then adding a sibling to it. For example, you can add a new pitcher named Tom Gordon to the bottom of Boston's pitching roster by calling Add and passing in a pointer to an existing Boston pitcher:

TTreeNode* tomGordonNode = 
  TreeView1->Items->Add(pedroMartinezNode, 
                        "Tom Gordon");
The Insert method gives you a bit more precision when adding new nodes. Insert takes a pointer to an existing node and inserts a new node just prior to it. For example, the following code will place Tom Gordon between Martinez and Wakefield on the Boston pitching roster:
TTreeNode* tomGordonNode = TreeView1->Items->
  Insert(timWakefieldNode, "Tom Gordon");
Like Add and AddFirst, Insert operates on the current sibling group. Note that calling Insert with a NULL Node parameter will append the new node to the end of the list of root nodes--essentially the equivalent of calling Add with a NULL Node parameter.

Adding to the family

AddChild and AddChildFirst function like Add and AddFirst, but they operate on the children of an existing node rather than the siblings of an existing node. Here's a quick example that appends a new infielder to the Boston team:
TreeView1->Items->
  AddChild(bostonInfieldersNode, "Mo Vaughn");
(AddChildFirst would have placed Mo at the top). Now, let's see how to delete a TTreeNode. When we added Tom Gordon to the list of pitchers, we got back a pointer to the new node and stored it in tomGordonNode. To get rid of him, we can simply make the following call:
tomGordonNode->Delete();
Note: Be careful with Delete
Calling Delete on a node that has children associated with it will delete all the children, as well--this includes all nodes subsequent to the node being deleted (children, grandchildren, and so on).

You can just as easily remove all the children of any parent node without removing the parent itself, by calling the DeleteChildren method. The following code wipes out the entire Boston pitching staff, but leaves the Pitchers node:

bostonPitchers->DeleteChildren();

Adding and deleting nodes that point to something

When we talked earlier about TTreeNode, we mentioned that it could point to the object of your choice. You can set this object pointer when you create the new TTreeNode by using an alternate series of add and insert methods that take a pointer to the object as a void* parameter. Table A shows the methods you've already seen and the corresponding methods that accept an object.

Table A: Add and Insert methods that accept objects
Method Method With Object
Add AddObject
AddFirst AddObjectFirst
AddChild AddChildObject
AddChildFirst AddChildObjectFirst
Insert InsertObject

The signature changes slightly to include the void*:

TTreeNode* __fastcall AddObject(TTreeNode* Node, 
  const System::AnsiString S, void * Ptr);
The add and insert methods invoke their Object versions behind the scenes. They simply provide NULL as the value for the void* parameter, indicating that no object is present. Besides supplying a pointer through the add and insert methods, you can also set and access the pointer through the node's Data property.

Suppose we have a Stats object containing a pitcher's current statistics. The following code creates a new Stats object and attaches it to a new TTreeNode:

Stats *newStats = new Stats();
tomGordonNode = TreeView1->Items->
  AddObject(pedroMartinezNode, "Tom Gordon", 
  newStats);
You can also attach this object to a pitcher's TTreeNode later by setting the Data property:
tomGordonNode->Data = (void*) newStats;
And the following code gets the object back:
Stats* tomsStats = (Stats*) tomGordonNode->Data;
Being able to associate any object with a TTreeNode adds exciting possibilities to your code. However, you must treat the Data property with caution. For example, the TTreeNode Delete method doesn't delete the memory associated with the void*--it's up to your code to take care of that. Here's a simple way to do it:
Stats* tomsStats = (Stats*) tomGordonNode->Data;
delete tomsStats;
tomGordonNode->Delete();
If you're going to delete the object pointed to by Data but leave the TTreeNode, it's a good idea to set the Data pointer to NULL in case it's checked by subsequent operations in your program. You can do so as follows:
Stats* tomsStats = (Stats*) tomGordonNode->Data;
delete tomsStats;
tomGordonNode->Data = (void*) NULL;
You now have the tools to build a TTreeView. Next, we'll discuss some methods you can use to navigate a TTreeView from within your code.

Navigating TTreeView

You can use numerous methods to locate specific nodes in a TTreeView at runtime--some of them are positional, and others are absolute. Let's examine how both methods work and look at examples of each, starting with positional.

 
Note: Watch out for lowercase
Some of the methods in this section (getFirstChild, getNextSibling, and getPrevSibling) don't start with a capital letter, as do the other methods. This is a bug in C++Builder that has been reported.

Positional methods

You can find the first root node of any tree using the GetFirstNode method, like this:
TTreeNode* firstRoot = TreeView1->Items->GetFirstNode();
Since all root nodes are siblings of each other (part of the same sibling group), you can then determine the other root nodes using the getNextSibling method. This code adds the names of a tree's root nodes to a ListBox:
????
TTreeNode* rootNode = 
  TreeView1->Items->GetFirstNode();
while ( rootNode )
{
  ListBox1->Items->Add(rootNode->Text);
  rootNode = rootNode->getNextSibling();
}
????
If we executed this code on the TTreeView in Figure A, ListBox1 would contain two items: American League and National League. The getNextSibling method's getPrevSibling counterpart returns the previous sibling in a sibling group. If there are no previous or next siblings, the method will return a NULL--your code should check for this before trying to perform some operation on the resulting TTreeNode pointer.

In addition to the sibling methods, four methods return information about the children of a TTreeNode: getFirstChild, GetNextChild, GetPrevChild, and GetLastChild. Here are two short examples--one that walks the teams in the American League East division from top to bottom, and one that goes from bottom to top (we've set alEast to the correct parent node):

TTreeNode* team = alEast->getFirstChild();
while ( team )
{
  ListBox1->Items->Add(team->Text);
  team = alEast->GetNextChild(team);
}

TTreeNode* team = alEast->GetLastChild();
while ( team )
{
  ListBox1->Items->Add(team->Text);
  team = alEast->GetPrevChild(team);
}
Here's another way to walk the children of alEast:
if ( alEast->HasChildren )
{
  ListBox1->Items->
    Add("Here are the teams in the AL East:");
  for ( int team = 0; team < alEast->Count; 
    team++ )
    ListBox1->Items->
       Add("  " + alEast->Item[team]->Text);
}
else
  ListBox1->Items->
    Add("There are no teams in the AL East!");
This example exposes some additional methods and properties we haven't covered yet, but their purpose will be obvious. Again, we're dumping information into ListBox1.

Notice that you can address an individual child by its item index. For any TTreeNode with children, each child has a unique index in its sibling group and can be addressed by the parent via parentNode->Item[x]. A child can also tell you its position, as follows:

int x = childNode->Index;
You can find out any TTreeNode's parent using the Parent property (this will return a NULL for root nodes):
TTreeNode* parent = childNode->Parent;
Interestingly enough, C++Builder also provides a method to find out if one node is the parent of another. Here, node A checks to see if node B is its parent:
bool isParent = nodeA->HasAsParent(nodeB);
You can also test in the opposite direction, checking to see whether node A is a child of node B. You do this using the IndexOf method, which returns the position of the child node among its siblings (starting with zero):
int pos = nodeB->IndexOf(nodeA);
The method returns -1 if the child node isn't found. Finally, you can ask any TTreeNode its level by using the Level property:
int nodeLvl = anyNode->Level;

Absolute positioning methods

You've already seen an absolute positioning method that gives you direct access to a parent's children via the Item property. The other absolute method--AbsoluteIndex--is a unique index assigned to every TTreeNode in a TTreeView. AbsoluteIndex values depend on a node's position within the TTreeView, and will change with the addition and deletion of nodes. Values are assigned starting with 0 for the first root node. Note that children of a current node will contain subsequent AbsoluteIndex values, not the siblings. Table B shows the AbsoluteIndex values for some of the members
of Figure A.

Table B: AbsoluteIndex values from Figure A
TTreeNode Index
American League 0
East 1
Boston 2
Pitchers 3
Pedro Martinez 4
Tim Wakefield 5

If the (etc) node didn't exist, then Catchers would be 6. We don't recommend relying heavily on AbsoluteIndex in your code, because the values change regularly.

Building a TTreeView

Now, let's write some code that reproduces the TTreeView shown in Figure A and illustrates many of the concepts we've discussed. The sample source code appears in Listing A (Unit1.cpp), and the header file is in Listing B (Unit1.h).

The application consists of one form containing a TTreeView, a list box to display method output, and several buttons that invoke the methods demonstrated in the article. Because the application demonstrates how to build a TTreeView, you should click the buttons in a logical order: first the left column from top to bottom, then the right column from top to bottom. Figure B shows the application's main window.

Figure B: Our sample application illustrates many of the concepts we've discussed.
[ Figure B ]

Listing A: Unit1.cpp 

//--------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//--------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//--------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
  tomGordonNodeAdd = NULL;
  tomGordonNodeInsert = NULL;
}
//--------------------------------------------------

void __fastcall TForm1::CreateFigAClick(TObject *Sender)
{

  // Add the root nodes
  al = TreeView1->Items->Add(NULL, "American League");
  nl = TreeView1->Items->Add(NULL, "National League");

  // Add the divisions in both leagues
  alEast    = TreeView1->Items->AddChild(al, "East");
  alCentral = TreeView1->Items->AddChild(al, "Central");
  alWest    = TreeView1->Items->AddChild(al, "West");
  nlEast    = TreeView1->Items->AddChild(nl, "East");
  nlCentral = TreeView1->Items->AddChild(nl, "Central");
  nlWest    = TreeView1->Items->AddChild(nl, "West");

  // Add teams only to American League East to save space
  bosNode = TreeView1->Items->AddChild(alEast, "Boston");
  TreeView1->Items->AddChild(alEast, "New York");
  TreeView1->Items->AddChild(alEast, "Toronto");
  TreeView1->Items->AddChild(alEast, "Baltimore");
  TreeView1->Items->AddChild(alEast, "Tampa Bay");

  // Insert player categories under each team
  for ( int i = 0; i < alEast->Count; i++ )
  {
    TreeView1->Items->AddChild(alEast->Item[i], "Pitchers");
    TreeView1->Items->AddChild(alEast->Item[i], "Catchers");
    TreeView1->Items->AddChild(alEast->Item[i], "Infielders");
    TreeView1->Items->AddChild(alEast->Item[i], "Outfielders");
  }

  // Add the Boston pitchers
  pitNode = bosNode->getFirstChild();
  pedroMartinezNode = TreeView1->Items->
    AddChild(pitNode, "Pedro Martinez");
  timWakefieldNode = TreeView1->Items->
    AddChild(pitNode, "Tim Wakefield");
  TreeView1->Items->
    AddChild(pitNode, "(etc)");

  // Find the infield for use later
  // pitchers
  bostonInfieldersNode = bosNode->getFirstChild();
  // Catchers
  bostonInfieldersNode = bostonInfieldersNode->getNextSibling();
  // Infielders!
  bostonInfieldersNode = bostonInfieldersNode->getNextSibling();

  // Show the entire tree from the top
  TreeView1->FullExpand();
  TreeView1->TopItem = TreeView1->Items->GetFirstNode();
}
//--------------------------------------------------
void __fastcall TForm1::QuitClick(TObject *Sender)
{
  Close();
}
//--------------------------------------------------

void __fastcall TForm1::AddTomGordonClick(TObject *Sender)
{
  // Add a Tom Gordon and put Stats in the Data property
  Stats* lclStats = new Stats(1);
  tomGordonNodeAdd = TreeView1->
    Items->Add(pedroMartinezNode, "Tom Gordon");
  tomGordonNodeAdd->Data = (void *) lclStats;
  ListBox1->Items->Add("Inserted Tom with a new stats object");
  ListBox1->Items->Add("containing the number 1");
}
//--------------------------------------------------

void __fastcall TForm1::InsertTomGordonClick(TObject *Sender)
{
  // Insert a Tom Gordon with a Stats object
  Stats* lclStats = new Stats(2);
  tomGordonNodeInsert = TreeView1->
    Items->InsertObject(timWakefieldNode,"Tom Gordon", lclStats);
  ListBox1->Items->Add("Inserted Tom with a new stats object");
  ListBox1->Items->Add("containing the number 2");
}
//--------------------------------------------------

void __fastcall TForm1::DeleteGordonClick(TObject *Sender)
{
  if ( tomGordonNodeAdd != NULL )
  {
    // Retrieve Stats object from Data property
    Stats* statsInstance = (Stats*) tomGordonNodeAdd->Data;
    String number = statsInstance->getStats();
    ListBox1->Items->Add("Stats contained a " + number);
    // Delete Stats and then the Tom Gordon node.
    delete statsInstance;
    tomGordonNodeAdd->Data = (void*) NULL;
    tomGordonNodeAdd->Delete();
    ListBox1->Items->Add("Deleted the added Tom Gordon node.")
    tomGordonNodeAdd = NULL;
  }

  if ( tomGordonNodeInsert != NULL )
  {
    // Retrieve Stats object from Data property
    Stats* statsInstance = (Stats*) tomGordonNodeInsert->Data;
    String number = statsInstance->getStats();
    ListBox1->Items->Add("Stats contained a " + number);
    // Delete Stats and then the Tom Gordon node.
    delete statsInstance;
    tomGordonNodeInsert->Data = (void*) NULL;
    tomGordonNodeInsert->Delete();
    ListBox1->Items->
      Add("Deleted the inserted Tom Gordon node.");
    tomGordonNodeInsert = NULL;
  }
}
//--------------------------------------------------

void __fastcall TForm1::AddVaughnClick(TObject *Sender)
{
  // Add Vaughn as child to Boston's infielders. Test handle 
  // object with Vaughn. Adapted from calander example.
  nodeIdHandle* moData = new nodeIdHandle;

  nodeIdHandle* moDataOut;
  nodeIdHandle* moDataIn;

  moData->nodeType = ev_player;
  moData->obj = NULL;
  moVaughn = TreeView1->Items->AddChild(bostonInfieldersNode, "Mo Vaughn");
  Stats* moStats = new Stats(3);
  moVaughn->Data = (void*) moData;

  moDataOut = (nodeIdHandle*) moVaughn->Data;
  moDataOut->obj = (void*) moStats;
  moStats = NULL;
  ListBox1->Items->Add("Added Mo with a Stats value of 3.");

  moDataIn = (nodeIdHandle*) moVaughn->Data;
  if ( moDataIn->nodeType == ev_player )
    ListBox1->Items->Add("Retrieved Mo -- he's a player");
  moStats = (Stats*) moDataIn->obj;
  String moVal = moStats->getStats();
  ListBox1->Items->Add("Mo's stats value is a " + moVal);
}
//--------------------------------------------------

void __fastcall TForm1::DeleteChildrenClick(TObject *Sender)
{
  // Delete all Boston pitchers
  pitNode->DeleteChildren();
}
//--------------------------------------------------

void __fastcall TForm1::GetFirstRootClick(TObject *Sender)
{
  // Show the name of the first root node
  ListBox1->Items->Add("First root node is " +
    TreeView1->Items->GetFirstNode()->Text);
}
//--------------------------------------------------

void __fastcall TForm1::ShowLevelClick(TObject *Sender)
{
  // Show the level of the selected node
  TTreeNode *fc = TreeView1->Selected;
  if ( fc != NULL )
    ListBox1->Items->Add("Node " + fc->Text + " is at level " +
      fc->Level);
  else
    ListBox1->Items->Add("Nothing selected!");
}
//--------------------------------------------------

void __fastcall TForm1::WalkRootsClick(TObject *Sender)
{
  // Get the first tree node (root)
  TTreeNode* rootNode = TreeView1->Items->GetFirstNode();
  while ( rootNode )
  {
    ListBox1->Items->Add(rootNode->Text);
    // Continue as long as there are siblings
    rootNode = rootNode->getNextSibling();
  }
}
//--------------------------------------------------

void __fastcall TForm1::WalkChildrenDownClick(TObject *Sender)
{
  // Get the first AL East child (team)
  TTreeNode* team = alEast->getFirstChild();
  while ( team )
  {
    ListBox1->Items->Add(team->Text);
    // Continue as long as there are children
    team = alEast->GetNextChild(team);
  }
}
//--------------------------------------------------

void __fastcall TForm1::WalkChildrenUpClick(TObject *Sender)
{
  // Get the last child node in the AL East (team)
  TTreeNode* team = alEast->GetLastChild();
  while ( team )
  {
    ListBox1->Items->Add(team->Text);
    // Walk backwards as long as there are previous children
    team = alEast->GetPrevChild(team);
  }
}
//--------------------------------------------------

void __fastcall TForm1::WalkChildrenForClick(TObject *Sender)
{
  // If AL East has children, add names to ListBox1 
  if ( alEast->HasChildren )
  {
    ListBox1->Items->Add("Here are the teams in the AL East:");
    for ( int team = 0; team < alEast->Count; team++ )
      ListBox1->Items->Add("  " + alEast->Item[team]->Text);
  }
  else
    ListBox1->Items->Add("There are on AL East teams!");
}
//--------------------------------------------------

void __fastcall TForm1::ChildIndexClick(TObject *Sender)
{
  // Get the index of the selected node
  TTreeNode *fc = TreeView1->Selected;
  if ( fc != NULL )
    ListBox1->Items->Add("Child node index for " + fc->Text +
      " is " + fc->Index);
  else
    ListBox1->Items->Add("Nothing selected!");
}
//--------------------------------------------------

void __fastcall TForm1::TestParentChildClick(TObject *Sender)
{
  // Test out Parent, HasAsParent, and Index
  TTreeNode* parent = bosNode->Parent;
  bool isparent = bosNode->HasAsParent(alEast);
  int bosPos = alEast->IndexOf(bosNode);

  if ( isparent )
  {
    String bosLoc = bosPos;
    ListBox1->Items->
      Add("Checked to see if Boston's parent was");
    ListBox1->Items->Add("AL East...it was!  Its child index is " 
      + bosLoc);
    ListBox1->Items->Add("Boston's parent name via the Parent");
    ListBox1->Items->Add("was " + parent->Text);
  }
}
//--------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  // Empty the ListBox 
  ListBox1->Items->Clear();
}
//--------------------------------------------------
Listing B: Unit1.h
//--------------------------------------------------
#ifndef Unit1H
#define Unit1H
//--------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ComCtrls.hpp>
//--------------------------------------------------

class Stats
{
  public:
    Stats(int x) { s = x; }
    ~Stats() { }
    int getStats(void) { return s; }
  private:
    int s;
};
//--------------------------------------------------

enum entryValue {ev_league = 0, ev_division, ev_team, ev_player};

struct nodeIdHandle
{
  entryValue nodeType;
  void* obj;
};

//--------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE-managed Components
  TTreeView *TreeView1;
  TButton *CreateFigA;
  TButton *AddTomGordon;
  TButton *InsertTomGordon;
  TButton *AddVaughn;
  TButton *DeleteGordon;
  TButton *DeleteChildren;
  TButton *GetFirstRoot;
  TButton *WalkRoots;
  TButton *WalkChildrenDown;
  TButton *Quit;
  TListBox *ListBox1;
  TButton *WalkChildrenUp;
  TButton *WalkChildrenFor;
  TButton *ChildIndex;
  TButton *TestParentChild;
  TButton *ShowLevel;
  TButton *Button1;
  void __fastcall CreateFigAClick(TObject *Sender);
  void __fastcall QuitClick(TObject *Sender);
  void __fastcall AddTomGordonClick(TObject *Sender);
  void __fastcall InsertTomGordonClick(TObject *Sender);
  void __fastcall DeleteGordonClick(TObject *Sender);
  void __fastcall AddVaughnClick(TObject *Sender);
  void __fastcall DeleteChildrenClick(TObject *Sender);
  void __fastcall GetFirstRootClick(TObject *Sender);
  void __fastcall ShowLevelClick(TObject *Sender);
  void __fastcall WalkRootsClick(TObject *Sender);
  void __fastcall WalkChildrenDownClick(TObject *Sender);
  void __fastcall WalkChildrenUpClick(TObject *Sender);
  void __fastcall WalkChildrenForClick(TObject *Sender);
  void __fastcall ChildIndexClick(TObject *Sender);
  void __fastcall TestParentChildClick(TObject *Sender);
  void __fastcall Button1Click(TObject *Sender);
private:	// User declarations
  // Pointers will help find specific parts of TTreeView later.
  TTreeNode *al, *alEast, *alCentral, *alWest, *nl, *nlEast, 
    *nlCentral, *nlWest, *bosNode, *pitNode, *pedroMartinezNode,
    *timWakefieldNode, *tomGordonNodeAdd, *tomGordonNodeInsert, 
    *bostonInfieldersNode, *moVaughn;
  Stats* stats;
public:		// User declarations
  __fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------------
extern PACKAGE TForm1 *Form1;
//--------------------------------------------------
#endif

Conclusion

In this article, we've tried to overcome the complexity of TTreeView by breaking it into manageable pieces. We've shown you how to create a TTreeView from scratch, and how to find your way around the TTreeView nodes. In the second part of this series, we'll discuss how to control the addition, deletion, and movement of TTreeNodes based on a user's interaction with TTreeView. We'll also work on controlling the appearance of TTreeView, as well as handling user events, persistence, and drag-and-drop operations.