楼上说的对,用treeview1.seleted.data吧……不过也有人说用这个很慢……我也有些代码,贸然贴出,见谅……type PTreeData = ^TTreeData; TTreeData = record TreeID : integer; ParentID : integer; TreeText : string; ImageID : integer; end;type PNodeTemp = ^TNodeTemp; TNodeTemp = record NodeTemp : TTreeNode; TreeID : integer; end;******************************************************* 重要的好象就这些了……function TForm1.GiveNode(ID: integer): TTreeNode; var i : integer; begin Result := nil; if NodeList.Count = 0 then Result := TreeView1.Items.GetFirstNode else for i:=0 to NodeList.Count-1 do if ID = PNodeTemp(NodeList.Items[i])^.TreeID then Result := PNodeTemp(NodeList.Items[i])^.NodeTemp; end;procedure TForm1.LoadData; var DataInput : PTreeData; begin with ADOQuery1 do begin Close; Sql.Clear; Sql.Add('select treeid,parentid,treetext from treetab '); Open; end; //TreeID := ADOQuery1.RecordCount; DataList := TList.Create; ADOQuery1.First; while NOT ADOQuery1.Eof do begin New(DataInput); DataInput^.ParentID := ADOQuery1.FieldByName('parentid').AsInteger; DataInput^.TreeID := ADOQuery1.FieldByName('treeid').AsInteger; DataInput^.TreeText := ADOQuery1.FieldByName('treetext').AsString; DataList.Add(DataInput); ADOQuery1.Next; end; end; //将数据库中的数据全部提出来,保存到指针数组里;procedure TForm1.ProduceTree; var i,j : integer; Temp : TTreeNode; ANodeTemp : PNodeTemp; begin NodeList := TList.Create; if TreeView1.Items.Count = 0 then begin Temp := TreeView1.Items.AddChildObject(nil,PTreeData(DataList.Items[0])^.TreeText,DataList.Items[0]); New(ANodeTemp); ANodeTemp^.NodeTemp := Temp; ANodeTemp^.TreeID := PTreeData(DataList.Items[0])^.TreeID; NodeList.Add(ANodeTemp); end; for i:=0 to DataList.Count-2 do for j:=i+1 to DataList.Count-1 do begin New(ANodeTemp); if PTreeData(DataList.Items[i])^.TreeID = PTreeData(DataList.Items[j])^.ParentID then begin Temp := GiveNode( PTreeData(DataList.Items[i])^.TreeID ); ANodeTemp^.NodeTemp := TreeView1.Items.AddChildObject(Temp,PTreeData(DataList.Items[j])^.TreeText,DataList.Items[j]); ANodeTemp^.TreeID := PTreeData(DataList.Items[j])^.TreeID; PTreeData(DataList.Items[i])^.ImageID := 0; //if ANodeTemp^.NodeTemp.getFirstChild = nil then PTreeData(DataList.Items[j])^.ImageID := 9 else //PTreeData(DataList.Items[j])^.ImageID := 2; NodeList.Add(ANodeTemp); end else PTreeData(DataList.Items[j])^.ImageID := 1; end; NodeList.Clear; DataList.Clear; end;procedure TForm1.N5Click(Sender: TObject); var NewNode : PTreeData; begin if TreeView1.Selected = nil then exit; New(NewNode); Inc(TreeID); NewNode^.TreeID := TreeID; NewNode^.ParentID := PTreeData(TreeView1.Selected.Data)^.TreeID; NewNode^.TreeText := '新增节点'; TreeView1.Items.AddChildObject(TreeView1.Selected,'新增节点',NewNode); with ADOQuery1 do begin Close; Sql.Clear; Sql.Add ('insert into treetab (treeid,parentid,treetext) values (:treeid,:parentid,:treetext)'); Parameters.ParamByName('treeid').Value := NewNode^.TreeID ; Parameters.ParamByName('parentid').Value := NewNode^.ParentID; Parameters.ParamByName('treetext').Value := '新增节点'; Execsql; end; end;
做此类程序关键是你数据库表结构的设计如何??? 如果按下面的结构设计肯定没有问题! create table t_item (fitemid int default 0 not null primary key, --主键 fnumber varchar(50) not null, --代码 fname varchar(50) not null, --名称 fparentid int default 0 not null, --父级fitemid flevel int default 1 not null, --级次 fdetail int default 1 not null ---是否为明细 )
我的办法:表结构: create table sys_treeview( id Int not null primary key, name Varchar(255) not null, ParentId Int null)生成树时,用递归算法。 定义一个窗口级的TStringList,用来存储数据。它们之间通过String()和Pointer强制转化。Query1.SQL.Text := 'Select id,name from sys_TreeView where parentid ='+String(Node.Data); tempNode := TreeView1.Items.AddChild(Node); tempNode.Text := Query1.FieldByName('name'); i ;= idList.Add(Query1.FieldByName('id').AsString; tempNode.Data := Pointer(IdList.Strings[i]);... ...
Delphi中树型控件的使用技巧 boymaster摘 要:Delphi中树型控件的使用技巧关键字:控件,使用技巧类 别:COM & ActiveX 我们都知道,开发者主要用Delphi来开发数据库管理软件,正因如此,树型控件的使用最好与数据库联系起来。Delphi提供了一个树型控件TTreeView,可以用来描述复杂的层次关系。树节点信息的存储和加载 常用的方法是用树控件的 LoadFromFile和SavetoFile方法,来实现树控件和文件之间的交互;或用Assign方法实现树控件和DBMemo,也就是和数据库间的交互。该方法的优点是编程相对简单,缺点是树控件的实际节点数可能会很大,对于“大树”,每次加载和存储的数据量会加大,将降低速度,增大系统开销,造成数据冗余。另一种方法,就是只在树上产生“看得见”的节点,没有专门记录全部树节点结构的文件或数据库字段,而将树节点结构分散在数据库的每一个记录中。 具体方法是:创建一个数据库,字段根据实际业务而定,其中必然有一个字段的信息将在树型控件的节点上显示,另外还要一个字段来保存节点的惟一标识号,该标识号由长度相等的两部分组成,前段表示当前节点的父节点号,后段表示当前节点的节点号,此标识号相当于一个“链表”,记录了树上节点的结构。该方法的优点:用户操作“大树”时,一般不会展开所有的节点,而只用到有限的一部分,同时只能从树根一层一层地展开,该法只在树上产生“看得见”的节点,所以,存储和加载“大树”的速度快,数据量小,系统开销和数据冗余较小。缺点:编程较复杂,但可以结合该方法编成一个新的树控件,将大大提高编程效率。值得注意的是,ID号必须惟一,所以在编程中如何合理产生ID尤为重要。数据库结构示例 创建一个数据库,为简化程序,我只创建两个数据库字段,定义如下: 字段名 类型 长度 text c 10 longid c 6 LongID字段实际上由两段组成,每一段3位,LongID只能表示1000条记录。将LongID定义为索引字段,存为c:\testtree\tree.dbf。编辑该DBF文件,新建一条记录,Text字段设为TOP,LongID字段设为“000”(3个“0”前为三个空格)。创建演示程序 在Form1上放置TreeView1、Table1、PopupMenu1、Edit1、Edit2。TreeView1的PopupMenu属性设为PopupMenu1;Table1的DataBaseName属性设为c:\testtree,TableName属性设为tree.dbf,IndexFieldNames属性设为LongID;为PopupMenu1加选单项Add1和Del1,Caption分别为Add和Del;Edit1用来输入新节点的Text属性值,Edit2用来输入新节点的3位ID号。存为c:\testtree\treeunit.pas和c:\testtree\testtree.dpr。 在treeunit.pas的Type关键字后加入一行:Pstr:^string;{Pstr为字符串指针} 为Form1的OnCreate事件添加代码: procedure TForm1.FormCreate(Sender: TObject); var p:Pstr;Node:TTreeNode; begin with Table1,Treeview1 do begin open; first; new(p);{为指针p分配内存} p^:=FieldByName(′LongID′).AsString; Node:=Items.AddChildObject(nil,FieldByName(′Text′).AsString,p); if HasSubInDbf(Node) then Items.AddChildObject(Node,′ ′,nil);{有子节点则加一个空子节点} end; end; HasSubInDbf为自定义函数,自变量为Node,检查节点Node有无子节点,有则返回True,反之返回False,并在TForm1的类定义里加入原型声明(其它自定义函数的原型也在TForm1的类定义里声明,不另作解释),函数代码如下: function TForm1.HasSubInDbf(Node:TTreeNode):Boolean; begin with Table1 do begin Table1.FindNearest([copy(Pstr(Node.Data)^,4,3)+′000′]); result:=copy(FieldByName(′LongID′).AsString,1,3)=copy(Pstr(Node.Data)^,4,3);{如数据库里当前记录的LongID字段内容的前3位和节点Node的Data的后3位相同,则Node应该有子节点} end; end; 为TreeView1控件的OnDeletion事件添加代码,需要指出的是,不仅调用Delete方法可以触发OnDeletion事件,而且当树控件本身被释放前,也触发OnDeletion事件,所以,在此处加入dispose(node.data)会很“安全”: procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode); begin Dispose(Node.Data);{释放节点数据内存} end; 为Add1选单项的OnClick事件添加代码如下: procedure TForm1.Add1Click(Sender: TObject); var p:pstr; Tmpstr:string; i:integer; begin try StrToInt(Edit2.Text); Tmpstr:=Edit2.Text;{注:在实用中,必须用更好的方法来产生ID} except; ShowMessage(′重新输入Edit2的内容′); abort; end; with TreeView1 do begin new(p); p^:=copy(Pstr(Selected.Data)^,4,3)+TmpStr; Items.AddChildObject(Selected,Edit1.Text,p); end; with Table1 do{ 在数据库里添加记录 } begin Append; FieldByName(′Text′).AsString:=Edit1.text; FieldByName(′LongID′).AsString:=p^; Post; end; TmpStr:=inttostr(strtoint(TmpStr)+1); for i:=length(TmpStr) to 2 do TmpStr:=′0′+TmpStr; Edit2.Text:=TmpStr; end; 为Del1菜单项的OnClick事件添加代码如下: procedure TForm1.Del1Click(Sender: TObject); var DelList:TStringList; LongID,NSubLongID:string; begin DelList:=TStringList.create; DelList.Sorted:=True; DelList.Add(Pstr(TreeView1.Selected.Data)^); while DelList.Count>0 do begin LongID:=DelList.Strings[0]; DelList.Delete(0); Table1.SetKey; Table1.FieldByName(′LongID′).AsString:=LongID; if Table1.GotoKey then Table1.Delete; if HasSubInDbf(TreeView1.Selected) then begin NSubLongID:=Table1.FieldByName(′LongID′).AsString; while (copy(NSubLongID,1,3)=copy(LongID,4,3))and(not Table1.Eof) do begin dellist.Add(NSubLongId); Table1.Next; NSubLongId:=Table1.FieldByName(′LongID′).AsString; end; end; end; DelList.Free; TreeView1.Items.Delete(TreeView1.Selected); end; 为TreeView1的OnExpanding事件添加代码: procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean); var TmpNode:TTreeNode; NSubLongID:String; p:Pstr; bm:TBookMark; begin with Table1,TreeView1 do begin Items.BeginUpdate; SetKey; FieldByName(′LongID′).AsString:=Pstr(Node.Data)^; if not GotoKey then Items.Delete(Node) else begin TmpNode:=Node.GetFirstChild; if (TmpNode.Text=′ ′)and(TmpNode.Data=nil) then begin TmpNode.Delete; if HasSubInDbf(Node) then begin NSubLongID:=FieldByName(′LongID′).AsString; while (copy(NSubLongID,1,3)=copy(Pstr(Node.Data)^,4,3))and(not Eof) do begin new(p); p^:=FieldByName(′LongID′).AsString; bm:=GetBookMark; TmpNode:=Items.AddChildObject(Node,FieldByName(′Text′).AsString,p); if HasSubInDbf(TmpNode) then Items.AddChildObject(TmpNode,′ ′,nil); GotoBookMark(bm); FreeBookMark(bm); Next; NSubLongId:=FieldByName(′LongID′).AsString; end; end; end; end; Items.EndUpdate; end; end; 以上简要谈了谈数据库的树状显示的基本方法,另外,编辑树上节点的Text属性的同时对数据库进行修改、同一数据
刚才那个格式不对,重发一遍,不好意思我们都知道,开发者主要用Delphi来开发数据库管理软件,正因如此,树型控件的使用最好与数据库联系起来。Delphi提供了一个树型控件TTreeView,可以用来描述复杂的层次关系。树节点信息的存储和加载 常用的方法是用树控件的 LoadFromFile和SavetoFile方法,来实现树控件和文件之间的交互;或用Assign方法实现树控件和DBMemo,也就是和数据库间的交互。该方法的优点是编程相对简单,缺点是树控件的实际节点数可能会很大,对于“大树”,每次加载和存储的数据量会加大,将降低速度,增大系统开销,造成数据冗余。另一种方法,就是只在树上产生“看得见”的节点,没有专门记录全部树节点结构的文件或数据库字段,而将树节点结构分散在数据库的每一个记录中。 具体方法是:创建一个数据库,字段根据实际业务而定,其中必然有一个字段的信息将在树型控件的节点上显示,另外还要一个字段来保存节点的惟一标识号,该标识号由长度相等的两部分组成,前段表示当前节点的父节点号,后段表示当前节点的节点号,此标识号相当于一个“链表”,记录了树上节点的结构。该方法的优点:用户操作“大树”时,一般不会展开所有的节点,而只用到有限的一部分,同时只能从树根一层一层地展开,该法只在树上产生“看得见”的节点,所以,存储和加载“大树”的速度快,数据量小,系统开销和数据冗余较小。缺点:编程较复杂,但可以结合该方法编成一个新的树控件,将大大提高编程效率。值得注意的是,ID号必须惟一,所以在编程中如何合理产生ID尤为重要。 数据库结构示例 创建一个数据库,为简化程序,我只创建两个数据库字段,定义如下: 字段名 类型 长度 text c 10 longid c 6 LongID字段实际上由两段组成,每一段3位,LongID只能表示1000条记录。将LongID定义为索引字段,存为c:\testtree\tree.dbf。编辑该DBF文件,新建一条记录,Text字段设为TOP,LongID字段设为“000”(3个“0”前为三个空格)。 创建演示程序 在Form1上放置TreeView1、Table1、PopupMenu1、Edit1、Edit2。TreeView1的PopupMenu属性设为PopupMenu1;Table1的DataBaseName属性设为c:\testtree,TableName属性设为tree.dbf,IndexFieldNames属性设为LongID;为PopupMenu1加选单项Add1和Del1,Caption分别为Add和Del;Edit1用来输入新节点的Text属性值,Edit2用来输入新节点的3位ID号。存为c:\testtree\treeunit.pas和c:\testtree\testtree.dpr。 在treeunit.pas的Type关键字后加入一行:Pstr:^string;{Pstr为字符串指针} 为Form1的OnCreate事件添加代码: procedure TForm1.FormCreate(Sender: TObject); var p:Pstr;Node:TTreeNode; begin with Table1,Treeview1 do begin open; first; new(p);{为指针p分配内存} p^:=FieldByName(′LongID′).AsString; Node:=Items.AddChildObject(nil,FieldByName(′Text′).AsString,p); if HasSubInDbf(Node) then Items.AddChildObject(Node,′ ′,nil); {有子节点则加一个空子节点} end; end; HasSubInDbf为自定义函数,自变量为Node,检查节点Node有无子节点,有则返回True,反之返回False,并在TForm1的类定义里加入原型声明(其它自定义函数的原型也在TForm1的类定义里声明,不另作解释),函数代码如下: function TForm1.HasSubInDbf(Node:TTreeNode):Boolean; begin with Table1 do begin Table1.FindNearest([copy(Pstr(Node.Data)^,4,3)+′000′]); result:=copy(FieldByName(′LongID′).AsString,1,3)=copy(Pstr(Node.Data)^,4,3); {如数据库里当前记录的LongID字段内容的前3位和节点Node的Data的后3位相同,则Node应该有子节点} end; end; 为TreeView1控件的OnDeletion事件添加代码,需要指出的是,不仅调用Delete方法可以触发OnDeletion事件,而且当树控件本身被释放前,也触发OnDeletion事件,所以,在此处加入dispose(node.data)会很“安全”: procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode); begin Dispose(Node.Data);{释放节点数据内存} end; 为Add1选单项的OnClick事件添加代码如下: procedure TForm1.Add1Click(Sender: TObject); var p:pstr; Tmpstr:string; i:integer; begin try StrToInt(Edit2.Text); Tmpstr:=Edit2.Text;{注:在实用中,必须用更好的方法来产生ID} except; ShowMessage(′重新输入Edit2的内容′); abort; end; with TreeView1 do begin new(p); p^:=copy(Pstr(Selected.Data)^,4,3)+TmpStr; Items.AddChildObject(Selected,Edit1.Text,p); end; with Table1 do{ 在数据库里添加记录 } begin Append; FieldByName(′Text′).AsString:=Edit1.text; FieldByName(′LongID′).AsString:=p^; Post; end; TmpStr:=inttostr(strtoint(TmpStr)+1); for i:=length(TmpStr) to 2 do TmpStr:=′0′+TmpStr; Edit2.Text:=TmpStr; end; 为Del1菜单项的OnClick事件添加代码如下: procedure TForm1.Del1Click(Sender: TObject); var DelList:TStringList; LongID,NSubLongID:string; begin DelList:=TStringList.create; DelList.Sorted:=True; DelList.Add(Pstr(TreeView1.Selected.Data)^); while DelList.Count>0 do begin LongID:=DelList.Strings[0]; DelList.Delete(0); Table1.SetKey; Table1.FieldByName(′LongID′).AsString:=LongID; if Table1.GotoKey then Table1.Delete; if HasSubInDbf(TreeView1.Selected) then begin NSubLongID:=Table1.FieldByName(′LongID′).AsString; while (copy(NSubLongID,1,3)=copy(LongID,4,3))and(not Table1.Eof) do begin dellist.Add(NSubLongId); Table1.Next; NSubLongId:=Table1.FieldByName(′LongID′).AsString; end; end; end; DelList.Free; TreeView1.Items.Delete(TreeView1.Selected); end; 为TreeView1的OnExpanding事件添加代码: procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean); var TmpNode:TTreeNode; NSubLongID:String; p:Pstr; bm:TBookMark; begin with Table1,TreeView1 do begin Items.BeginUpdate; SetKey; FieldByName(′LongID′).AsString:=Pstr(Node.Data)^; if not GotoKey then Items.Delete(Node) else begin TmpNode:=Node.GetFirstChild; if (TmpNode.Text=′ ′)and(TmpNode.Data=nil) then begin TmpNode.Delete; if HasSubInDbf(Node) then begin NSubLongID:=FieldByName(′LongID′).AsString; while (copy(NSubLongID,1,3)=copy(Pstr(Node.Data)^,4,3))and(not Eof) do begin new(p); p^:=FieldByName(′LongID′).AsString; bm:=GetBookMark; TmpNode:=Items.AddChildObject(Node,FieldByName(′Text′).AsString,p); if HasSubInDbf(TmpNode) then Items.AddChildObject(TmpNode,′ ′,nil); GotoBookMark(bm); FreeBookMark(bm); Next; NSubLongId:=FieldByName(′LongID′).AsString; end; end; end; end; Items.EndUpdate; end; end;
自己定义一个记录类型,如: PMyData = ^TMyData; TMyData = record ID: Integer; Name: String; end; 当然,你可以加入其它你所需要的属性,然后与TreeView的Data属性关联。 Delphi的帮助中有如下内容: The following code defines a record type of TMyRec and a record pointer type of PMyRec.type PMyRec = ^TMyRec; TMyRec = record FName: string; LName: string; end;Assuming these types are used, the following code adds a node to TreeView1 as the last sibling of a specified node. A TMyRec record is associated with the added item. The FName and LName fields are obtained from edit boxes Edit1 and Edit2. The Index parameter is obtained from edit box Edit3. The item is added only if the Index is a valid value.procedure TForm1.Button1Click(Sender: TObject);var MyRecPtr: PMyRec; TreeViewIndex: LongInt; begin New(MyRecPtr); MyRecPtr^.FName := Edit1.Text; MyRecPtr^.LName := Edit2.Text; TreeViewIndex := StrToInt(Edit3.Text); with TreeView1 do begin if Items.Count = 0 then Items.AddObject(nil, 'Item' + IntToStr(TreeViewIndex), MyRecPtr) else if (TreeViewIndex < Items.Count) and (TreeViewIndex >= 0) then Items.AddObject(Items[TreeViewIndex], 'Item' + IntToStr(TreeViewIndex), MyRecPtr); end; end;After an item containing a TMyRec record has been added, the following code retrieves the FName and LName values associated with the item and displays the values in a label.procedure TForm1.Button2Click(Sender: TObject);begin Label1.Caption := PMyRec(TreeView1.Selected.Data)^.FName + ' ' + PMyRec(TreeView1.Selected.Data)^.LName; end;
用树型结构表示科目代码的一种高效算法
在很多常见的财务软件中,科目代码一般都用树型结构来显示。要实现这一点,通常的做法是用多个(嵌套)循环,甚至递归等算法,将科目表中的代码”织”成树,但这样不但算法复杂,而且执行效率低。本人在实际的开发应用中,摸索出一种简单高效的算法,在此和盆托出,只在抛砖引玉,找出最佳解决方案。下面介绍在Delphi中的实现方法。一.表结构 首先建立如下结构的数据表Code.DB,并输入一些测试数据:字段名 类型 长度 说明
aCode 字符型 20 科目代码
aName 字符型 30 科目代码名称
...... ...... ...... ......
表(一) 其中,科目代码aCode的数据类型一定要字符型(一定),长度按具体要求而定,假如要支持六级编码,且代码结构是”3-2-2-2-2-2”,则该字段的长度不小于18,而其他字段则不作要求 。另外,要为字段aCode建一索引(切记),因为要用它来排序。二.编写程序 1.新建一Project:CodeTree.drp,主窗体命名为frmMain,单元存为Main.Pas。在frmMain上添加一TtreeView控件,命名为tveCode,一个TImageList,命名为imgIcon,并装入三个Icon和Bmp,最后添加一Ttable控件,命名tblCode。frmMain和各控件的属性按表(二)设置:组件 属性 设置
FrmMain Caption ’科目代码’
Font 宋体 9号
BorderStyle BsDialog
TvwCode Images ImgIcon
ReadOnly True
ImgIcon ImageList 装入三个图标
BtnClose Caption 关闭(C)表(二) 2. 单元main.pas的完整源代码如下:unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs,
Db, DBTables, ComCtrls, ImgList, StdCtrls;
type
TForm1 = class(TForm)
tvwCode: TTreeView;
tblCode: TTable;
ImageList1: TImageList;
btnClose: TButton;
procedure FormCreate(Sender: TObject);
procedure btnCloseClick(Sender: TObject);
private
{ Private declarations }
function LoadCode(crTbl:TDBDataSet):Integer;
function GetLevel(sFormat,sCode:String):Integer;
public
{ Public declarations }
end;
var
Form1: TForm1;
const
SCodeFormat = ’322222’; //科目代码结构
SFirstNodeTxt = ’科目代码’; //首节点显示的文字
implementation
{$R *.DFM}
//以下函数是本文的重点部分,
其主要功能是用一循环将Code.db表中的
//科目代码和科目代码名称显示出来
function TForm1.LoadCode(crTbl:TDBDataSet):Integer;
var NowID,sName,ShowTxt:String;
i,Level:Integer;
MyNode:array[0..6]of TTreeNode;
//保存各级节点,最长支持6级(重点)
begin
Screen.Cursor:=crHourGlass;
Level:=0;
With crTbl do
begin
try
if not Active then Open;
First;
tvwCode.Items.Clear;
//以下是增加第一项
MyNode[Level]:=tvwCode.Items.Add
(tvwCode.TopItem,SFirstNodeTxt);
MyNode[Level].ImageIndex:=0;
MyNode[Level].SelectedIndex:=0;
//以上是增加第一项
While Not Eof do
begin
NowID:=Trim(FieldByName(’aCode’).AsString);
ShowTxt:=NowID+’ ’+FieldByName(’aName’).AsString;
Level:=GetLevel(SCodeFormat,NowID);
//返回代码的级数
//以下是增加子项
//以下用上一级节点为父节点添加子节点
if Level>0 then//确保代码符合标准
begin
MyNode[Level]:=tvwCode.Items.AddChild
(MyNode[Level-1],ShowTxt);
MyNode[Level].ImageIndex:=1;
MyNode[Level].SelectedIndex:=2;
end;
//以上是增加子项
Next;
end;
finally
Close;
end;
end;
MyNode[0].Expand(False);//将首节点展开
Screen.Cursor:=crDefault;
end;
//以上函数将Code.db表中的科目代码和科目代码名称显示出来
//下面函数的功能是返回一代码的级数,
参数sFormat传递科目代码结构;
//参数sCode传递某一科目代码
function TForm1.GetLevel
(sFormat,sCode:String):Integer;
var i,Level,iLen:Integer;
begin
Level:=-1;//如果代码不符合标准,则返回-1
iLen:=0;
if (sFormat< >’’)and(sCode< >’’)then
for i:=1 to Length(sFormat) do
begin
iLen:=iLen+StrToInt(sFormat[i]);
if Length(sCode)=iLen then
begin
Level:=i;
Break;
end;
end;
Result:=Level;
end;
//上面函数的功能是返回一代码的级数
procedure TForm1.FormCreate(Sender: TObject);
begin
with tblCode do
begin
DatabaseName:=ParamStr(1);
//使tblCode的DatabaseName指向应用程序所在的路径
TableName:=’Code.DB’; //指向数据表Code.DB
Open;
IndexFieldNames:=’aCode’;
//按字段aCode排序(不要漏掉)
end;
LoadCode(tblCode);
end;
procedure TForm1.btnCloseClick(Sender: TObject);
begin
Close;
end;
end.
其中,常量ScodeFormat是科目代码的代码结构,其定义的规则一定要和数据表Code..DB中的字段aCode的值相符。所以在实际应用中,让用户新增科目代码时,必须严格检查其规范性,只有完全符合事先定义的代码结构,才能添加入库。 函数GetLevel是求某一科目代码的级数,例如,有一科目代码”10102”,在代码结构是”322222”的情况下,调用函数GetLevel(’322222’,’10102’)将返回整数2 。 当然,本文的核心是LoadCode函数,该函数用了一个循环来遍历数据表Code.DB的所有记录,将字段aCode和aName的内容按层次显示出来。而在该函数中定义的二维数组MyNode[0..6],则显得优为重要,在这里,它作用类似于递归中的栈。因为在TTreeView添加子节点的方法AddChild(Node: TTreeNode; const S: string)中,要为其指定一父节点作为参数,而父节点的代码级数一定是要添加节点的代码级数减1,所以只要用一数组来动态保存和指定父节点就成功了。三.运行结果 好了,现在把Code.DB复制到和可执行文件相同的目录下,按下F9键编译运行,本人运行的效果如图(一)。只要在以上的基础上加以完善,如增加维护功能,就可搬到实际应用中了。当然,本算法不单能用在科目代码上,其他类似的树型结构都能奏效。本人就已将此算法应用于[科目代码]、[物料清单(BOM)]、[库房管理]和[物料主文件]等多个模块中,取得令人满意的效果。
最通用的方法还是链表,每条记录保存本结点ID和上级结点ID。
搜索时,采用队列数据结构,先进先出,先找上级ID为0的,自然是根结点,进队列。队头出队时,把它的所有直接下级搜索进队。直到队空,就完成了建树的工作。
PTreeData = ^TTreeData;
TTreeData = record
TreeID : integer;
ParentID : integer;
TreeText : string;
ImageID : integer;
end;type
PNodeTemp = ^TNodeTemp;
TNodeTemp = record
NodeTemp : TTreeNode;
TreeID : integer;
end;*******************************************************
重要的好象就这些了……function TForm1.GiveNode(ID: integer): TTreeNode;
var i : integer;
begin
Result := nil;
if NodeList.Count = 0 then
Result := TreeView1.Items.GetFirstNode else
for i:=0 to NodeList.Count-1 do
if ID = PNodeTemp(NodeList.Items[i])^.TreeID then
Result := PNodeTemp(NodeList.Items[i])^.NodeTemp;
end;procedure TForm1.LoadData;
var DataInput : PTreeData;
begin
with ADOQuery1 do begin
Close;
Sql.Clear;
Sql.Add('select treeid,parentid,treetext from treetab ');
Open;
end;
//TreeID := ADOQuery1.RecordCount;
DataList := TList.Create;
ADOQuery1.First;
while NOT ADOQuery1.Eof do begin
New(DataInput);
DataInput^.ParentID := ADOQuery1.FieldByName('parentid').AsInteger;
DataInput^.TreeID := ADOQuery1.FieldByName('treeid').AsInteger;
DataInput^.TreeText := ADOQuery1.FieldByName('treetext').AsString;
DataList.Add(DataInput);
ADOQuery1.Next;
end;
end; //将数据库中的数据全部提出来,保存到指针数组里;procedure TForm1.ProduceTree;
var i,j : integer;
Temp : TTreeNode;
ANodeTemp : PNodeTemp;
begin
NodeList := TList.Create; if TreeView1.Items.Count = 0 then begin
Temp := TreeView1.Items.AddChildObject(nil,PTreeData(DataList.Items[0])^.TreeText,DataList.Items[0]);
New(ANodeTemp);
ANodeTemp^.NodeTemp := Temp;
ANodeTemp^.TreeID := PTreeData(DataList.Items[0])^.TreeID;
NodeList.Add(ANodeTemp);
end; for i:=0 to DataList.Count-2 do
for j:=i+1 to DataList.Count-1 do begin
New(ANodeTemp);
if PTreeData(DataList.Items[i])^.TreeID = PTreeData(DataList.Items[j])^.ParentID then begin
Temp := GiveNode( PTreeData(DataList.Items[i])^.TreeID );
ANodeTemp^.NodeTemp := TreeView1.Items.AddChildObject(Temp,PTreeData(DataList.Items[j])^.TreeText,DataList.Items[j]);
ANodeTemp^.TreeID := PTreeData(DataList.Items[j])^.TreeID;
PTreeData(DataList.Items[i])^.ImageID := 0;
//if ANodeTemp^.NodeTemp.getFirstChild = nil then PTreeData(DataList.Items[j])^.ImageID := 9 else
//PTreeData(DataList.Items[j])^.ImageID := 2;
NodeList.Add(ANodeTemp);
end else PTreeData(DataList.Items[j])^.ImageID := 1;
end; NodeList.Clear;
DataList.Clear;
end;procedure TForm1.N5Click(Sender: TObject);
var NewNode : PTreeData;
begin
if TreeView1.Selected = nil then exit;
New(NewNode);
Inc(TreeID);
NewNode^.TreeID := TreeID;
NewNode^.ParentID := PTreeData(TreeView1.Selected.Data)^.TreeID;
NewNode^.TreeText := '新增节点';
TreeView1.Items.AddChildObject(TreeView1.Selected,'新增节点',NewNode);
with ADOQuery1 do begin
Close;
Sql.Clear;
Sql.Add ('insert into treetab (treeid,parentid,treetext) values (:treeid,:parentid,:treetext)');
Parameters.ParamByName('treeid').Value := NewNode^.TreeID ;
Parameters.ParamByName('parentid').Value := NewNode^.ParentID;
Parameters.ParamByName('treetext').Value := '新增节点';
Execsql;
end;
end;
如果按下面的结构设计肯定没有问题!
create table t_item
(fitemid int default 0 not null primary key, --主键
fnumber varchar(50) not null, --代码
fname varchar(50) not null, --名称
fparentid int default 0 not null, --父级fitemid
flevel int default 1 not null, --级次
fdetail int default 1 not null ---是否为明细
)
每次生成treeview时,再从数据库中导出这个文件,用loadfromfile()转载就行了,呵呵
不过我还没有试过,不知道行不行!
create table sys_treeview(
id Int not null primary key,
name Varchar(255) not null,
ParentId Int null)生成树时,用递归算法。
定义一个窗口级的TStringList,用来存储数据。它们之间通过String()和Pointer强制转化。Query1.SQL.Text := 'Select id,name from sys_TreeView where parentid ='+String(Node.Data);
tempNode := TreeView1.Items.AddChild(Node);
tempNode.Text := Query1.FieldByName('name');
i ;= idList.Add(Query1.FieldByName('id').AsString;
tempNode.Data := Pointer(IdList.Strings[i]);...
...
你的数据表设计可以如下1.根结点名称(即其text,下同)2.父节点名称 3.本节点名称 4.层次号 5.是否无子节点从上面的表结构设计中注意以下几点:
a.根结点就是一棵树的根,在TReeview中可以有几颗树
b.根结点的父节点名称可以任意设置一个不可能的值比如我选择“◎◎◎◎”
c.层次号就是0.1.2....,可以直接使用treeview的level属性
d.一棵树的某个层次不能出现相同的节点名称(你觉得可能吗?呵呵)
e.不同树的相同层次可以出现相同的节点,但是别忘了他们的根结点不能相同。编写程序的时候,有两种方法,第一种就是笨笨的递归调用,一次生成所有节点(想当初我就是这么干的,好没效率,特别是节点很多的时候难以忍受)
第二种方法就有点技巧了,那就是使用Treeview的onexpanding事件,在生成Treeview的时候首先生成0层节点,然后通过是“否有字节点”字段可以判断它是否还有下一级,如果有的话就生成一个临时节点,以表示有字节点,当然这时不能让操作的人员发现其实下面不是真正他想要的节点,等他expand这个节点的时候我们就可以通过onexpanding事件来查找真正的子节点是什么了,删除这个临时节点并生成其下一级。唉不知道各位看官有没有明白,也许需要回顾一下以下几点:
1。初次生成treeview时,只生成第0层,同时判断是否有下一级节点,如有,生成temp节点,不过不要展开,不然就露馅了
2。当点击某个节点时,现从数据表中找到其下层节点,然后删除temp节点,并添加下层节点。(层层递进)
3。当然还有一点要说明的就是需要程序中加入一个判断的标记,表示这个节点已经展开过了,不是临时节点,从而避免重复展开和搜索下层节点。方法是给你的临时节点加上一个古怪的Text,然后判断getfirstchild的text是否为这个古怪的Text。好了,我的做法就是这样,也许还有更好的方法,不过这个方法可以避免首度进入form就要搜索全部节点,也避免使用递归,只不过编程是麻烦一点。
boymaster摘 要:Delphi中树型控件的使用技巧关键字:控件,使用技巧类 别:COM & ActiveX
我们都知道,开发者主要用Delphi来开发数据库管理软件,正因如此,树型控件的使用最好与数据库联系起来。Delphi提供了一个树型控件TTreeView,可以用来描述复杂的层次关系。树节点信息的存储和加载 常用的方法是用树控件的 LoadFromFile和SavetoFile方法,来实现树控件和文件之间的交互;或用Assign方法实现树控件和DBMemo,也就是和数据库间的交互。该方法的优点是编程相对简单,缺点是树控件的实际节点数可能会很大,对于“大树”,每次加载和存储的数据量会加大,将降低速度,增大系统开销,造成数据冗余。另一种方法,就是只在树上产生“看得见”的节点,没有专门记录全部树节点结构的文件或数据库字段,而将树节点结构分散在数据库的每一个记录中。 具体方法是:创建一个数据库,字段根据实际业务而定,其中必然有一个字段的信息将在树型控件的节点上显示,另外还要一个字段来保存节点的惟一标识号,该标识号由长度相等的两部分组成,前段表示当前节点的父节点号,后段表示当前节点的节点号,此标识号相当于一个“链表”,记录了树上节点的结构。该方法的优点:用户操作“大树”时,一般不会展开所有的节点,而只用到有限的一部分,同时只能从树根一层一层地展开,该法只在树上产生“看得见”的节点,所以,存储和加载“大树”的速度快,数据量小,系统开销和数据冗余较小。缺点:编程较复杂,但可以结合该方法编成一个新的树控件,将大大提高编程效率。值得注意的是,ID号必须惟一,所以在编程中如何合理产生ID尤为重要。数据库结构示例 创建一个数据库,为简化程序,我只创建两个数据库字段,定义如下: 字段名 类型 长度 text c 10 longid c 6 LongID字段实际上由两段组成,每一段3位,LongID只能表示1000条记录。将LongID定义为索引字段,存为c:\testtree\tree.dbf。编辑该DBF文件,新建一条记录,Text字段设为TOP,LongID字段设为“000”(3个“0”前为三个空格)。创建演示程序 在Form1上放置TreeView1、Table1、PopupMenu1、Edit1、Edit2。TreeView1的PopupMenu属性设为PopupMenu1;Table1的DataBaseName属性设为c:\testtree,TableName属性设为tree.dbf,IndexFieldNames属性设为LongID;为PopupMenu1加选单项Add1和Del1,Caption分别为Add和Del;Edit1用来输入新节点的Text属性值,Edit2用来输入新节点的3位ID号。存为c:\testtree\treeunit.pas和c:\testtree\testtree.dpr。 在treeunit.pas的Type关键字后加入一行:Pstr:^string;{Pstr为字符串指针} 为Form1的OnCreate事件添加代码: procedure TForm1.FormCreate(Sender: TObject); var p:Pstr;Node:TTreeNode; begin with Table1,Treeview1 do begin open; first; new(p);{为指针p分配内存} p^:=FieldByName(′LongID′).AsString; Node:=Items.AddChildObject(nil,FieldByName(′Text′).AsString,p); if HasSubInDbf(Node) then Items.AddChildObject(Node,′ ′,nil);{有子节点则加一个空子节点} end; end; HasSubInDbf为自定义函数,自变量为Node,检查节点Node有无子节点,有则返回True,反之返回False,并在TForm1的类定义里加入原型声明(其它自定义函数的原型也在TForm1的类定义里声明,不另作解释),函数代码如下: function TForm1.HasSubInDbf(Node:TTreeNode):Boolean; begin with Table1 do begin Table1.FindNearest([copy(Pstr(Node.Data)^,4,3)+′000′]); result:=copy(FieldByName(′LongID′).AsString,1,3)=copy(Pstr(Node.Data)^,4,3);{如数据库里当前记录的LongID字段内容的前3位和节点Node的Data的后3位相同,则Node应该有子节点} end; end; 为TreeView1控件的OnDeletion事件添加代码,需要指出的是,不仅调用Delete方法可以触发OnDeletion事件,而且当树控件本身被释放前,也触发OnDeletion事件,所以,在此处加入dispose(node.data)会很“安全”: procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode); begin Dispose(Node.Data);{释放节点数据内存} end; 为Add1选单项的OnClick事件添加代码如下: procedure TForm1.Add1Click(Sender: TObject); var p:pstr; Tmpstr:string; i:integer; begin try StrToInt(Edit2.Text); Tmpstr:=Edit2.Text;{注:在实用中,必须用更好的方法来产生ID} except; ShowMessage(′重新输入Edit2的内容′); abort; end; with TreeView1 do begin new(p); p^:=copy(Pstr(Selected.Data)^,4,3)+TmpStr; Items.AddChildObject(Selected,Edit1.Text,p); end; with Table1 do{ 在数据库里添加记录 } begin Append; FieldByName(′Text′).AsString:=Edit1.text; FieldByName(′LongID′).AsString:=p^; Post; end; TmpStr:=inttostr(strtoint(TmpStr)+1); for i:=length(TmpStr) to 2 do TmpStr:=′0′+TmpStr; Edit2.Text:=TmpStr; end; 为Del1菜单项的OnClick事件添加代码如下: procedure TForm1.Del1Click(Sender: TObject); var DelList:TStringList; LongID,NSubLongID:string; begin DelList:=TStringList.create; DelList.Sorted:=True; DelList.Add(Pstr(TreeView1.Selected.Data)^); while DelList.Count>0 do begin LongID:=DelList.Strings[0]; DelList.Delete(0); Table1.SetKey; Table1.FieldByName(′LongID′).AsString:=LongID; if Table1.GotoKey then Table1.Delete; if HasSubInDbf(TreeView1.Selected) then begin NSubLongID:=Table1.FieldByName(′LongID′).AsString; while (copy(NSubLongID,1,3)=copy(LongID,4,3))and(not Table1.Eof) do begin dellist.Add(NSubLongId); Table1.Next; NSubLongId:=Table1.FieldByName(′LongID′).AsString; end; end; end; DelList.Free; TreeView1.Items.Delete(TreeView1.Selected); end; 为TreeView1的OnExpanding事件添加代码: procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean); var TmpNode:TTreeNode; NSubLongID:String; p:Pstr; bm:TBookMark; begin with Table1,TreeView1 do begin Items.BeginUpdate; SetKey; FieldByName(′LongID′).AsString:=Pstr(Node.Data)^; if not GotoKey then Items.Delete(Node) else begin TmpNode:=Node.GetFirstChild; if (TmpNode.Text=′ ′)and(TmpNode.Data=nil) then begin TmpNode.Delete; if HasSubInDbf(Node) then begin NSubLongID:=FieldByName(′LongID′).AsString; while (copy(NSubLongID,1,3)=copy(Pstr(Node.Data)^,4,3))and(not Eof) do begin new(p); p^:=FieldByName(′LongID′).AsString; bm:=GetBookMark; TmpNode:=Items.AddChildObject(Node,FieldByName(′Text′).AsString,p); if HasSubInDbf(TmpNode) then Items.AddChildObject(TmpNode,′ ′,nil); GotoBookMark(bm); FreeBookMark(bm); Next; NSubLongId:=FieldByName(′LongID′).AsString; end; end; end; end; Items.EndUpdate; end; end; 以上简要谈了谈数据库的树状显示的基本方法,另外,编辑树上节点的Text属性的同时对数据库进行修改、同一数据
数据库结构示例
创建一个数据库,为简化程序,我只创建两个数据库字段,定义如下:
字段名 类型 长度
text c 10
longid c 6 LongID字段实际上由两段组成,每一段3位,LongID只能表示1000条记录。将LongID定义为索引字段,存为c:\testtree\tree.dbf。编辑该DBF文件,新建一条记录,Text字段设为TOP,LongID字段设为“000”(3个“0”前为三个空格)。
创建演示程序 在Form1上放置TreeView1、Table1、PopupMenu1、Edit1、Edit2。TreeView1的PopupMenu属性设为PopupMenu1;Table1的DataBaseName属性设为c:\testtree,TableName属性设为tree.dbf,IndexFieldNames属性设为LongID;为PopupMenu1加选单项Add1和Del1,Caption分别为Add和Del;Edit1用来输入新节点的Text属性值,Edit2用来输入新节点的3位ID号。存为c:\testtree\treeunit.pas和c:\testtree\testtree.dpr。 在treeunit.pas的Type关键字后加入一行:Pstr:^string;{Pstr为字符串指针} 为Form1的OnCreate事件添加代码:
procedure TForm1.FormCreate(Sender: TObject);
var
p:Pstr;Node:TTreeNode;
begin
with Table1,Treeview1 do
begin
open;
first;
new(p);{为指针p分配内存}
p^:=FieldByName(′LongID′).AsString;
Node:=Items.AddChildObject(nil,FieldByName(′Text′).AsString,p);
if HasSubInDbf(Node) then Items.AddChildObject(Node,′ ′,nil);
{有子节点则加一个空子节点}
end;
end; HasSubInDbf为自定义函数,自变量为Node,检查节点Node有无子节点,有则返回True,反之返回False,并在TForm1的类定义里加入原型声明(其它自定义函数的原型也在TForm1的类定义里声明,不另作解释),函数代码如下:
function TForm1.HasSubInDbf(Node:TTreeNode):Boolean;
begin
with Table1 do
begin
Table1.FindNearest([copy(Pstr(Node.Data)^,4,3)+′000′]);
result:=copy(FieldByName(′LongID′).AsString,1,3)=copy(Pstr(Node.Data)^,4,3);
{如数据库里当前记录的LongID字段内容的前3位和节点Node的Data的后3位相同,则Node应该有子节点}
end;
end; 为TreeView1控件的OnDeletion事件添加代码,需要指出的是,不仅调用Delete方法可以触发OnDeletion事件,而且当树控件本身被释放前,也触发OnDeletion事件,所以,在此处加入dispose(node.data)会很“安全”:
procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode);
begin
Dispose(Node.Data);{释放节点数据内存}
end; 为Add1选单项的OnClick事件添加代码如下:
procedure TForm1.Add1Click(Sender: TObject);
var
p:pstr;
Tmpstr:string;
i:integer;
begin
try
StrToInt(Edit2.Text);
Tmpstr:=Edit2.Text;{注:在实用中,必须用更好的方法来产生ID}
except;
ShowMessage(′重新输入Edit2的内容′);
abort;
end;
with TreeView1 do
begin
new(p);
p^:=copy(Pstr(Selected.Data)^,4,3)+TmpStr;
Items.AddChildObject(Selected,Edit1.Text,p);
end; with Table1 do{ 在数据库里添加记录 }
begin
Append;
FieldByName(′Text′).AsString:=Edit1.text;
FieldByName(′LongID′).AsString:=p^;
Post;
end; TmpStr:=inttostr(strtoint(TmpStr)+1);
for i:=length(TmpStr) to 2 do TmpStr:=′0′+TmpStr;
Edit2.Text:=TmpStr;
end; 为Del1菜单项的OnClick事件添加代码如下: procedure TForm1.Del1Click(Sender: TObject);
var
DelList:TStringList;
LongID,NSubLongID:string;
begin
DelList:=TStringList.create;
DelList.Sorted:=True;
DelList.Add(Pstr(TreeView1.Selected.Data)^);
while DelList.Count>0 do
begin
LongID:=DelList.Strings[0];
DelList.Delete(0);
Table1.SetKey;
Table1.FieldByName(′LongID′).AsString:=LongID;
if Table1.GotoKey then Table1.Delete;
if HasSubInDbf(TreeView1.Selected) then
begin
NSubLongID:=Table1.FieldByName(′LongID′).AsString;
while (copy(NSubLongID,1,3)=copy(LongID,4,3))and(not Table1.Eof) do
begin
dellist.Add(NSubLongId);
Table1.Next;
NSubLongId:=Table1.FieldByName(′LongID′).AsString;
end;
end;
end;
DelList.Free;
TreeView1.Items.Delete(TreeView1.Selected);
end; 为TreeView1的OnExpanding事件添加代码:
procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
var
TmpNode:TTreeNode;
NSubLongID:String;
p:Pstr;
bm:TBookMark;
begin
with Table1,TreeView1 do
begin
Items.BeginUpdate;
SetKey;
FieldByName(′LongID′).AsString:=Pstr(Node.Data)^;
if not GotoKey then Items.Delete(Node)
else
begin
TmpNode:=Node.GetFirstChild;
if (TmpNode.Text=′ ′)and(TmpNode.Data=nil) then
begin
TmpNode.Delete;
if HasSubInDbf(Node) then
begin
NSubLongID:=FieldByName(′LongID′).AsString;
while (copy(NSubLongID,1,3)=copy(Pstr(Node.Data)^,4,3))and(not Eof) do
begin
new(p);
p^:=FieldByName(′LongID′).AsString;
bm:=GetBookMark;
TmpNode:=Items.AddChildObject(Node,FieldByName(′Text′).AsString,p);
if HasSubInDbf(TmpNode) then Items.AddChildObject(TmpNode,′ ′,nil);
GotoBookMark(bm);
FreeBookMark(bm);
Next;
NSubLongId:=FieldByName(′LongID′).AsString;
end;
end;
end;
end;
Items.EndUpdate;
end;
end;
PMyData = ^TMyData;
TMyData = record
ID: Integer;
Name: String;
end;
当然,你可以加入其它你所需要的属性,然后与TreeView的Data属性关联。
Delphi的帮助中有如下内容:
The following code defines a record type of TMyRec and a record pointer type of PMyRec.type
PMyRec = ^TMyRec;
TMyRec = record
FName: string;
LName: string;
end;Assuming these types are used, the following code adds a node to TreeView1 as the last sibling of a specified node. A TMyRec record is associated with the added item. The FName and LName fields are obtained from edit boxes Edit1 and Edit2. The Index parameter is obtained from edit box Edit3. The item is added only if the Index is a valid value.procedure TForm1.Button1Click(Sender: TObject);var
MyRecPtr: PMyRec;
TreeViewIndex: LongInt;
begin
New(MyRecPtr);
MyRecPtr^.FName := Edit1.Text;
MyRecPtr^.LName := Edit2.Text;
TreeViewIndex := StrToInt(Edit3.Text);
with TreeView1 do
begin
if Items.Count = 0 then
Items.AddObject(nil, 'Item' + IntToStr(TreeViewIndex), MyRecPtr)
else if (TreeViewIndex < Items.Count) and (TreeViewIndex >= 0) then Items.AddObject(Items[TreeViewIndex], 'Item' + IntToStr(TreeViewIndex), MyRecPtr);
end;
end;After an item containing a TMyRec record has been added, the following code retrieves the FName and LName values associated with the item and displays the values in a label.procedure TForm1.Button2Click(Sender: TObject);begin
Label1.Caption := PMyRec(TreeView1.Selected.Data)^.FName + ' ' +
PMyRec(TreeView1.Selected.Data)^.LName;
end;