Bruce
Bruce

Reputation: 11

How to use FindFirst() to enumerate subdirectories?

I have just installed Delphi Community Edition and thought as a newbie I was doing well until I hit the FindFirst() procedure.

What I have is a directory list:

c:\BFUtils\01Utils
          \02Utils
          \03Utils
          \04Utils

Then, to try out FindFirst() to get the subdirectories of BFUtils, I wrote the following code, which I feel is needed to simply count the number of subdirectories:

sStartPath := 'c:\BFUtils';

Result := 0;
res := FindFirst(sStartPath, faDirectory, SearchRec);

if res = 0 then begin
  try
    while res = 0 do begin
      if SearchRec.FindData.dwFileAttributes and faDirectory <> 0 then begin
        Name := SearchRec.FindData.cFileName;
        if (Name <> '.') and (Name <> '..') then begin
          inc(Result);
        end;
      end;
      res := FindNext(SearchRec);
    end;
  finally
    FindClose(SearchRec);
  end;
end;

This, as it is, compiles and runs, but it only finds the BFUtils directory.

Searching the Internet, I feel that the file mask (sStartPath) is not correct. So I have tried:

All these options will compile and link without errors, but when the program runs it displays an error:

"'.'" is an Invalid Item.

What else can I try here?

Upvotes: 1

Views: 967

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 596407

You are asking FindFirst() to locate your c:\BFUtils folder itself. To enumerate the contents of that folder, you need to use a wildcard mask instead:

function CountSubDirs(const Path: string): Integer;
var
  sMask: string;
  SearchRec: TSearchRec;
  res: Integer;
begin
  Result := 0;

  sMask := IncludeTrailingPathDelimiter(Path) + '*'; // or '*.*'

  res := FindFirst(sMask, faDirectory, SearchRec);
  if res <> 0 then
  begin
    if res <> ERROR_FILE_NOT_FOUND then
      RaiseLastOSError(res);
  end else
  begin
    try
      repeat
        if (SearchRec.FindData.dwFileAttributes and faDirectory) = faDirectory then
        begin
          if (StrComp(SearchRec.FindData.cFileName, '.') <> 0) and
             (StrComp(SearchRec.FindData.cFileName, '..') <> 0) then
          begin
            Inc(Result);
          end;
        end;
        res := FindNext(SearchRec);
      until res <> 0;
      if res <> ERROR_NO_MORE_FILES then
        RaiseLastOSError(res);
    finally
      FindClose(SearchRec);
    end;
  end;
end;
var NumSubDirs: Integer;
NumSubDirs := CountSubDirs('c:\BFUtils');

That has always worked fine for me. If it is not working for you, then something else is wrong with code you have not shown yet, as there is nothing in the code actually shown that can cause the "'.'" is an Invalid Item error message you claim.

Upvotes: 1

DelphiCoder
DelphiCoder

Reputation: 2007

In modnern Delphi versions you don't necessarily need to work with FindFirst, FindNext and FindClose directly anymore!

Add the unit System.IOUtils to a uses section in your unit.
There is, among some others, the record TDirectory:

Contains a large number of static utility methods used in directory manipulations.

TDirectory is a record containing only static methods used to perform various operations on directories. You should not declare variables of type TDirectory, since TDirectory has no instance methods or fields. Possible operations that can be completed using TDirectory include:

  • Creating, renaming, and deleting directories.
  • Traversing directories (also recursively).
  • Manipulating directory attributes and timestamps.

For finding all subdirectories you can then use one of the overloaded GetDirectories methods:

Use GetDirectories to obtain a list of subdirectories in a given directory. The return value of GetDirectories is a dynamic array of strings in which each element stores the name of a subdirectory.

There are three forms of the GetDirectories method:

  • The first form only accepts the path of the directory for which subdirectories are enumerated.
  • The second form includes a search pattern used when matching subdirectory names.
  • The third form includes an option specifying whether a recursive mode will be used while enumerating.

So for counting all subdirectories you can simply do the following:

function SubDirCount(const Path: string): integer;
begin
  result := Length(TDirectory.GetDirectories(Path));
end;

// somewhere else
ShowMessage(SubDirCount('C:\BFUtils').ToString);

And for your original code use this as a FileMask: sStartPath + '\.*'

Upvotes: 2

Related Questions