The Database Managers, Inc.

Contact The Database Managers, Inc.


Use an RSS enabled news reader to read these articles.Use an RSS enabled news reader to read these articles.

Use FindFirst and FindNext to read a directory

by Curtis Krauskopf

Q:  How can I use FindFirst and FindNext to read a directory listing?

A:  Here's an example program:

findfirst_example.zip (6K)


#include <vcl.h>
#pragma hdrstop

#include "findfirst_form.h"
#include "dir.h"
//---------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------

__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)

{
  int iAttributes = 0;

  iAttributes |= faReadOnly * CheckBox1->Checked;
  iAttributes |= faHidden * CheckBox2->Checked;
  iAttributes |= faSysFile * CheckBox3->Checked;
  iAttributes |= faVolumeID * CheckBox4->Checked;
  iAttributes |= faDirectory * CheckBox5->Checked;
  iAttributes |= faArchive * CheckBox6->Checked;
  iAttributes |= faAnyFile * CheckBox7->Checked;

  // Reset the grid, display a default failure message (which will
  // be overwritten if a file is found)
  StringGrid1->RowCount = 2;
  StringGrid1->FixedRows = 1;
  StringGrid1->Rows[0]->CommaText = "Filename,Size,Attributes";
  StringGrid1->Cells[0][1] = "No Files Found";
  StringGrid1->Cells[1][1] = "";
  StringGrid1->Cells[2][1] = "";
  StringGrid1->Visible = true;

  displayFiles(Edit1->Text, iAttributes, 1);

  if (StringGrid1->RowCount > 2) StringGrid1->RowCount--;
}


//---------------------------------------------------------------------

void TForm1::displayFiles(AnsiString path, 
                          int iAttributes, 
                          int nestingLevel)
{
  TSearchRec sr;

  // Creating the nesting prefix for displaying with each file name
  AnsiString nesting = "";
  for (int i = 0; i < nestingLevel; i++)
    nesting += "  ";

  if (FindFirst(path, iAttributes, sr) == 0)
  {
    do
    {
      {
        StringGrid1->Cells[0][StringGrid1->RowCount-1] = nesting + sr.Name;
        StringGrid1->Cells[1][StringGrid1->RowCount-1] = IntToStr(sr.Size);
        StringGrid1->Cells[2][StringGrid1->RowCount-1] = 
             "0x" + IntToHex(sr.Attr & faAnyFile, 2);
        StringGrid1->RowCount = StringGrid1->RowCount + 1;
        if ((sr.Attr & faDirectory) && DrillDirectories->Checked) {
          // Do not drill into "." or ".."
          if (sr.Name != "." && sr.Name != "..") {
            AnsiString drillPath = appendDirectory(path, sr.Name);
            displayFiles(drillPath, iAttributes, (nestingLevel+1));
          }
        }
      }
    } while (FindNext(sr) == 0);
    FindClose(sr);
  }


  // If we are supposed to drill into directories but the faDirectory flag
  // was not set, that means findFirst skipped all of the directories.  Do
  // the search again, this time only looking for directories so that we
  // can drill into them.
  //
  if (DrillDirectories->Checked && ((faDirectory & iAttributes) == 0)) {
    // Use driveAndPath() to Create a path that does not contain a
    // filename or extension so that directories can be drilled into
    if (FindFirst(driveAndPath(path) + "*",
                  iAttributes | faDirectory,
                  sr) == 0)
    {
      do {
        if (sr.Attr & faDirectory) {
          if (sr.Name != "." && sr.Name != "..") {
            // When we find a directory that can be drilled into,
            //  recurse into that directory so that the matching
            //  files can be displayed.
            displayFiles(appendDirectory(path, sr.Name), 
                         iAttributes, 
                         nestingLevel+1);
          }
        }
      } while (FindNext(sr) == 0);
    }
    FindClose(sr);
  }
}


// Append the newDir directory to the path, retaining any 
// filename that might already be on the path.
//
AnsiString TForm1::appendDirectory(AnsiString path, AnsiString newDir)
{
  char drillIntoPath[MAXPATH];
  char drive[MAXDRIVE];
  char dir[MAXDIR];
  char file[MAXFILE];
  char ext[MAXEXT];

  fnsplit(path.c_str(),drive,dir,file,ext);

  strcat(dir, newDir.c_str());

  fnmerge(drillIntoPath,drive,dir,file,ext);

  return(AnsiString(drillIntoPath));
}



// Given a fully qualified filename, return just the drive and
// path.
//
AnsiString TForm1::driveAndPath(AnsiString path)
{
  char drillIntoPath[MAXPATH];
  char drive[MAXDRIVE];
  char dir[MAXDIR];
  char file[MAXFILE];
  char ext[MAXEXT];

  fnsplit(path.c_str(),drive,dir,file,ext);

  fnmerge(drillIntoPath,drive,dir,0,0);

  return(AnsiString(drillIntoPath));
}

When this program is executed, it first prompts the user for a directory (with optional wildcards). The user can also check (tick) any of the optional FindFirst() flags:

  • faReadOnly
  • faHidden
  • faSysFile
  • faVolumeID
  • faDirectory
  • faArchive
  • faAnyFile

In addition, the user can also optionally choose to drill into subdirectories.

When the user clicks on the Search button, the directory listing is displayed in a TStringGrid:

Another example of using this sample program is by checking (ticking) the faDirectory checkbox. The same directory listing appears like this:

Notice that in this example, the current directory (".") and the parent directory ("..") are shown in the list.

The Borland C++ Builder 6 documentation for FindFirst() says:

Searches for the first instance of a file name with a given set of attributes in a specified directory.

I've found that this isn't exactly true. For example, in both of the above examples, files with a 0x20 attribute are listed even though the faArchive bit was not set. Also, if Borland's documentation was correct, the only files listed in the first example should be non-archived files and in the second example the only files should be directory entries.

This inconsistency is the main reason why I ended up creating a FindFirst() and FindNext() example program. I use the sample program to test which parameters should be used and the result for various test directories.

This article was written by Curtis Krauskopf (email at ).

Copyright 2003-2010 The Database Managers, Inc.


Popular C++ topics at The Database Managers:

C++ FAQ Services | Programming | Contact Us | Recent Updates
Send feedback to: