Author: Tomas Rutkauskas
I would like to present a master - detail relationship within a TTreeView upon
opening a query at runtime. Getting the correct info is not a problem but I'm
totally stumped by the use of a TTreeView. Are there commands usable at runtime to
enable me to create and edit the nodes/ sub nodes of the treeview to present the
master records as nodes and the detail records as sub nodes within the treeview?
Answer:
Here's an example of creating / maintaining TreeNodes at runtime. Before I take
off, I assume that you use database tables like the following:
A MASTERTABLE, with fields M_ID (unique identifier) and M_NAME (a descriptive name);
A DETAILTABLE, with fields D_ID (unique identifier), D_NAME (a descriptive name),
and D_M_ID (a foreign key value that links the detail record to a master record.)
This could be quite different from what you have, but I need to make assumptions in
order to write this example.
The first step of the process is to add all master records as parent nodes of
detail nodes. It goes without saying that I need to add parent nodes first, since
detail nodes are 'dependent' of them.
You can add all master records to the TreeView by looping the query at runtime and
do something like this:
{Get all master records in query}
1 { ... }
2 while not qryMasterRecords.EOF do
3 begin
4 {some code will follow here, be patient my friend.}
5 TreeView1.Items.Add(nil, qryMasterRecords.FieldByName('M_NAME').AsString);
6 qryMasterRecords.Next;
7 end;
8 { ... }
The Add method (of a TTreeNodes object) adds the node to the TreeView; the first
parameter specifies the parent node. (in this case, nil means that it is added as a
root node.) The second parameter is the node name that is represented in the
TreeView. (this is the Text property of a TTreeNode.)
However, I am finished yet with the master records. How the heck am I going to
identify the master nodes when I want to insert detail nodes later on? When adding
a detail node, I need to know what its parent should be. A bad answer (to me) is to
say that one can use the node name as an identifier. Since we have a unique
identifier for each master record in the database, why don't we use it?
The solution to this lies in the Data property of the TreeNode object: This is a
pointer one can assign to an application-designed object. The intention is to use
such an object to store the unique identifier of the master record.
Let's use an record-type object like this:
9 type
10 PMaster = ^TMaster
11 TMaster = record
12 Identifier: integer;
13 end;
Assuming these types are used, I modify the master-node-adding code to this:
14 { ... }
15 var
16 MasterPtr: PMaster;
17 { ... }
18 {Get all master records in query}
19 { ... }
20 while not qryMasterRecords.EOF do
21 begin
22 New(MasterPtr);
23 Master^.Identifier := qryMasterRecords.FieldByname('M_ID').AsInteger);
24 TreeView1.Items.AddObject(nil, qryMasterRecords.FieldByName('M_NAME').AsString,
25 MasterPtr);
26 qryMasterRecords.Next;
27 end;
28 { ... }
At runtime, I create a record type object for each record that is found in the
query. I use a slightly extended version of the Add method. AddObject also links
MasterPtr with the Data property of the new node.
For now, I have finished with the master nodes: The next step is to add all detail
nodes. I need to write a small function that searches for a TreeNode with a
specified M_ID value. I need this while adding detail nodes, because I need to
identify a node that is the parent node of the detail node that is to be inserted.
29
30 function SearchMasterNode(iM_ID: integer): TTreeNode;
31 {Searches for a node with a specified M_ID value. Returns the TreeNode that has the
32 specified M_ID value. When it is not found, nil is returned.}
33 var
34 iCount: integer;
35 begin
36 {Default value to return}
37 Result := nil;
38 {For your info: iterating like this loops through all nodes in a TreeView,
39 including detail nodes}
40 for iCount := 0 to TreeView1.Items.Count - 1 do
41 begin
42 if Assigned(TreeView1.Items.Item[iCount].Data) then
43 if PMaster(TreeView1.Items.Item[iCount].Data)^.Identifier = iM_ID then
44 Result := TreeView1.Items.Item[iCount];
45 {We got a match !}
46 end;
47 end;
From now on, adding detail nodes is much like adding master nodes, with one extra
move: a search for a parent node.
48 { ... }
49 {Insert all master nodes to the TreeView}
50 { ... }
51 var
52 MasterNode: TTreeNode;
53
54 {Get all detail records in query}
55 { ... }
56 while not qryDetailRecords.EOF do
57 begin
58 MasterNode :=
59 SearchMasterNode(qryDetailRecords.FieldByName('D_M_ID').AsInteger);
60 {For your info: The Data property of this new node is set to nil.}
61 TreeView1.Items.AddChild(MasterNode,
62 qryDetailRecords.FieldByName('D_NAME').AsString);
63 qryDetailRecords.Next;
64 end;
The Add method is used here, since I assume that you don't need to identify detail
nodes for something else. When you do need this (for example, clicking on a detail
node must result in the representation of detail record data in edit boxes,
memo-boxes, whatever input control.) use the approach with master nodes.
Finally, to create an application that uses computer memory efficiently, I should
free all memory used for the record-type objects. I did this by iterating through
all nodes and freeing the data objects:
65 { ... }
66 var
67 iCount: integer;
68 { ... }
69 for iCount := 0 to TreeView1.Items.Count - 1 do
70 begin
71 if Assigned(TreeView1.Items.Item[iCount].Data) then
72 Dispose(TreeView1.Items.Item[iCount].Data);
73 end;
74 {Finally, free all nodes constructed at runtime}
75 TreeView1.Items.Clear;
76 { ... }
|