treeview1.items.count阿这个是唯一的啊

解决方案 »

  1.   

    我就给你介绍一个例子吧!
    用树型结构表示科目代码的一种高效算法  
         在很多常见的财务软件中,科目代码一般都用树型结构来显示。要实现这一点,通常的做法是用多个(嵌套)循环,甚至递归等算法,将科目表中的代码”织”成树,但这样不但算法复杂,而且执行效率低。本人在实际的开发应用中,摸索出一种简单高效的算法,在此和盆托出,只在抛砖引玉,找出最佳解决方案。下面介绍在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)]、[库房管理]和[物料主文件]等多个模块中,取得令人满意的效果。      
      

  2.   

    TTreeView中的每个节点TTreeNode的指针不就是唯一的了。
      

  3.   

    这种编码的方法是好用,不过通用性不好,如果层次无限加深或是节点数无限增大就会有问题了。
    最通用的方法还是链表,每条记录保存本结点ID和上级结点ID。
    搜索时,采用队列数据结构,先进先出,先找上级ID为0的,自然是根结点,进队列。队头出队时,把它的所有直接下级搜索进队。直到队空,就完成了建树的工作。
      

  4.   

    楼上说的对,用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;
      

  5.   

    以上各位说的都有道理,我也知道用链表,为了保持在添加、删除、排序后不用更新整个树在数据库中的信息,必须保证父节点的ID是唯一的,似乎Treeview1.items.count不行(添加或删除后节点数值就变了)。楼上仁兄的方法我要试一下。但是真的没有方法获得节点中全局始终唯一的信息吗?这个信息要能够在SAVE树时可以存储在数据库记录中,在LOAD树时再赋给生成的节点。如何实现呢,算法我已经想通了。
      

  6.   

    你存储在父结点中的纪录应该有信息是唯一的吧,参考TTreeNode的帮助,将它存在父节点的Data指针中,这不就有唯一信息了吗?
      

  7.   

    父节点内容不就是个Caption吗,有可能重复。还能有什么是唯一的呢?INDEX等属性不行,一添加删除就会改变。父节点中还有什么唯一信息呢?
      

  8.   

    做此类程序关键是你数据库表结构的设计如何???
    如果按下面的结构设计肯定没有问题!
    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 ---是否为明细
     )
      

  9.   

    有一种方法,不过没有试过,干脆而简洁,那就是用savetofile()把Treeview的结构生成的一个临时文件,然后把这个文件存入数据库!
    每次生成treeview时,再从数据库中导出这个文件,用loadfromfile()转载就行了,呵呵
    不过我还没有试过,不知道行不行!
      

  10.   

    我的办法:表结构:
    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]);...
    ...
      

  11.   

    另外一种方法:
    你的数据表设计可以如下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就要搜索全部节点,也避免使用递归,只不过编程是麻烦一点。
      

  12.   

    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&gt;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属性的同时对数据库进行修改、同一数据
      

  13.   

    刚才那个格式不对,重发一遍,不好意思我们都知道,开发者主要用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&gt;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;
      

  14.   

    自己定义一个记录类型,如:
    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;