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



Comments

  • icon`s flag

    Posted by Legacy on 01/02/2001 12:00am

    Originally posted by: hiro


    hi i found your nice class. thanks i use it.

    i think you should set flag not ILC_COLOR only when
    you initialize CImageList... below is better

    OnInitDialog()
    {
    ...
    pImageList->Create(...ILC_COLOR24|ILC_MASK...);
    ...
    pImageList->Add(ID,yourmaskcolor);
    ...
    }


    thanks!

    Reply
  • Thanx .....!

    Posted by Legacy on 12/12/2000 12:00am

    Originally posted by: Dillip Kumar Kara

    I got what exactly I was looking for ..Thanx a lot

    Reply
  • Wanting the moon (showing greyed out files)

    Posted by Legacy on 08/24/1999 12:00am

    Originally posted by: Jeff Donner

    Hi,
    How about adding the ability to show the files beneath the directory, greyed out? A user might have several directories holding similar files, and might want reassurance that they've picked the right one by seeing the right set of files beneath it. Of course you can't select the files, but they're there, providing context for the user.

    Just a suggestion. I haven't seen this implemented anywhere else.

    Jeff

    Reply
  • With network capability see.....

    Posted by Legacy on 07/28/1999 12:00am

    Originally posted by: John McTainsh

    The sample at /treeview/PathPicker.shtml
    can be used in this manner and also has network support. It can be uset to set an initial directory and browse the network neighbourhood.

    Reply
  • Small suggestion ;-)

    Posted by Legacy on 07/28/1999 12:00am

    Originally posted by: George

    Hi,

    It is nice. I would use more nice icons thought(actually I did some time ago doing the same thing), this ones are looking more like VB, or Borland Delphi style. Anyway, I think that the root directory should be indicated like that: "C:\".
    Also the whole thing have some flash when changing small amount of directories. It should not redraw all the contents.
    BTW, there a cool class in the Shell programming section here using SHBrowseForFolder.

    George

    Reply
  • Why didn't you include your project .dsp file?

    Posted by Legacy on 07/19/1999 12:00am

    Originally posted by: Jim Ryan

    I extracted your source code but there is no project file (ChooseDir.dsp) in the archive. VC6.0 is looking for it in the workspace (ChooseDir.dsw).

    Reply
  • Why don't...

    Posted by Legacy on 06/18/1999 12:00am

    Originally posted by: Paulo

      Why haven't you done something like CFileDialog, i would like to do only the following code :
    
    

    CChooseDirDlg * dirDlg = new CChooseDirDlg(defaultPath);

    int response = dirDlg->DoModal();
    if( response = OK )
    {
    CString path;
    path = dirDlg->getDir();
    // use path ....
    }
    else
    {
    //do nothing
    }

    Reply
  • How to fix memory leak in DirectoryTree.cpp

    Posted by Legacy on 06/08/1999 12:00am

    Originally posted by: Diana Cook

    BoundsChecker found a memory leak in InitDialog because  the CImageList pointer is allocated on the heap with
    "new" but never deleted.  
    
    To solve this leak, I made pImageList a private variable instead of a pointer.

    CImageList m_ImageList; //add this to the header file

    You have to change the references to pImageList from pointers (pImageList->Create, etc.) to non-pointers (m_ImageList.Create, etc.).
    When this is done the memory leak goes away.

    Reply
  • Why there is an Access Violation

    Posted by Legacy on 05/20/1999 12:00am

    Originally posted by: Henry

    \\\\If you add a message map hander for the changed selection, it tries to select an item before the window is ready - put a flag in to tell it to exit the OnSelChanged method until its ready, e.g. when you get WM_INIT_DIALOG or set a timer.

    That enables the program to begin - the 2nd problem is that the function cannot call Initialise a second time because it goes into an eternal loop. Here you really do need a timer.

    HTH
    Henry

    Reply
  • /controls/choosedir.shtml

    Posted by Legacy on 05/18/1999 12:00am

    Originally posted by: Igor

    Hi,
    I downloaded (for several times) this nice "choosedir" piece of code, but *.dsp file is missing...
    (or maybe something's wrong with me?!)

    thanks,
    Igor

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The rapid evolution of enterprise storage technologies, combined with external forces, like the explosion of big data, can cause Linux® and server administrators to play catch-up when it comes to storage. Running a bunch of monolithic storage devices and proprietary, disconnected technologies forces administrators to spend valuable time creating and managing complex solutions. To reduce complexity and enable rapid deployment of new technologies and applications, server administrators need a single open …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds