`
ivfh
  • 浏览: 59947 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

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

阅读更多

新一篇: 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;
    //~~~~~~~~~~~~

分享到:
评论

相关推荐

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

    在本文中,我们将深入探讨如何在Delphi中根据分类数据生成树形结构,特别是利用TreeView组件来实现这一目标。这在数据库应用、文件系统显示、组织结构展示等方面都有广泛应用。 首先,我们要理解Delphi中的TreeView...

    自动生成树形结构

    在IT领域,尤其是在软件开发中,树形结构是一种常见的数据表示方式,特别是在处理层次关系时。"自动生成树形结构"这一技术主要应用于构建可视化界面,例如文件系统、组织架构或者数据库表之间的关系展示。本篇文章将...

    Delphi中快速实现数据库树形结构并实现Treeview导航表数据.rar

    "Delphi中快速实现数据库树形结构并实现Treeview导航表数据"是一个实用的技术主题,它涉及如何将数据库中的层级关系数据映射到图形化的Treeview组件上,使用户能够通过树形结构来浏览和操作数据。下面我们将深入探讨...

    Delphi中快速实现数据库树形结构并实现Treeview导航表数据

    在Delphi编程环境中,开发人员经常需要将数据库中的数据以树形结构展示,这有助于用户直观地理解层次关系。在本教程中,我们将探讨如何快速地在Delphi中实现这样的功能,利用TreeView组件来导航数据库表的数据。我们...

    Delphi DBTree读取数据库生成树形菜单.rar

    Delphi基于DBTree方法生成树形菜单,菜单数据来自数据库。本树形菜单控件的数据库使用了Access,每次从数据库读取数据后,自动生成节点,节点自动编号,可一次删除节点成母节点,在一些大型的软件系统中,树形菜单的...

    delphi 树型控件自动根据数据集生成树型结构

    本文将详细讲解如何在 Delphi7 中使用树型控件,并自动根据数据集生成树型结构。 首先,理解 TTreeView 控件的基本操作。TTreeView 提供了一个可视化的组件,用于展示具有父节点和子节点的关系的数据。每个节点表示...

    无限库树形结构生成

    在Delphi中,我们可以使用TTreeview控件来创建基本的树形结构,但要实现无限级别的树形结构,通常需要自定义逻辑和递归算法。无限树形结构意味着节点可以有任意数量的子节点,且这些子节点也可以继续扩展出更多的子...

    DELPHI实现数据库目录树生成

    1. **TTreeView** 控件:Delphi提供的TTreeView控件是构建目录树视图的基本组件,它可以显示树形结构的数据,支持添加、删除、展开和折叠节点等操作。 2. **数据库访问组件**:如DBExpress、ADO等,用于连接和查询...

    delphi树形结构源码

    在Delphi中,"树形结构"通常指的是控件TTreeView,它是用户界面中的一个重要元素,用于展示层次化的数据。这种数据结构在很多场景下都非常实用,比如文件系统浏览、组织结构展示等。 在描述中提到的"delphi树形结构...

    Cxgrid做树形

    2. **创建数据源**:树形结构的数据通常包含父节点和子节点,因此你需要一个支持层级数据的数据源。这可以是自定义的类结构,或者是数据库中的表,通过关系字段表示父子关系。例如,你可以使用`DataSet`或`DataTable...

    《Delphi算法与数据结构》全书的源代码

    在《Delphi算法与数据结构》中,你将找到各种类型的算法实现,如排序算法(快速排序、冒泡排序、插入排序等)、搜索算法(二分查找、广度优先搜索、深度优先搜索等)以及图论中的算法(最短路径算法、最小生成树等)...

    delphi树形结构地域管理

    1. **TTreeView组件**:Delphi中用于显示树形结构的主要控件是TTreeView。我们可以使用TTreeView来创建和维护一个节点层次结构,每个节点代表一个地域,如国家、省份、城市等。 2. **数据绑定**:为了将地域数据与...

    delphi生成最小生成树

    delphi比啊摹写生成最小生成树,给出点 就可以生成最小胜创术

    delphi文件XML生成树

    本篇文章将深入探讨如何在Delphi中实现XML树的生成。 首先,我们需要了解Delphi中处理XML的主要组件——TXMLDocument。TXMLDocument是Indy库中的一个组件,它可以用来创建、加载和保存XML文档。在设计阶段,将...

    Delphi使用递归算法读取数据库生成树形菜单.rar

    Delphi使用递归算法读取数据库生成树形菜单,Delphi创建一个动态的树叉菜单,菜单内容基于递归法从数据库读取而来,请参考数据库显示,看数据库内的内容,这里仅仅给了附表和数据库中的数据创建树的示例,至于向...

    Delphi 从数据库生成目录树

    在 Delphi 开发环境中,构建一个能够从数据库中动态生成目录树的应用程序是一项常见的任务,尤其是在数据管理和文件系统操作相关的项目中。这个标题"Delphi 从数据库生成目录树"暗示我们将利用 Delphi 的组件和...

    delphi的二维码生成实例

    以下是一个简化的步骤,展示了如何在Delphi中使用ZXing库生成二维码: 1. **引入ZXing库**:首先,你需要下载ZXing.Delphi库,这是一个为Delphi编译器优化的版本。将库的源代码导入到你的项目中,确保所有依赖项都...

    Delphi中从Excel导入数据的通用方法

    总之,Delphi中从Excel导入数据的通用方法主要依赖于第三方组件,如JvExcel。通过这些组件,我们可以方便地访问Excel文件中的数据,并将其集成到Delphi应用程序中。当然,还有其他库和方法,如使用Microsoft的OLE ...

    delphi 数据结构与算法

    在Delphi中,这些数据结构可以通过内置的TArray、TList、TStack、TQueue等类进行实现,也可以自定义结构体或类来构建更复杂的数据结构。例如,数组提供了固定大小的连续内存空间,适合快速访问;链表则通过节点间的...

    delphi算法与数据结构源码

    在Delphi中,数据结构包括数组、链表、栈、队列、树(如二叉树、AVL树、红黑树等)、图等。这些数据结构的选择和实现方式直接影响到程序的性能和可维护性。例如: 1. **数组**:是最基本的数据结构,提供了固定大小...

Global site tag (gtag.js) - Google Analytics