如何在Delphi中通过所有子目录搜索文件

9
我可以帮助您进行翻译。以下是您需要翻译的内容:

我已经在Delphi中实现了这段代码,它将搜索给定的文件或名称,但会省略搜索所有子目录。如何解决这个问题?

代码:

 if FindFirst(filePath,faAnyFile,searchResult)=0 then
  try
    repeat
    lbSearchResult.Items.Append(searchResult.Name);

    until FindNext(searchResult)<>0
  except
  on e:Exception do
  ShowMessage(e.Message);
  end; //try ends
  FindClose(searchResult); 
5个回答

27

使用Delphi XE及更高版本,您可以查看IOUtils.pas:

TDirectory.GetFiles('C:\', '*.dll', TSearchOption.soAllDirectories);

1
我使用Delphi 2009已经很多年了。当我升级到XE2,然后再到XE8时,没有意识到这些是新增的。+1 - lkessler

12

如果您不需要线程,最简单的方法是:

procedure TForm1.AddAllFilesInDir(const Dir: string);
var
  SR: TSearchRec;
begin
  if FindFirst(IncludeTrailingBackslash(Dir) + '*.*', faAnyFile or faDirectory, SR) = 0 then
    try
      repeat
        if (SR.Attr and faDirectory) = 0 then
          ListBox1.Items.Add(SR.Name)
        else if (SR.Name <> '.') and (SR.Name <> '..') then
          AddAllFilesInDir(IncludeTrailingBackslash(Dir) + SR.Name);  // recursive call!
      until FindNext(Sr) <> 0;
    finally
      FindClose(SR);
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ListBox1.Items.BeginUpdate;
  AddAllFilesInDir('C:\Users\Andreas Rejbrand\Documents\Aweb');
  ListBox1.Items.EndUpdate;
end;

特别注意两个重要事项:FindClose函数在finally部分被调用,而所有对Items.Add的调用都位于Items.BeginUpdateItems.EndUpdate之间。 - Andreas Rejbrand
值得注意的是,这种方法使用了递归。如果没有递归的概念,对于提出这个问题的人来说,这一点可能并不明显。我会在代码中添加注释,指出这一点。 - Chris Thornton
你能解释一下这段代码吗?像 FindFirst(IncludeTrailingBackslash(Dir) + '.' 和 if (SR.Attr and faDirectory) = 0 这些小细节是什么意思? - CyprUS
@sunandan: (1) IncludeTrailingBackslash只是一个函数,如果字符串的结尾没有\字符,则会在其末尾添加该字符。如果DirC:\Users,那么我们将搜索C:\Users*.*,这是无效的,但是通过使用IncludeTrailingBackslash,我们将搜索C:\Users\*.*,这是有效的。(2)SR.Attr是一个位域,即每个数字都是0或1的二进制数,它告诉您一些有趣的信息。从右边数第五个数字是0或1,具体取决于找到的文件是否为... - Andreas Rejbrand
目录或非目录。faDirectory在二进制中为10000。因此,当找到的文件是目录时,SR.Attr and faDirectory是非零的。因此,if (SR.Attr and faDirectory) = 0 then应该被理解为“如果找到的文件不是目录”,在这种情况下,我们将文件添加到列表框中。另一方面,如果文件是一个目录(但不是每个目录中都存在的“..”或“.”目录,它们分别表示上一个和当前目录),我们再次使用这个新目录调用函数AddAllFilesInDir - Andreas Rejbrand

4

最简单的方法是:

uses
  DSiWin32;

DSiEnumFilesToStringList('c:\somefolder\file.name', 0, ListBox1.Items, true, true);

DSiWin32 是一个免费的 Delphi 库。


2
否则,你应该开始使用它 :) - gabr

1
当我需要做像重载保护方法这样的技巧时,我倾向于使用通用解决方案来解决问题……我对类进行了一些黑客攻击。
以下是如何使用TDirectoryListbox进行操作的方法。
在每个需要使用此“黑客”TDirectoryListbox的表单上,只需将unitTDirectoryListbox_WithHiddenAndSystemFolders添加到接口uses中,这样该表单就会使用“黑客”TDirectoryListbox。
在您的项目文件夹中创建一个名为unitTDirectoryListbox_WithHiddenAndSystemFolders.pas的文件。
将以下文本放入该文件中(稍后将解释我所做的内容):
unit unitTDirectoryListbox_WithHiddenAndSystemFolders;

interface

uses
    Windows
   ,SysUtils
   ,Classes
   ,FileCtrl
   ;

type TDirectoryListbox=class(FileCtrl.TDirectoryListbox)
   private
     FPreserveCase:Boolean;
     FCaseSensitive:Boolean;
   protected
     function ReadDirectoryNames(const ParentDirectory:String;DirectoryList:TStringList):Integer;
     procedure BuildList;override;
   public
     constructor Create(AOwner:TComponent);override;
     destructor Destroy;override;
     property PreserveCase:Boolean read FPreserveCase;
     property CaseSensitive:Boolean read FCaseSensitive;
end;

implementation

constructor TDirectoryListbox.Create(AOwner:TComponent);
begin
     inherited Create(AOwner);
end;

destructor TDirectoryListbox.Destroy;
begin
     inherited Destroy;
end;

function TDirectoryListbox.ReadDirectoryNames(const ParentDirectory:String;DirectoryList:TStringList):Integer;
var
  TheCount,Status:Integer;
  SearchRec:TSearchRec;
begin
     TheCount:=0;
     Status:=FindFirst(IncludeTrailingPathDelimiter(ParentDirectory)+'*.*',faDirectory or faHidden or faSysFile,SearchRec);
     try
        while 0=Status
        do begin
                if faDirectory=(faDirectory and SearchRec.Attr) 
                then begin
                          if   ('.'<>SearchRec.Name)
                            and
                               ('..'<>SearchRec.Name)
                          then begin
                                    DirectoryList.Add(SearchRec.Name);
                                    Inc(TheCount);
                               end;
                     end;
                Status:=FindNext(SearchRec);
           end;
     finally
            FindClose(SearchRec);
     end;
     ReadDirectoryNames:=TheCount;
end;

procedure TDirectoryListBox.BuildList;
var
  TempPath: string;
  DirName: string;
  IndentLevel, BackSlashPos: Integer;
  VolFlags: DWORD;
  I: Integer;
  Siblings: TStringList;
  NewSelect: Integer;
  Root: string;
begin
  try
    Items.BeginUpdate;
    Items.Clear;
    IndentLevel := 0;
    Root := ExtractFileDrive(Directory)+'\';
    GetVolumeInformation(PChar(Root), nil, 0, nil, DWORD(i), VolFlags, nil, 0);
    FPreserveCase := VolFlags and (FS_CASE_IS_PRESERVED or FS_CASE_SENSITIVE) <> 0;
    FCaseSensitive := (VolFlags and FS_CASE_SENSITIVE) <> 0;
    if (Length(Root) >= 2) and (Root[2] = '\') then
    begin
      Items.AddObject(Root, OpenedBMP);
      Inc(IndentLevel);
      TempPath := Copy(Directory, Length(Root)+1, Length(Directory));
    end
    else
      TempPath := Directory;
    if (Length(TempPath) > 0) then
    begin
      if AnsiLastChar(TempPath)^ <> '\' then
      begin
        BackSlashPos := AnsiPos('\', TempPath);
        while BackSlashPos <> 0 do
        begin
          DirName := Copy(TempPath, 1, BackSlashPos - 1);
          if IndentLevel = 0 then DirName := DirName + '\';
          Delete(TempPath, 1, BackSlashPos);
          Items.AddObject(DirName, OpenedBMP);
          Inc(IndentLevel);
          BackSlashPos := AnsiPos('\', TempPath);
        end;
      end;
      Items.AddObject(TempPath, CurrentBMP);
    end;
    NewSelect := Items.Count - 1;
    Siblings := TStringList.Create;
    try
      Siblings.Sorted := True;
        { read all the dir names into Siblings }
      ReadDirectoryNames(Directory, Siblings);
      for i := 0 to Siblings.Count - 1 do
        Items.AddObject(Siblings[i], ClosedBMP);
    finally
      Siblings.Free;
    end;
  finally
    Items.EndUpdate;
  end;
  if HandleAllocated then
    ItemIndex := NewSelect;
end;

end.

现在我解释一下我所做的事情:
  • 通过将unitTDirectoryListbox_WithHiddenAndSystemFolders添加到接口uses中,我使表单使用修改后的(也就是hacked)组件。
  • 我首先复制了受保护的方法ReadDirectoryNames(需要修改的方法),我从FileCtrl单元中复制它,然后在我的自定义单元上编辑该副本以fix问题(不显示隐藏文件夹和系统文件夹);trick是在调用FindFirst之后添加faHidden或faSysFile部分,同时更改SlashSepIncludeTrailingPathDelimiter(避免一些额外的引用等),并进行重新格式化(索引等),以便我可以看到已修改的方法。
  • 然后我按照缺失的步骤进行操作...例如BuildList,我只需从FileCtrl单元中简单地复制它而没有进行任何修改(如果未复制,则hack无法工作,因为对ReadDirectoryNames的调用位于BuildList中)。
  • 然后我复制了FPreserveCaseFCaseSensitive的声明及其属性声明(它们在BuildList方法中使用)。
  • 就这样,现在修改后的TDirectoryListBox将看到隐藏和系统文件夹。
希望这能帮助其他人,这样你就可以在项目中同时拥有TDirectoryListBox(原始版本和修改版),而无需修改VCL。

P.D .:某些具有额外知识的人可能能够添加属性来配置是否显示隐藏和/或系统文件夹作为改进,这不应该很困难,只需要两个私有布尔变量及其相应的property声明和读写方法…我没有这样做,因为我想添加的不仅是这两个,还有符号链接等(在单元SysUtils中搜索faSymLink并查看有多少个,添加它们所有的工作量很大),对此造成任何不便表示抱歉。


我不确定你回答的是什么问题,但这个问题并不涉及TDirectoryListBox或修改组件方法或属性,也不涉及隐藏或系统文件。这个问题是关于迭代子目录中的文件的。 - Ken White

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接