如何通过FTP填充Delphi的Treeview并修复递归回溯问题
问题:Delphi中通过IdFTP填充TTreeView时递归回溯失败,无法完整展示远程目录结构
场景
想要模仿本地目录填充TreeView的逻辑,通过IdFTP从远程服务器根目录开始获取目录结构,填充Delphi的TTreeView,实现类似FileZilla的目录树效果。参考瑞士Delphi中心的本地目录代码(可正常展示本地结构),将FindFirst()/FindNext()替换为IdFTP.ChangeDir(Directory)和IdFTP.List进行修改。
现有问题
- 递归无法正常回溯:遍历完
/cpanel/cashes/config目录后,程序直接退出,不会返回根目录遍历其他子目录 - 未显示所有顶层文件夹,怀疑和
IdFTP.List的返回顺序有关 - 不知道如何在TreeView中显示根目录(
/)
目前已注释文件显示逻辑,仅需展示文件夹。
预期效果vs实际效果
- 预期:类似FileZilla的完整目录树展示
- 实际:仅显示部分子目录
我的代码
procedure TForm2.Button1Click(Sender: TObject); var StartingDir : string; begin TreeView1.Items.BeginUpdate; try StartingDir := '/'; Screen.Cursor := crHourGlass; TreeView1.Items.Clear; FTPconnect; //连接远程服务器的自定义过程 GetDirectories(TreeView1, StartingDir, nil, True); FTPDisconnect; //断开连接的自定义过程 finally TreeView1.Items.EndUpdate; Screen.Cursor := crDefault; end; end; procedure TForm2.GetDirectories(Tree: TTreeView; Directory: string; Item: TTreeNode; IncludeFiles: Boolean); var ItemTemp: TTreeNode; DirItemType : TIdDirItemType ; Filename , NewStartingDirectory: string; i : Integer; begin Tree.Items.BeginUpdate; IdFTP.ChangeDir(Directory); IdFTP.List; //获取远程文件夹内容 i:=0; repeat DirItemType := IdFTP.DirectoryListing[I].ItemType; Filename := IdFTP.DirectoryListing[I].FileName; If (DirItemType = ditDirectory) and (Filename <> '.') and (Filename <> '..')then begin if DirItemType = ditDirectory then Item := Tree.Items.AddChild(Item, Filename); ItemTemp := Item.Parent; if Directory = '/' then NewStartingDirectory := Directory + Filename else NewStartingDirectory := Directory + '/' +Filename; GetDirectories(Tree, NewStartingDirectory, Item, IncludeFiles); Item := ItemTemp; end else if IncludeFiles then begin //已注释,仅展示目录 // if (Filename <> '.') and (Filename <> '..') then // Tree.Items.AddChild(Item, Filename); end; inc(i); until i = IdFTP.DirectoryListing.Count; Tree.Items.EndUpdate; end;
参考代码(瑞士Delphi中心)
procedure TForm1.Button1Click(Sender: TObject); var Node: TTreeNode; Path: string; Dir: string; begin Dir := 'c:\temp'; Screen.Cursor := crHourGlass; TreeView1.Items.BeginUpdate; try TreeView1.Items.Clear; GetDirectories(TreeView1, Dir, nil, True); finally Screen.Cursor := crDefault; TreeView1.Items.EndUpdate; end; end; procedure TForm1.GetDirectories(Tree: TTreeView; Directory: string; Item: TTreeNode; IncludeFiles: Boolean); var SearchRec: TSearchRec; ItemTemp: TTreeNode; begin Tree.Items.BeginUpdate; if Directory[Length(Directory)] <> '\' then Directory := Directory + '\'; if FindFirst(Directory + '*.*', faDirectory, SearchRec) = 0 then begin repeat if (SearchRec.Attr and faDirectory = faDirectory) and (SearchRec.Name[1] <> '.') then begin if (SearchRec.Attr and faDirectory > 0) then Item := Tree.Items.AddChild(Item, SearchRec.Name); ItemTemp := Item.Parent; GetDirectories(Tree, Directory + SearchRec.Name, Item, IncludeFiles); Item := ItemTemp; end else if IncludeFiles then if SearchRec.Name[1] <> '.' then Tree.Items.AddChild(Item, SearchRec.Name); until FindNext(SearchRec) <> 0; FindClose(SearchRec); end; Tree.Items.EndUpdate; end;
解决方案
问题根源分析
- 递归回溯失败:每次递归调用
GetDirectories后,没有切换回上级目录,导致FTP客户端停留在子目录中,后续遍历逻辑错误 - 根目录未显示:初始调用时没有手动添加根节点,直接开始遍历子目录
BeginUpdate滥用:递归过程中反复调用Tree.Items.BeginUpdate/EndUpdate,会干扰TreeView的更新逻辑,应该只在最外层调用一次
修正后的代码
1. 主调用逻辑(添加根节点)
procedure TForm2.Button1Click(Sender: TObject); var StartingDir : string; RootNode: TTreeNode; begin TreeView1.Items.BeginUpdate; try StartingDir := '/'; Screen.Cursor := crHourGlass; TreeView1.Items.Clear; FTPconnect; // 添加根节点(显示为"/") RootNode := TreeView1.Items.Add(nil, '/'); // 从根目录开始遍历子目录 GetDirectories(TreeView1, StartingDir, RootNode, False); FTPDisconnect; finally TreeView1.Items.EndUpdate; Screen.Cursor := crDefault; end; end;
2. 递归遍历逻辑(修复回溯)
procedure TForm2.GetDirectories(Tree: TTreeView; Directory: string; Item: TTreeNode; IncludeFiles: Boolean); var DirItem: TIdDirItem; NewNode: TTreeNode; NewDir: string; begin // 先切换到当前目录,获取内容 IdFTP.ChangeDir(Directory); IdFTP.List(nil, False); // 第二个参数False表示只获取常规目录和文件 for DirItem in IdFTP.DirectoryListing do begin if DirItem.ItemType = ditDirectory then begin if (DirItem.FileName = '.') or (DirItem.FileName = '..') then Continue; // 添加当前目录节点 NewNode := Tree.Items.AddChild(Item, DirItem.FileName); // 构造新目录路径 if Directory = '/' then NewDir := Directory + DirItem.FileName else NewDir := Directory + '/' + DirItem.FileName; // 递归遍历子目录 GetDirectories(Tree, NewDir, NewNode, IncludeFiles); // 回溯:切回上级目录,保证后续遍历正常 IdFTP.ChangeDir(Directory); end else if IncludeFiles then begin // 如需显示文件,取消注释 // Tree.Items.AddChild(Item, DirItem.FileName); end; end; end;
关键修改点
- 添加了根节点的创建,直接显示
/ - 递归完成后调用
IdFTP.ChangeDir(Directory)切回上级目录,保证回溯正常 - 使用
for-in循环遍历DirectoryListing,比repeat-until更直观且不易出错 - 移除了递归内部的
BeginUpdate/EndUpdate,只在最外层主调用中使用,避免更新冲突 - 调用
IdFTP.List(nil, False)明确只获取常规目录和文件,过滤不必要的处理
可选:TListView实现(详情模式)
如果想用TListView展示目录结构(类似资源管理器的详情视图),可以参考以下代码:
procedure TForm2.FillListViewWithFTPDir(const ADir: string; AParentItem: TListItem); var DirItem: TIdDirItem; ListItem: TListItem; NewDir: string; begin IdFTP.ChangeDir(ADir); IdFTP.List(nil, False); for DirItem in IdFTP.DirectoryListing do begin if DirItem.ItemType = ditDirectory then begin if (DirItem.FileName = '.') or (DirItem.FileName = '..') then Continue; ListItem := ListView1.Items.Add; ListItem.Caption := DirItem.FileName; ListItem.ImageIndex := 0; // 假设ImageList中有文件夹图标 ListItem.Data := Pointer(StrNew(PChar(ADir + '/' + DirItem.FileName))); // 递归填充子目录(可选,若需要展开层级) FillListViewWithFTPDir(ADir + '/' + DirItem.FileName, ListItem); IdFTP.ChangeDir(ADir); end else begin // 显示文件详情 ListItem := ListView1.Items.Add; ListItem.Caption := DirItem.FileName; ListItem.SubItems.Add(IntToStr(DirItem.Size)); ListItem.SubItems.Add(DirItem.ModifiedDate.ToString); ListItem.ImageIndex := 1; // 文件图标 end; end; end; // 主调用 procedure TForm2.Button2Click(Sender: TObject); begin ListView1.Items.BeginUpdate; try Screen.Cursor := crHourGlass; ListView1.Items.Clear; FTPconnect; FillListViewWithFTPDir('/', nil); FTPDisconnect; finally ListView1.Items.EndUpdate; Screen.Cursor := crDefault; end; end;
注意:使用TListView时需要提前设置ViewStyle := vsReport,并添加对应的列(名称、大小、修改时间等),同时关联ImageList存放文件夹和文件图标。
内容的提问来源于stack exchange,提问作者user2834566
相关产品推荐
相关产品推荐

