Directory Selection Components in MFC

Have you ever written an application which asks the user to select a directory? Have you ever wanted to provide them a nice, neat way to do that without forcing them to manually type in the path? Well, now you can!! I have created two controls that provide you the bacic functionality to make a dialog like the one above. Yes, this is a screen shot of my demo application that uses the controls I created. It was patterned off of the "Choose Directory" dialog that appears when you select a directory when creating a new project in Microsoft* Visual C++ 5.0. (To see this for yourself, choose "File | New" from the menu, then click on the "..." button to the right of the "Location" edit box.)

The controls I implemented are:

  • CDirectoryTree: CDirectoryTree is based off of CTreeCtrl and automatically fills itself in with the directory information. It does not display any files underneath the directories, only the directories themselves. As users change directories, only the current path to the directory and its children will be displayed. This prevents the control from appearing cluttered, and the user can easily see the direct path from the root down to the current directory. This is the same behavior as exhibited by the "Choose Directory" dialog in MSVC++ 5.0.
  • CDriveCombo: CDriveCombo is based off of CComboBox and adds a function which will find all the logical drives on the system, finds the volume name for each found drive, and populates itself with that information. It then determines the current drive and highlightes that item. Included are functions to test if the drive selected by the user is ready, and to revert the selection to the last known good drive.

This project is somewhat similar to the one in the article posted by Tom Werner Halvorsrxd on January 08 1999, but it has a completely different look and feel and is intended to provide a neat, orderly way for users to be able to select a complete, correct path name. It improves on the listing of logical drives on the system by providing the volume label (if it exists), and it provides a hierarchical view of the path from the root to the current directory. By the addition of a read-only edit box, the entire path is displayed in plain text for easy reading.

All the directions on including these components into your project are also included in the file Readme.txt in the source archive.

First, include the appropriate files for the controls you want to use. CDirectoryTree is implemented in files DirectoryTree.cpp and DirectoryTree.h, and CDriveCombo is implemented in files DriveCombo.cpp and DriveCombo.h. You will also have to include the four file folder bitmaps from my demo project, or you will have to provide four bitmaps of your own design. The bitmaps from this project are named closed.bmp, closedSel.bmp, open.bmp, and openSel.bmp.

Next, create your dialog with the controls you want to use. Using Class Wizard, create member variables for those controls using the appropriate class. If you want to have an edit box or static text to display the path as a readable string, include an edit control on the dialog and create a member variable for it. For example:



CDirectoryTree  m_DirTree;
CDriveCombo     m_DriveList;
CEdit           m_DirName;


Now, you must initialize the image list associated with the CDirectoryTree object. Create a CImageList object and add the four bitmaps you added to the project to that object. There are two ways you can do this. One is to add all the bitmaps in this predefined order:
  1. Closed File Folder image
  2. Selected File Folder image
  3. Open File Folder image
  4. Selected File Folder image
The other is to add the bitmaps to the CImageList object in any order you wish, but this will add one more function call you'll have to make. Once the bitmap resources have been added to the CImageList object, call the function CDirectoryTree::SetBitmapOrder() to initialize the image list in the CDirectoryTree object. If you added the images in a non-standard order, you will also need to call the function CDirectoryTree::SetBitmapOrder(). (See the function header in DirectoryTree.cpp for a description of the function paramters.) For example:


// Set up the image list for the tree control
CImageList * pImageList;
pImageList = new CImageList();
pImageList->Create(16, 16, ILC_COLOR, 4, 4);

// Load the bitmaps.  This must be done before calling Initialize()
// on the CDirectoryTree object.
// NOTE: IDB_FIRST_TREE_ICON = ID of the first bitmap resource,
//       IDB_LASE_TREE_ICON = ID of the last bitmap resource.
// Of course, you can choose any method you want to build this list.
CBitmap	bBitmap;
for (int nID = IDB_FIRST_TREE_ICON; nID <= IDB_LAST_TREE_ICON; nID++)
{
	bBitmap.LoadMappedBitmap(nID);
	pImageList->Add(&bBitmap, (COLORREF)0x000000);
	bBitmap.DeleteObject();
}
m_DirTree.SetBitmapList(pImageList);

// If the bitmaps were loaded in a non-standard order, call this function.
// Otherwise, the default order will be used.
m_DirTree.SetBitmapOrder(1,3,2,0);


Next, you must initialize the controls before displaying them, preferably in your application's initialization routine (such as OnInitDialog()). To initialize the controls, simply call their member function Initialize(). To initialize the edit control with the current directory, call the member function CString CDirectoryTree::GetCurrentDir(). For example:



m_DriveList.Initialize();
m_DirTree.Initialize();
m_DirName.SetWindowText(m_DirTree.GetCurrentDir());


At this point, you'll be able to get a dialog that appears just like the one in the screen shot at the top of this article. Now, you just need to enable communication between the controls.

To be notified that the user has selected a new drive, use the Class Wizard to map a function to the CBN_SELCHANGE message. You can use all the standard CComboBox functions to retrieve the letter of the drive chosen by the user. When the user selects a new drive from the CDriveCombo control, the new drive letter must be communicated to the CDirectoryTree control and the control must be reinitialized. Call the function SetCurrentDrive(char), passing in the letter of the new drive, to communicate the change to the control. Then, call Initialize() to reinitialize the control. For example:



// Handler for the CBN_SELCHANGE message
void CMyDialog::OnSelchangeDriveCombo() 
{
     int     nSelected = 0;
     CString szText;

     // Determine the new drive letter and test to see if it's valid.
     nSelected = m_DriveList.GetCurSel();
     if (!m_DriveList.IsDriveReady(nSelected))
     {
          // The drive wasn't valid!!  Revert to the previous one
          m_DriveList.ResetDrive(m_DirTree.GetCurrentDrive());
          return;
     }

     // Get the drive letter and communicate it to the tree.
     m_DriveList.GetLBText(nSelected, szText);
     m_DirTree.SetCurrentDrive(szText[0]);

     // Re-initialize the tree to show the new drive's directories.
     m_DirTree.Initialize();
     m_DirName.SetWindowText(m_DirTree.GetCurrentDir());
}


When the user chooses a new directory, the edit box must also be updated to display the new, complete path. When the user double-clicks on a new directory name, a user-defined message is passed to the control's parent. To catch this message, implement the function PreTranslateMessage() in your application and look for message ID WM_USER_PATHCHANGED (defined in DirectoryTree.h). DO NOT IMPLEMENT YOUR OWN HANDLER AND MESSAGE MAP ENTRY. I am not sure why, but doing so will cause an Access Violation error and will crash your application. However, catching the message within PreTranslateMessage() has been very stable and reliable. Once you have caught this message, you know that the path has changed. To get the new path string, use function CString CDirectoryTree::GetCurrentDir(). For example:



// Implementation of PreTranslateMessage()
BOOL CChooseDirDlg::PreTranslateMessage(MSG* pMsg) 
{
     if (WM_USER_PATHCHANGED == pMsg->message)
     {
          OnPathChanged();
          return TRUE;
     }

     return CDialog::PreTranslateMessage(pMsg);
}

// Handler for the user-defined message WM_USER_PATHCHANGED
void CChooseDirDlg::OnPathChanged()
{
     m_DirName.SetWindowText(m_DirTree.GetCurrentDir());
}


Download demo executable - 7 KB

Download source - 24 KB