Delphi中根据分类数据生成树形结构的最优方法

Posted jijm123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Delphi中根据分类数据生成树形结构的最优方法相关的知识,希望对你有一定的参考价值。

一、 引言:
    TreeView控件适合于表示具有多层次关系的数据。它以简洁的界面,表现形式清晰、形象,操作简单而深受用户喜爱。而且用它可以实现ListView、ListBox所无法实现的很多功能,因而受到广大程序员的青睐。
    树形结构在Windows环境中被普遍应用,但在数据库开发中面对层次多、结构复杂的数据,如何快速构造树形目录并实现导航呢?
    二、 实现关键技术:
    在Delphi提供的控件中包含了TreeView控件,但树的具体形成还需要用户编写代码。即它的列表项要在程序中动态添加,而这些列表数据通常由用户已录入在数据库表中,并作为数据库维护的一项内容。
    许多人用TreeView构造树形目录时,通常都使用多个嵌套循环,或递归算法,将代码“编织”成树。这样不但算法复杂,且运行效率低下,不是最佳选择。这里介绍的是基于编码结构的高效算法。该算法的主要优点是:程序短小精悍,运行效率高,能快速实现数据库的树形结构,可以适应任何复杂的层次数据,实现方法简单,且树的内容有变动时,无需更改程序行。
    算法的关键在于代码字典表中的代码字段的设计上一定要符合一定的代码设计要求,数据类型使用字符型。用户新增和修改代码时,必须有严格的约束,否则会导致程序出错。编码表的基本字段包括编码和编码名称,其编码规则是以数字、字母的位数来区分不同层次,同一层编码位数相同,层次按位数递增,程序通过判断编码位数来决定所在层数。
    本例程中编码结构是“222”,编码格式为 “XX XX XX”。例如:第一层为10~99两位,第二层为1001~1099四位,用户需要做的是先要设计树的结构和对应编码,并录入相应名称,然后程序在读取这些数据时形成树。本例程不需要用户自己进行编码,程序中自动实现各层编码,保证了代码格式的正确性。
    用TreeView导航表时,采用弹出式菜单,通过对话框操作数据表,同步更新树形控件和数据库。在所有操作中,树形控件不用重构,从而避免了重构时TreeView控件出现的闪动,也提高了程序的运行速度。
    本示例程序为了使大家看清楚数据表中记录是否同步更新,用TDBGrid控件显示当前数据库表中所有记录。下面给出范例程序和主要代码分析。
    三、 范例程序和主要代码分析:
    我们以建立一个城市名称的树形结构为例来说明如何快速生成树形并实现导航数据表。
    1. 先建立编码表:city_tree(bianma,cityname)。
    2. 新建一个项目,按默认保存。
    3. 新建一公共单元pubvar,在其中定义以下常量:
    Const
    cTreeCodeFormat = ‘222’;//编码格式为 XX XX XX
    cTreeMaxLevel = 3;//最大编码层次
    cTreeRootTxt = ‘城市’;//树根结点名称
    这样做为了提高程序的通用性,以后用于其他代码字典的维护时,只需要更改这些特征常量。
    4. 程序源代码:
    unit Unit1;
    interface
    uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, DB, DBTables, ImgList, ComCtrls , PubVar, Grids, DBGrids, Menus , StrUtils, StdCtrls;

    type
    TForm1 = class(TForm)
    Tree: TTreeView;
    ImageList1: TImageList;
    Table1: TTable;
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
    PopupMenu1: TPopupMenu;
    AddMenu: TMenuItem;
    DeleteMenu: TMenuItem;
    RenameMenu: TMenuItem;
    Query1: TQuery;
    DataSource2: TDataSource;
    procedure AddMenuClick(Sender: TObject);//点击增加子项菜单
    procedure RenameMenuClick(Sender: TObject);//点击重命名菜单
    procedure DeleteMenuClick(Sender: TObject); //点击删除该项菜单
    procedure FormCreate(Sender: TObject);
    procedure TreeClick(Sender: TObject);
    private
    { Private declarations }
    public
    { Public declarations }
    procedure LoadTree(treeDB:TDBDataSet);//初始化树
    procedure UpdateTree(curNode:TTreenode; nodeTxt:string; state:string);//更新树
    function GetNodeLevel(sFormat,sCode:string):integer;//获得节点层数
    function GetCurrChildNodeMaxMa(curNode:TTreenode):string;
    //获得当前节点子节点的最大编码
    function GetCurrentNodeBianma(curNode:TTreenode):string;//获得当前节点的编码
    procedure UpdateTable(bianma:string; cityname:string ;state:string); //更新数据库
    end;

    var
    Form1: TForm1;
    CurrentTreeNode: TTreeNode;
    // AddChildeTreeNode: TTreeNode;
    // flag:boolean; //用于标识是否需要在重命名树结点时更新数据

    implementation
    uses AddChildUnit,RenameItemUnit;
    {$R *.dfm}

    procedure TForm1.LoadTree(treeDB:TDBDataSet);//初始化树
    var
    curID,nodeTxt:string;
    level:integer;
    mynode:array[0..3] of TTreenode;
    begin //初始化变量
    Screen.Cursor:=crHourGlass;
    tree.Enabled:=True;
    tree.Items.Clear;
    level:=0 ;
    //设置根节点
    mynode[level]:=tree.items.add(Tree.Topitem,cTreeRootTxt);
    mynode[level].ImageIndex:=1;
    //遍历数据表,利用编码字段记录排序规律,依次添加树节点
    with treeDB do
    begin
    try
    if not Active then open;
    first;
    while not Eof do
    begin
    curID:=trim(FieldByName(’bianma’).AsString);
    nodeTxt:=curID+’-’+trim(FieldByName(’cityname’).AsString);
    level:=GetNodeLevel(cTreeCodeFormat,curID);
    //这里返回代码的层次数
    if level〉0 then
    begin
    //增加下一节点时,用添加子节点的方法可轻松实现节点间的层次关系
    //注意:这里的父节点是用当前节点的上一级节点mynode[level-1]
    mynode[level]:= tree.items.addchild(mynode[level-1],nodeTxt);
    mynode[level].ImageIndex:=2;
    end;
    next;//下一条记录
    end;
    finally
    close;
    End;
    mynode[0].expand(False);
    Screen.Cursor:=crDefault;
    end;
    end;

    function TForm1.GetNodeLevel(sFormat,sCode:string):integer;//获得节点层数
    var i,level,iLen:integer;
    begin
    level:=-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.UpdateTree(curNode:TTreenode; nodeTxt:string; state:string);
    Begin
    if UpperCase(state)=’ADD’ then
    begin
    curNode:=tree.items.addchild(curNode,nodeTxt);
    curNode.ImageIndex:=2;
    end;
    if UpperCase(state)=’DEL’ then
    begin
    curNode.DeleteChildren;
    curNode.delete;
    end;
    if UpperCase(state)=’EDI’ then curNode.Text:=nodeTxt;
    end;

    procedure TForm1.AddMenuClick(Sender: TObject);//点击增加子项菜单
    var AddChildText, AddTableText,maxbianma : string;
    begin
    AddChildForm.Label1.Caption:=’为“’+CurrentTreeNode.Text+’“增加子项 ’;
    if AddChildForm.ShowModal=mrOk then
    begin
    AddChildText:=AddChildForm.Edit1.Text;
    maxbianma:=GetCurrChildNodeMaxMa(CurrentTreeNode);
    if (CurrentTreeNode.Text=’城市’) and (maxbianma=’1000’) then
    maxbianma:=’11’//如果当前节点为根节点,且只有一个子节点,使增加节点编码为11
    else if CurrentTreeNode.Text=’城市’ then
    maxbianma:=IntToStr(StrToInt(LeftStr(maxbianma,2))+1)
    else
    maxbianma:=IntToStr(StrToInt(maxbianma)+1); //使子项编码自动增1
    if maxbianma〈〉’0’ then
    begin
    //增加树子层
    AddTableText:=maxbianma+’-’+AddChildText;
    UpdateTree(CurrentTreeNode,AddTableText,’add’); //更新树
    UpdateTable(maxbianma,AddChildText,’add’); //更新表
    ShowMessage(’添加成功!’);
    end
    else ShowMessage(’此层为最低子层,不能在该层增加子层’);
    AddChildForm.Edit1.Text:=’’;
    end;
    end;

    function TForm1.GetCurrChildNodeMaxMa(curNode:TTreenode):string;
    //获得当前节点子节点的最大编码
    var
    aSQL,maxbianma:string;
    li_pos:integer;
    begin
    li_pos:=pos(’-’,curNode.Text);
    if li_pos=7 then
    begin result:=’-1’; exit; end;
    if (li_pos=0) and (not(curNode.HasChildren)) then // 如果当前节点为根节点并且没有子节点
    begin
    result:=’9’; //使根节点第一个节点编码为10
    exit;
    end
    else begin
    aSQL:=’select bianma from city_tree where bianma like “’ + MidStr(curNode.Text, 1, li_pos-1) + ’%“’;
    Query1.UnPrepare;
    Query1.Close;
    Query1.SQL.Clear;
    Query1.SQL.Text:=aSQL;
    Query1.Prepare;
    Query1.ExecSQL;
    Query1.Active:=true;
    Query1.Last;
    maxbianma:=Query1.fieldbyname(’bianma’).AsString;
    if Query1.RecordCount=1 then//如果当前项没有子项
    maxbianma:=maxbianma+’00’;
    Query1.Active:=false;
    end;
    result:=maxbianma;
    end;

    procedure TForm1.RenameMenuClick(Sender: TObject);//点击重命名菜单
    var
    bianma:string;
    itemtext:string; //用于重命名时保存输入的Edit.text
    begin
    RenameItemForm.Label1.Caption:=’将“’+CurrentTreeNode.Text+’“命名为 ’;
    if RenameItemForm.ShowModal=mrOk then
    begin
    itemtext:=RenameItemForm.Edit1.Text;
    bianma:=GetCurrentNodeBianma(CurrentTreeNode);
    Table1.Locate(’bianma’,bianma,[]);
    UpdateTable(’’,itemtext,’edi’);
    itemtext:=bianma+’-’+itemtext;
    UpdateTree(CurrentTreeNode,itemtext,’edi’);
    ShowMessage(’重命名成功!’);
    end;
    end;
    //以下过程在新增、删除、修改记录时,同步更新数据库表
    procedure TForm1.UpdateTable(bianma:string; cityname:string ;state:string); //更新数据库
    begin
    if state=’add’ then
    begin
    Table1.Active:=True;
    Table1.Insert;
    Table1.SetFields([bianma,cityname]);
    Table1.Post;
    end;
    if state=’del’ then
    begin
    Table1.Active:=True;
    Table1.Locate(’bianma’,bianma,[]);
    Table1.Delete;
    end;
    if state=’edi’ then
    begin
    Table1.Edit;
    Table1.FieldByName(’cityname’).AsString:=cityname;
    Table1.Post;
    end;
    end;

    procedure TForm1.DeleteMenuClick(Sender: TObject); //点击删除该项菜单
    var
    bianma:string;
    begin
    CurrentTreeNode.expand(False);
    if CurrentTreeNode.Text=’城市’ then //如果当前节点为根节点
    begin
    ShowMessage(’不能删除根节点’);
    exit;//退出该过程
    end;
    if CurrentTreeNode.HasChildren then //如果当前节点具有子项
    begin
    ShowMessage(’请先删除其子项’);
    exit;//退出该过程
    end;
    if Application.MessageBox(PChar(’真的要删除“’+CurrentTreeNode.Text+’“这项吗?’),’警告’,MB_YESNO)=mrYES then
    begin
    bianma:=GetCurrentNodeBianma(CurrentTreeNode);
    UpdateTree(CurrentTreeNode,’’,’del’);
    UpdateTable(bianma,’’,’del’);
    ShowMessage(’删除成功!’);
    Table1.Refresh;//更新TBGrid控件中的显示
    Table1.Active:=true;
    CurrentTreeNode:=Form1.tree.selected;
    end;
    end;

    function TForm1.GetCurrentNodeBianma(curNode:TTreeNode):string;//获得当前节点的编码
    var
    li_pos:integer;
    bianma:string;
    begin
    li_pos:=pos(’-’,curNode.Text);
    bianma:=MidStr(curNode.Text,1,li_pos-1);
    result:=bianma;
    end;

    procedure TForm1.FormCreate(Sender: TObject);
    begin
    LoadTree(Table1);
    Table1.Active:=true;
    end;

    procedure TForm1.TreeClick(Sender: TObject);
    begin
    CurrentTreeNode:=Form1.tree.selected; //获得当前节点
    end;

    end.

    unit PubVar;
    interface
    uses
    SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, DbTables, StdCtrls, ExtCtrls, Buttons, Dialogs,Registry,Db, ComCtrls;

    const
    cTreeCodeFormat=’222’; //编码格式:xx xx xx
    cTreeMaxLevel=3; //最大编码(树节点)层次
    cTreeRootTxt=’城市’; //树的根节点名称
    implementation
    end.
    5. 编译运行结果图:(附后)


    四、 小结:
    本程序与编码法快速生成树形结构,通过TreeView控件直接操作数据表,实现了对表的数据导航。如果你想通过点击TreeView控件某项直接显示该项的相关信息,可在该程序的基础上进行修改。

    原文载于《电脑编程技巧与维护》2003年第一期P27

    以上源码在http://www.comprg.com.cn/tit1_rjxz.htm 可以下载得到!需要的各位可以去下载!

    程序运行结果图(稍后传上)

    2004-6-18 8:20:32
    查看评语???

    2004-6-18 8:22:34 续篇
    用Delphi实现Windows文件夹管理树
    李鹏 薛志东

    摘要:本文利用Windows名空间所提供的IShellFolder接口,用Delphi实现了文件夹管理树的生成。
    关键字:文件夹 接口 Delphi

    一、概述
    Windows95/98视觉感观上区别Windows3.1的一个重要方面就是大量采用了树形视图控件,资源管理器左侧的文件夹管理树便是如此,它将本地和网络上的文件夹和文件等资源以层次树的方式罗列出来,为用户集中管理计算机提供了极大便利,同时在外貌上也焕然一新。Delphi为我们提供了大量Windows标准控件,但遗憾的是在目录浏览方面却只提供了一个Windows3.1样式的DirectoryListBox(Delphi5的测试版也是如此),因此,在Delphi中实现Windows文件夹管理树对开发更“地道”的Windows程序有着重大意义。
    二、实现原理
    Windows文件夹管理树的实现实质上是对Windows名空间(Namespace)的遍历。名空间中每个文件夹都提供了一个IShellFolder接口,遍历名空间的方法是:
    1)调用SHGetDesktopFolder函数获得桌面文件夹的IShellFolder接口,桌面文件夹是文件夹管理树的根节点。
    2)再调用所获得的IShellFolder接口的EnumObjects成员函数列举出子文件夹。
    3)调用IShellFolder的BindToObject成员函数获得子文件夹的IShellFolder接口。
    4)重复步骤2)、3)列举出某文件夹下的所有子文件夹,只至所获得的IShellFolder接口为nil为止。
    下面解释将要用到的几个主要函数,它们在ShlObj单元中定义:
    1)function SHGetDesktopFolder(var ppshf: IShellFolder): HResult;
    该函数通过ppshf获得桌面文件夹的IShellFolder接口。
    2)function IShellFolder.EnumObjects(hwndOwner: HWND; grfFlags: DWORD;
    out EnumIDList: IEnumIDList): HResult;
    该函数获得一个IEnumIDList接口,通过调用该接口的Next等函数可以列举出IShellFolder接口所对应的文件夹的内容,内容的类型由grfFlags来指定。我们需要列举出子文件夹来,因此grfFlags的值指定为SHCONTF_FOLDERS。HwndOwner是属主窗口的句柄。
    3)function IShellFolder.BindToObject(pidl: PItemIDList; pbcReserved: Pointer;
    const riid: TIID; out ppvOut: Pointer): HResult;
    该函数获得某个子文件夹的IShellFolder接口,该接口由ppvOut返回。pidl是一个指向元素标识符列表的指针,Windows95/98中用元素标识符和元素标识符列表来标识名空间中的对象,它们分别类似于文件名和路径。需要特别指出的是:pidl作为参数传递给Shell API函数时,必须是相对于桌面文件夹的绝对路径,而传递给IShellFolder接口的成员函数时,则应是相对于该接口所对应文件夹的相对路径。pbcReserved应指定为nil,riid则应指定为IID_IShellFolder。
    其它函数可以查阅Delphi提供的《Win32 Programmer’s Reference》。
    三、程序清单
    下面的源代码在Windows98中实现,并在Windows2000测试版中测试无误(程序运行结果如图1所示),有兴趣的读者可以将其改写成Delphi组件,以备常用。
    unit BrowseTreeView;
    interface
    uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    ShlObj, ComCtrls;
    type
    PTreeViewItem = ^TTreeViewItem;
    TTreeViewItem = record
    ParentFolder: IShellFolder; // 接点对应的文件夹的父文件夹的IShellFolder接口
    Pidl, FullPidl: PItemIDList; // 接点对应的文件夹的相对和绝对项目标识符列表
    HasExpanded: Boolean; // 接点是否展开
    end;

    图1 程序运行结果
    TForm1 = class(TForm)
    TreeView1: TTreeView;
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure TreeView1Expanding(Sender: TObject; Node: TTreeNode;
    var AllowExpansion: Boolean);
    private
    FItemList: TList;
    procedure SetTreeViewImageList;
    procedure FillTreeView(Folder: IShellFolder; FullPIDL: PItemIDList; ParentNode: TTreeNode);
    end;
    var
    Form1: TForm1;
    implementation
    {$R *.DFM}
    uses
    ActiveX, ComObj, ShellAPI, CommCtrl;
    // 以下是几个对项目标识符进行操作的函数
    procedure DisposePIDL(ID: PItemIDList);
    var
    Malloc: IMalloc;
    begin
    if ID = nil then Exit;
    OLECheck(SHGetMalloc(Malloc));
    Malloc.Free(ID);
    end;
    function CopyItemID(ID: PItemIDList): PItemIDList;
    var
    Malloc: IMalloc;
    begin
    Result := nil;
    OLECheck(SHGetMalloc(Malloc));
    if Assigned(ID) then
    begin
    Result := Malloc.Alloc(ID^.mkid.cb + sizeof(ID^.mkid.cb));
    CopyMemory(Result, ID, ID^.mkid.cb + sizeof(ID^.mkid.cb));
    end;
    end;
    function NextPIDL(ID: PItemIDList): PItemIDList;
    begin
    Result := ID;
    Inc(PChar(Result), ID^.mkid.cb);
    end;
    function GetPIDLSize(ID: PItemIDList): Integer;
    begin
    Result := 0;
    if Assigned(ID) then
    begin
    Result := sizeof(ID^.mkid.cb);
    while ID^.mkid.cb 〈〉 0 do
    begin
    Inc(Result, ID^.mkid.cb);
    ID := NextPIDL(ID);
    end;
    end;
    end;
    function CreatePIDL(Size: Integer): PItemIDList;
    var
    Malloc: IMalloc;
    HR: HResult;
    begin
    Result := nil;
    HR := SHGetMalloc(Malloc);
    if Failed(HR) then Exit;
    try
    Result := Malloc.Alloc(Size);
    if Assigned(Result) then
    FillChar(Result^, Size, 0);
    finally
    end;
    end;
    function ConcatPIDLs(ID1, ID2: PItemIDList): PItemIDList;
    var
    cb1, cb2: Integer;
    begin
    if Assigned(ID1) then
    cb1 := GetPIDLSize(ID1) - sizeof(ID1^.mkid.cb)
    else
    cb1 := 0;
    cb2 := GetPIDLSize(ID2);
    Result := CreatePIDL(cb1 + cb2);
    if Assigned(Result) then
    begin
    if Assigned(ID1) then
    CopyMemory(Result, ID1, cb1);

    CopyMemory(PChar(Result) + cb1, ID2, cb2);
    end;
    end;
    // 将二进制表示的项目标识符列表转换成有可识的项目名
    function GetDisplayName(Folder: IShellFolder; PIDL: PItemIDList;
    ForParsing: Boolean): String;
    var
    StrRet: TStrRet;
    P: PChar;
    Flags: Integer;
    begin
    Result := ’’;
    if ForParsing then
    Flags := SHGDN_FORPARSING
    else
    Flags := SHGDN_NORMAL;
    Folder.GetDisplayNameOf(PIDL, Flags, StrRet);
    case StrRet.uType of
    STRRET_CSTR:
    SetString(Result, StrRet.cStr, lStrLen(StrRet.cStr));
    STRRET_OFFSET:
    begin
    P := @PIDL.mkid.abID[StrRet.uOffset - sizeof(PIDL.mkid.cb)];
    SetString(Result, P, PIDL.mkid.cb - StrRet.uOffset);
    end;
    STRRET_WSTR:
    Result := StrRet.pOleStr;
    end;
    end;
    function GetIcon(PIDL: PItemIDList; Open: Boolean): Integer;
    const
    IconFlag = SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SMALLICON;
    var
    FileInfo: TSHFileInfo;
    Flags: Integer;
    begin
    if Open then
    Flags := IconFlag or SHGFI_OPENICON
    else
    Flags := IconFlag;

    SHGetFileInfo(PChar(PIDL), 0, FileInfo, sizeof(TSHFileInfo), Flags);
    Result := FileInfo.iIcon;
    end;
    // 获得每个文件夹在系统中的图标
    procedure GetItemIcons(FullPIDL: PItemIDList; TreeNode: TTreeNode);
    begin
    with TreeNode do
    begin
    ImageIndex := GetIcon(FullPIDL, False);
    SelectedIndex := GetIcon(FullPIDL, True);
    end;
    end;
    // 获得系统的图标列表
    procedure TForm1.SetTreeViewImageList;
    var
    ImageList: THandle;
    FileInfo: TSHFileInfo;
    begin
    ImageList := SHGetFileInfo(PChar(’C:/’), 0, FileInfo,
    sizeof(TSHFileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
    if ImageList 〈〉 0 then
    TreeView_SetImageList(TreeView1.Handle, ImageList, 0);
    end;
    // 生成文件夹管理树
    procedure TForm1.FillTreeView(Folder: IShellFolder;
    FullPIDL: PItemIDList; ParentNode: TTreeNode);
    var
    TreeViewItem: PTreeViewItem;
    EnumIDList: IEnumIDList;
    PIDLs, FullItemPIDL: PItemIDList;
    NumID: LongWord;
    ChildNode: TTreeNode;
    Attr: Cardinal;
    begin
    try
    OLECheck(Folder.EnumObjects(Handle, SHCONTF_FOLDERS, EnumIDList));
    while EnumIDList.Next(1, PIDLs, NumID) = S_OK do
    begin
    FullItemPIDL := ConcatPIDLs(FullPIDL, PIDLs);
    TreeViewItem := New(PTreeViewItem);
    TreeViewItem.ParentFolder := Folder;
    TreeViewItem.Pidl := CopyItemID(PIDLs);
    TreeViewItem.FullPidl := FullItemPIDL;
    TreeViewItem.HasExpanded := False;
    FItemList.Add(TreeViewItem);
    ChildNode := TreeView1.Items.AddChildObject(ParentNode,
    GetDisplayName(Folder, PIDLs, False), TreeViewItem);
    GetItemIcons(FullItemPIDL, ChildNode);
    Attr := SFGAO_HASSUBFOLDER or SFGAO_FOLDER;
    Folder.GetAttributesOf(1, PIDLs, Attr);
    if Bool(Attr and (SFGAO_HASSUBFOLDER or SFGAO_FOLDER)) then
    if Bool(Attr and SFGAO_FOLDER) then
    if Bool(Attr and SFGAO_HASSUBFOLDER) then
    ChildNode.HasChildren := True;
    end;
    except
    // 你可在此处对异常进行处理
    end;
    end;
    procedure TForm1.FormDestroy(Sender: TObject);
    var
    I: Integer;
    begin
    try
    for I := 0 to FItemList.Count-1 do
    begin
    DisposePIDL(PTreeViewItem(FItemList[i]).PIDL);
    DisposePIDL(PTreeViewItem(FItemList[i]).FullPIDL);
    end;
    FItemList.Clear;
    FItemList.Free;
    except
    end;
    end;
    procedure TForm1.FormCreate(Sender: TObject);
    var
    Folder: IShellFolder;
    begin
    SetTreeViewImageList;
    OLECheck(SHGetDesktopFolder(Folder));
    FItemList := TList.Create;
    FillTreeView(Folder, nil, nil);
    end;
    procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode;
    var AllowExpansion: Boolean);
    var
    TVItem: PTreeViewItem;
    SHFolder: IShellFolder;
    begin
    TVItem := PTreeViewItem(Node.Data);
    if TVItem.HasExpanded then Exit;
    OLECheck(TVItem.ParentFolder.BindToObject(TVItem^.Pidl,
    nil, IID_IShellFolder, Pointer(SHFolder)));
    FillTreeView(SHFolder, TVItem^.FullPidl, Node);
    Node.AlphaSort;
    TVItem^.HasExpanded := True;
    end;
    end.

    2004-6-18 8:23:37 再续
    仅仅十几行代码实现对TreeView的遍历

    摘 要:对TreeView的遍历
    关键字:TreeView
    类 别:Delphi & IDE
    E-Mail:iloveyou9595@sina.com

    function TForm1.AllOverTreeView(node:TTreenode):TTreenode;
    begin
    while node〈〉nil do
    begin
    if node.HasChildren then
    begin
    node:=node.getFirstChild;
    allovertreeview(node);
    node:=node.Parent;
    end;
    if node.getNextSibling〈〉nil then
    node:=node.getNextSibling
    else
    exit;
    end;
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    var
    parentnode:TTreenode;
    begin
    parentnode:=Mytreeview.Items.GetFirstNode;
    AllOverTreeView(parentnode);
    end;
    ------------------------------------------------------

    遍历TreeView的方法有很多,我经过反复编程实现,上面是我用最少的代码实现TreeView的遍历。效果还不错。
    利用这个对所有节点的遍历,我们可以很方便的对所有节点进行各种操作。例如:统计每层节点的个数、对满足要求的节点进行操作、等等。

    投稿人:iloveyou9595 投稿日期:2003-3-26 23:51:00

    2004-6-18 8:24:32 再续...
    TreeView用法参考

    unit Main;

    interface

    uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    StdCtrls, ExtCtrls;

    type
    TForm1 = class(TForm)
    SearchBtn: TButton;
    DirectoryEdt: TMemo;
    PathEdt: TEdit;
    Label1: TLabel;
    Image1: TImage;
    procedure SearchBtnClick(Sender: TObject);
    procedure MakeTree;
    private
    { Private declarations }
    public
    { Public declarations }
    end;

    var
    Form1: TForm1;

    implementation

    {$R *.DFM}

    procedure TForm1.MakeTree;
    var
    Sr: TSearchRec;
    Err: Integer;
    FilePath: string;
    begin
    Err := FindFirst(’*.*’,$37,Sr);//$37为除Volumn ID Files外的所有文件
    // 如果找到文件
    while (Err = 0) do
    begin
    if Sr.Name[1] 〈〉 ’.’ then
    begin
    //找到文件
    if (Sr.Attr and faDirectory) = 0 then
    begin

    end;
    //找到子目录
    if (Sr.Attr and faDirectory) = 16 then
    begin
    FilePath := ExpandFileName(Sr.Name);
    DirectoryEdt.Lines.Add(FilePath);
    ChDir(Sr.Name);
    MakeTree;
    ChDir(’..’);
    end;
    end;
    //结束递归
    Err := FindNext(Sr);
    end;
    end;

    procedure TForm1.SearchBtnClick(Sender: TObject);
    begin
    DirectoryEdt.Lines.Clear;
    ChDir(PathEdt.Text);
    MakeTree;
    end;

    end.
    这是个递归搜文件的例子,

    摘 要:将数据表连接到TreeView中
    关键字:数据表 TreeView
    类 别:API
    CoDelphi.com版权所有,未经允许,不得进行任何形式转载

    procedure AddDataToTree(TreeView:TTreeView;DataSet:TDataSet)
    var
    TreeNodes:TTreeNodes
    TreeNode:array[0..100] of TTreeNode;
    i:Integer;
    begin
    DataSet.Close;
    DataSet.Open;
    TreeNodes=TTreeView.Items;
    if DataSet.RecordCount〉0 then
    begin
    DataSet.First;
    while not DataSet.Eof do
    begin
    TreeNode[0]=TreeNodes.Add(Nil,DataSet.Fields[0].AsString);
    for i=1 to DataSet.Fields.Count-1 do
    TreeNode[i]=TreeNodes.AddChild(TreeNode[i-1],DataSet.Fields[i].AsString);
    DataSet.Next;
    end;
    end;
    end;


    unit Unit1;

    interface

    uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, ImgList, StdCtrls, FileCtrl, ComCtrls;

    type
    TForm1 = class(TForm)
    DirTreeView: TTreeView;
    DriveComboBox1: TDriveComboBox;
    FileListBox1: TFileListBox;
    ImageList1: TImageList;
    procedure FormCreate(Sender: TObject);
    procedure DirTreeViewExpanding(Sender: TObject; Node: TTreeNode;
    var AllowExpansion: Boolean);
    private
    { Private declarations }
    public
    { Public declarations }
    end;

    var
    Form1: TForm1;

    implementation

    {$R *.dfm}

    procedure TForm1.FormCreate(Sender: TObject);
    var
    FirstNode,DirNode : TTreeNode;
    ItemCount,Index:integer;
    Itemstr:string;
    begin
    ItemCount:= DriveComboBox1.Items.Count; //所有驅動器的個數
    FirstNode := DirTreeView.Items.GetFirstNode;
    for index := 0 to ItemCount -1 do
    begin
    ItemStr:= DriveComboBox1.Items[index];
    ItemStr:= copy(ItemStr,1,pos(’:’,ItemStr)) ; //?得驅動器的名稱(比如C/D)
    DirNode := DirTreeView.Items.AddChild(FirstNode, ItemStr );
    DirNode.HasChildren := true;
    DirNode.ImageIndex := 0;
    DirNode.SelectedIndex := 1;
    end;
    end;

    procedure TForm1.DirTreeViewExpanding(Sender: TObject; Node: TTreeNode;
    var AllowExpansion: Boolean);
    var
    DirNode : TTreeNode;
    ItemCount,Index,level,icount:integer;
    Itemstr,strPath:string;
    begin
    if node.Count = 0 then
    begin
    icount:=0;
    level:=node.Level ;
    dirnode:=node;
    strPath:=node.Text+’/’ ;
    while level〈〉0 do
    begin
    strPath:=dirnode.Parent.Text+’/’+strpath;
    dirnode:=dirnode.parent;
    level :=level -1;
    end;
    FileListBox1.Clear ;
    FileListBox1.Directory := strpath;
    ItemCount:= FileListBox1.Items.Count;
    for index:=0 to ItemCount -1 do
    begin
    itemstr:=filelistbox1.items[index];
    itemstr:= copy(ItemStr,2,pos(’]’,ItemStr)-2) ;
    if (itemstr〈〉’.’) and (itemstr〈〉’..’) then
    begin
    DirNode := DirTreeView.Items.AddChild(Node,itemstr );
    DirNode.HasChildren :=true;
    DirNode.ImageIndex := 0;
    DirNode.SelectedIndex := 1;
    icount:=icount+1;
    end;
    if icount = 0 then
    Node.HasChildren := false;
    end;
    end;
    end;

    end.

    procedure TMain.CreateTree(QuerySource:TADOQuery;NodeParent:TTreeNode;treeview1:ttreeview);
    var
    pstr1, pstr2 : ^string;
    NodeTemp : TTreeNode;
    begin
    pstr1 := NodeParent.Data;
    with QuerySource do
    begin
    close;
    sql.Clear;
    sql.Text:=’SELECT key,xcode,xname FROM xzdm WHERE parent = ’ + ’’’’ + pstr1^ + ’’’’;
    open;
    if isempty then exit;
    NodeTemp := nil;
    while not eof do
    begin
    new(pstr2);
    pstr2^ := FieldByName(’key’).AsString;
    NodeTemp := TreeView1.Items.AddChildObject(NodeParent,
    trim(FieldByName(’xname’).AsString)+’(’+fieldbyname(’xcode’).AsString+’)’, pstr2);
    Next;
    end;
    end;
    while NodeTemp 〈〉 nil do
    begin
    CreateTree(QuerySource, NodeTemp,treeview1);
    NodeTemp := Nodetemp.getPrevSibling;
    end;
    end;

    procedure TMain.RootTree(treeview1:ttreeview);
    var
    NodeTemp : TTreeNode;
    pstr : ^string;
    Query:TADOQuery;
    begin
    Query:=TADOQuery.Create(self);
    query.Connection:=BgConnection;
    try
    Treeview1.Items.BeginUpdate;
    with query do
    begin
    SQL.Text :=’select top 1 * from xzdm ’;
    open;
    if isempty then exit;
    NodeTemp := nil;
    while not eof do
    begin
    new(pstr);
    pstr^ := FieldByName(’key’).AsString;
    NodeTemp :=treeview1.Items.AddObject(nil,
    trim(FieldByName(’xname’).AsString)+’(’+fieldbyname(’xcode’).AsString+’)’, pstr);
    Next;
    end;
    end;
    while NodeTemp 〈〉 nil do
    begin
    CreateTree(Query, NodeTemp,treeview1);
    NodeTemp:=NodeTemp.getPrevSibling;
    end;
    treeview1.Items.EndUpdate;
    finally
    Query.Free;
    end;
    end;

    var
    node1:Ttreenode;
    begin
    node1:=TreeView1.items.AddChild(node,’收件箱’);//建一个节点
    node1.ImageIndex:=86;//节点图象,要加imagelist控件
    node1.SelectedIndex:=92;
    node1.Data:=pointer(0);//重要,node1.data可以存入你有用ID
    end;

    其实看在线帮助是最好的了!另外就是要多用,Treeview我用的比较多,有什么问题可以问具体点!
    楼上的说的不错,要注意的是Node节点中的.data是一个指针,要小心使用!

    Delphi帮助文件里是这样写的:
    Set ToolTips to True to specify that items in the tree view control have tooltips (Help Hints

    当前的节点为: TreeView1.Selected;
    他的字节点:child,父节点:Parent
    其中的TreeNode的类型下可以保存一个指针类型的值;

    var
    CurItem: TTreeNode;
    begin
    CurItem := TreeView1.Items.GetFirstNode;
    while CurItem 〈〉 nildo
    begin
    ListBox1.Items.Add(CurItem.Text);
    CurItem := CurItem.GetNext;
    end;
    end

    用GetFirstNode,GetNext会比较快

    怎么知道我选中的是几级节点呀?
    procedure TForm1.Button1Click(Sender: TObject);
    begin
    showmessage(vartostr(TreeView1.selected.level));
    end;

    在节点展开的事件中如下:
    var sID:string;
    Node,NewNode;TTreeNode;
    begin
    Node := TreeView1.Selected;
    sID := PCHAR(Node.Data);
    Query1.Close;
    Query1.SQL.Clear;
    Query1.SQL.Add(’select * from mytbl where ParentID=’ + sID);
    Query1.Open;
    Node.Items.Clear; //清除以下所有节点
    While note Query1.Eof do
    begin
    NewNode:= TTreeView1.Items.AddChild(Node,Query1.FieldByName(’mc’).AsString);
    NewNode.ImageIndex := ***;
    Node.Data := PChar(Query1.FieldByName(’ID’).AsString); //注意保留索引值
    Query1.Next;
    end;


    另外在TreeView的节点清除时注意释放内存。

    在节点展开的事件中如下:
    var sID:string;
    NewNode;TTreeNode;
    begin
    sID := PCHAR(Node.Data);
    Query1.Close;
    Query1.SQL.Clear;
    Query1.SQL.Add(’select * from mytbl where ParentID=’ + sID);
    Query1.Open;
    Node.Items.Clear; //清除以下所有节点
    While note Query1.Eof do
    begin
    NewNode:= TTreeView1.Items.AddChild(Node,Query1.FieldByName(’mc’).AsString);
    NewNode.ImageIndex := ***;
    Node.Data := PChar(Query1.FieldByName(’ID’).AsString); //注意保留索引值
    Query1.Next;
    end;

    以上程序仅供叁考,没做测试。

    PNodeRec = 你定义的record 的变量;

    procedure loadRootNode
    var pID:integer; aNode: TTreeNode;
    Item : PNodeRec;
    begin
    Query1.close; Query1.SQL.clear;
    Query1.SQL.add(’select * from T where parentID=0’);
    Query1.open;

    where not Query1.eof do
    begin
    pID:=Query1.fieldByName(’id’).asInteger;
    //(1)加载一个根节点
    ... ...
    //aNode:=TV.Items.AddObject(nil, Item^.Name, Item);

    //(2)加载此根节点下的所有子节点
    loadChilds(pID,aNode);
    Query1.next;
    end;
    end;

    prodedure loadChilds(pID:integer;pNode:TTreeNode);
    var cpID:integer; aNode: TTreeNode;
    Item : PNodeRec;
    begin
    //(1)加载子节点
    Query2.close; Query2.SQL.clear;
    Query2.SQL.add(’select * from T where parentID=’+intToStr(pID));
    Query2.open;
    where not Query2.eof do
    begin
    //载入子节点
    ... ... //TV.Items.AddChildobject(pNode,Item^.Name,item);
    Query2.next;
    end;
    //(2)递归载入子节点的子节点
    for i:=0 to pNode.Count -1 do
    begin
    LoadChild(PNodeRec(pNode.Item[i].data)^.parentID,pNode.item[i]);
    end;
    end;

    大家是从算法上来说,我来从GUI方面来说。

    TreeList1.Items.BeginUpdate;
    ....执行添加代码
    TreeList1.Items.EndUpdate;

    哎呀,来晚了!
    Eastunfail(恶鱼杀手)对!浪费时间的部分主要实在绘制上,不用BeginUpdate和用BeginUpdate在数据量较大时,差着“十万八千里”呢!算法当然也很重要,但要是从最快的角度将,影响最大的还是BeginUpdate和EndUpdate(就是等数据全部加载完毕再进行绘制)。我有亲身体会...

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    //无限层数:数据严格按照(层(在TreeView上),ParentID,ID,Text,图标序号)顺序排序************
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    procedure DataSetToTreeView(DataSet: TDataSet; var TreeView: TTreeView; var TreeList: TStringList); overload;
    var IsAvtive: Boolean;
    TempIndex: integer;
    TempNode: TTreeNode;
    begin
    DataSet.DisableControls;
    IsAvtive := DataSet.Active;
    if not IsAvtive then DataSet.Open;
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    TreeList.Clear;
    TreeList.Sorted := true;
    DataSet.First;
    while not DataSet.Eof do
    begin
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    if TreeList.Find(DataSet.Fields[1].AsString, TempIndex)
    then TempNode := TreeView.Items.AddChild(TreeList.Objects[TempIndex] as TTreeNode, DataSet.Fields[3].AsString)
    else TempNode := TreeView.Items.AddChild(TreeView.Items[0], DataSet.Fields[3].AsString);
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    TempNode.ImageIndex := DataSet.Fields[4].AsInteger;
    TempNode.SelectedIndex := DataSet.Fields[4].AsInteger;
    TempNode.Data := Pointer(TreeList.Strings[TreeList.AddObject(DataSet.Fields[2].AsString, TempNode)]);
    DataSet.Next;
    end;
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    DataSet.Active := IsAvtive;
    DataSet.EnableControls;
    end;

    速度奇快,利用了TStringList的AddObject

    如果你的数据量特大,树结构又不经常变化,可以考虑保存为文本文件,第二次加载此文件,速度更快。 

以上是关于Delphi中根据分类数据生成树形结构的最优方法的主要内容,如果未能解决你的问题,请参考以下文章

决策树(decision tree)

统计学习方法 李航 决策树

决策树的部分理解

基于树的模型的最优超参数调整

统计学习方法李航学习笔记

李航统计学习方法(第二版):决策树简介