Click here to Skip to main content
Click here to Skip to main content

A class to make it easy to enumerate folder contents

, 5 Apr 2004 CPOL
Rate this:
Please Sign up or sign in to vote.
Enumerating folder contents the reusable way
<!-- Download Links --> <!-- Add the rest of your HTML here -->

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Rob Manderson

United States United States
I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.
 
I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.
 
Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.
 
Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.
 
I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.
 
Oh, I'm also a Kentucky Colonel. http://www.kycolonels.org

Comments and Discussions

 
GeneralGood Stuff PinmemberJack Rabbit6-Apr-04 14:51 
GeneralRe: Good Stuff PineditorRob Manderson6-Apr-04 15:51 
GeneralRe: Good Stuff PinmemberBlake Miller7-Apr-04 7:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 6 Apr 2004
Article Copyright 2004 by Rob Manderson
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid