You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过FTP填充Delphi的Treeview并修复递归回溯问题

问题:Delphi中通过IdFTP填充TTreeView时递归回溯失败,无法完整展示远程目录结构

场景

想要模仿本地目录填充TreeView的逻辑,通过IdFTP从远程服务器根目录开始获取目录结构,填充Delphi的TTreeView,实现类似FileZilla的目录树效果。参考瑞士Delphi中心的本地目录代码(可正常展示本地结构),将FindFirst()/FindNext()替换为IdFTP.ChangeDir(Directory)和IdFTP.List进行修改。

现有问题

  1. 递归无法正常回溯:遍历完/cpanel/cashes/config目录后,程序直接退出,不会返回根目录遍历其他子目录
  2. 未显示所有顶层文件夹,怀疑和IdFTP.List的返回顺序有关
  3. 不知道如何在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;

解决方案

问题根源分析

  1. 递归回溯失败:每次递归调用GetDirectories后,没有切换回上级目录,导致FTP客户端停留在子目录中,后续遍历逻辑错误
  2. 根目录未显示:初始调用时没有手动添加根节点,直接开始遍历子目录
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 06:10:58