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:
|