Introduction
Recently I've found myself writing a lot of directory enumeration code. You
know the sort of thing. List all files in a directory and do something (or
nothing) with a file depending on the file extension or perhaps some magic
number at the start of the file. The code usually looks like this.
void EnumerateFolderContents(LPCTSTR szFolderName)
{
CString csTemp = szFolderName;
csTemp += _T("\\*.*");
CFileFind ff;
BOOL bWorking = ff.FindFile(csTemp);
while (bWorking)
{
bWorking = ff.FindNextFile();
if (ff.IsDirectory())
{
if (ff.IsDots())
continue;
EnumerateFolderContents(ff.GetFilePath());
}
else
{
}
}
}
We call
EnumerateFolderContents()
passing it a folder path. We
append
\*.*
(or some more specific filename extension) to the path
and enumerate the contents of that folder. If it finds a folder we check if the
folder meets the
IsDots()
test (is it
.
(the current
folder) or is it
..
(the parent folder). If it's either we ignore
it lest we go into an infinite loop. If not we enumerate that folder in turn.
If the something returned to us isn't a folder then it must be a file so we
apply our file processing code to that something.
This is a pretty simple piece of code to write. Like me, you've probably cribbed
it straight out of the MSDN documentation for the CFileFind
class,
including the bWorking
variable!
But after a while, if you need this kind of code in more than one place in your
applications, you start to notice that you're repeating yourself. It's starting
to look like a candidate for a class.
CEnumerateFolders
is a base class that encapsulates the guts of enumerating a folder. For each
sub-folder it finds (excluding the 'dot' folders) it calls a virtual function
and for each file it finds it calls another virtual function.
The class itself is very simple.
class CEnumerateFolders : public CObject
{
protected:
CEnumerateFolders();
public:
BOOL Execute(LPCTSTR szFolder,
BOOL bRecurse = TRUE,
DWORD dwCookie = 0,
LPCTSTR szFilter = _T("*.*"));
virtual BOOL OnFolder(LPCTSTR szFolder, DWORD dwCookie) const;
virtual BOOL OnFile(LPCTSTR szFilename, DWORD dwCookie) const;
};
The constructor is protected in order to force you to derive a class from this
one. The reason is that the base class
OnFolder()
and
OnFile()
implementations do nothing. If you were to instantiate a
CEnumerateFolders
object directly it would go ahead and enumerate the folder you told it to and
throw away all the results. Not quite the expected outcome!
On the other hand, I can hardly make either of the two virtual
functions pure virtual. It's perfectly possible that you might want to use the
base class implementation of one whilst overriding the do-nothing functionality
of the other. And I certainly didn't want to force the definition of a dummy
function
The Execute()
method is where the work is done. It looks like this.
BOOL CEnumerateFolders::Execute(LPCTSTR szFolder, BOOL bRecurse,
DWORD dwCookie, LPCTSTR szFilter)
{
ASSERT(szFolder);
ASSERT(AfxIsValidString(szFolder));
CFileSpec fs(szFolder, szFilter);
CFileFind ff;
BOOL bWorking = ff.FindFile(fs.GetFullSpec());
while (bWorking)
{
bWorking = ff.FindNextFile();
if (ff.IsDirectory())
{
if (ff.IsDots())
continue;
if (OnFolder(ff.GetFilePath(), dwCookie) == FALSE)
return FALSE;
if (bRecurse &&
Execute(ff.GetFilePath() + _T("\\"), dwCookie, szFilter) == FALSE)
return FALSE;
}
else if (OnFile(ff.GetFilePath(), dwCookie) == FALSE)
return FALSE;
}
return TRUE;
}
It looks almost exactly like the opening code snippet, which is not by
accident. All we've done is abstract the logic of dealing with the things found
in the folder enumeration operation. Note that passing
FALSE
for
bRecurse
prevents recursion. My first draft of the class didn't
call
OnFolder()
if recursion was turned off. In practice this
turned out to be way too restrictive.
Your overrides should return TRUE
if you want to continue
enumerating.
Using the class
is as easy as creating your own class derived from
CEnumerateFolders
.
For example, my application wants to find all image files in a particular
folder and use them to populate an
ImageList
. My derived class
looks like this.
class CPopulateThumbnailImageList : public CEnumerateFolders
{
public:
virtual BOOL OnFile(LPCTSTR szFilename, DWORD dwCookie) const;
};
where the only member is an override of
OnFile()
. My
OnFile()
looks like this.
BOOL CPopulateThumbnailImageList::OnFile(LPCTSTR szFilename, DWORD dwCookie) const
{
ASSERT(szFilename);
ASSERT(AfxIsValidString(szFilename));
CSelectImage *pDlg = (CSelectImage *) dwCookie;
ASSERT(pDlg);
ASSERT_KINDOF(CSelectImage, pDlg);
ASSERT(IsWindow(pDlg->GetSafeHwnd()));
CFileSpec fs(szFilename);
if (IsImageFile(szFilename))
pDlg->PostMessage(guiAdvise,
CSelectImage::adviseLoadImage,
LPARAM(new CString(fs.GetFullSpec()))
);
return TRUE;
}
which assumes that the
dwCookie
is in fact a pointer to my
CSelectImage
dialog. It checks the file name passed to it to see if it's an image file and
if it passes that test it posts a message to my dialog class with the name of
the file. Finally, in the
OnInitDialog()
handler in my dialog
class I do this.
BOOL CSelectImage::OnInitDialog()
{
CDialog::OnInitDialog();
CBitmap bmp;
CPopulateThumbnailImageList ptil;
m_images.Create(80, 60, ILC_COLOR16, 1, 1);
m_list.SetImageList(&m_images, LVSIL_NORMAL);
m_list.ApproximateViewRect(CSize(80, 60), -1);
ptil.Execute(m_csImagePath + _T("\\"), FALSE, DWORD(this));
return TRUE;
}
which sets up the
ImageList
and does some other housekeeping and
then calls
Execute()
on the derived class. Notice I've passed
FALSE
for the
bRecurse
parameter to ensure I'll only get images in the
folder I specified (and not images in all sub-folders).
Other issues
It's tempting to think that the class can be derived from
CFileFind
,
eliminating the need to declare an instance of
CFileFind
inside
the
Excecute()
method. That works until you do recursion. Each
level you recurse deeper into the folder structure will return the correct
results. It's when you reach the bottom of the tree and want to come back up
that you start losing results. The reason, of course, is that by reusing the
base class methods you've destroyed the return stack of open
FILEFIND
handles.
Dependencies
As presented the class relies on my
CFileSpec
class which can be
found
here[
^].
The download includes a copy of that class.
History
31 March 2004 - Initial version.
6 April 2004 - Updated to call OnFolder()
even when not recursing.