Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A class to make it easy to enumerate folder contents

0.00/5 (No votes)
5 Apr 2004 1  
Enumerating folder contents the reusable way

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;

            //  Recursively traverse this subdirectory

            EnumerateFolderContents(ff.GetFilePath());
        }
        else
        {
            // Do something with a file...

        }
    }
}
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here