File Searcher Edit Control with Browse Button

Environment: VC6, W9x, W2K


When I need a user to select a file or folder, I would have to
create two controls, an edit control for entering the text, and a
browse button that would bring up a dialog for actually choosing
the file or folder. So I thought why no combine the two controls
into one. The CFileEditCtrl class is the result. The
class definition and implementation are in the files FileEditCtrl.h
and FileEditCtrl.cpp which are included in the demo


This code has not been tested for UNICODE builds, nor has it
been tested on a network with UNC paths. If any bugs are found
and fixed, please drop me a note at [email protected]


Thanks to Michael Dunn for his article "Introduction
to COM – What It Is and How to Use It"
for showing me
how to handle shortcut (*.lnk) files.


  1. The control is derived from CEdit. All CEdit member
    functions are CFileEditCtrl member functions. It can
    be created with any of the ES_* edit control styles.
    It will respond just like any other edit control to any EM_*
    commands, and sends all EN_* notification messages.
  2. The ellipses button is drawn in the controls
    nonclient area. It is a part of the control, not a seperate
    button that has to be added onto the dialog template, or
    otherwise created or setup. It can be placed on either the left
    or the right side of the control.
  3. The control has its own DDX_FileEditCtrl
    and DDV_FileEditCtrl dialog data exchange functions.
    Setting up the control for use is very easy.
  4. Using the Create() member function,
    the control can be created in any window, not just dialogs or
  5. The control can be used to browse for files or
    folders. And it has a member function that can be used to switch
    between the two. When the CFileDialog or SHBrowseForFolder
    dialogs are opened, they will be set to the directory currently
    entered in the control.
  6. The control accepts relative paths. Users can enter
    ..\..\anyfolder and the control will return the
    absolute path relative to the current working directory. Entering
    ‘.’ will return the current working directory. If
    the FEC_MULTIPLE flag is set, the first file entered
    will be relative to the current directory, and all subsequent
    files will be relative to the first file, unless the absolute
    path is entered.
  7. The control accepts wildcards ( ‘*’ and/or
    ‘?’ ) in the file name. Just set the FEC_WILDCARDS
  8. The control will automatically
    dereference shortcut ( *.lnk ) files. To disable this feature,
    just set the FEC_NODEREFERENCELINKS flag
  9. The control accepts Drag and Drop files and folders.
    Just create it with the WS_EX_ACCEPTFILES extended
    widows style.
  10. Member functions give access to the internal BROWSEINFO
    and OPENFILENAME structures, so if the default
    settings are not satisfactory, there is complete control over how
    the CFileDialog and SHBrowseForFolder
    dialogs are implemented.
  11. The control is resizable. The button keeps its
    proportionate size relative to the height of the control. As the
    control gets taller, the button gets bigger, and so do the dots
    on the button.
  12. The CFileDialog has the text on its
    default button changed from ‘Open’ to ‘OK’.
  13. When the ellipses button is clicked, the control
    sends a WM_NOTIFY message to its parent window,
    giving the parent window a chance to stop the SHBrowseForFolder
    or CFileDialog from popping up. The <Ctrl><.>
    keystroke has the same action as a click on the button.
  14. Using the Control

    To use this control in your application, Add the FileEditCtrl.h
    and FileEditCtrl.cpp files to your project. Then it is
    recommended to add the text strings defined at the top of the FileEditCtrl.cpp
    file to your string table resource, using the FEC_IDS_*
    identifiers defined there.

    // FEC_IDS_ALLFILES will be defined in resource.h if these strings
    // are in a string table resource
    #if !defined FEC_IDS_ALLFILES
        #define FEC_NORESOURCESTRINGS so this class knows how to handle these strings
        #define FEC_IDS_ALLFILES        _T("All Files (*.*)|*.*||")
        #define FEC_IDS_BUTTONTIP       _T("Browse")
        #define FEC_IDS_FILEDIALOGTITLE _T("Browse for File"
        #define FEC_IDS_SEPERATOR       _T(";")
        #define FEC_IDS_NOFILE          _T("Enter an existing file.")
        #define FEC_IDS_NOTEXIST        _T("%s does not exist.")
        #define FEC_IDS_NOTFILE         _T("%s is not a file.")
        #define FEC_IDS_NOTFOLDER       _T("%s is not a folder.")
        #define FEC_IDS_OKBUTTON        _T("OK")

    To use the control on a dialog, using all the default
    settings, add an edit control to the dialog template, add a
    CString member variable to the dialog class, and in DoDataExchange()
    add the DDX_FileEditCtrl() and DDV_FileEditCtrl()
    functions. The default settings for the SHBrowseForFolder
    dialog has the BIF_RETURNONLYFSDIRS flag set. And
    the default settings for the CFileDialog dialog has
    and OFN_NOCHANGEDIR flags set, the file filter is
    set to the FEC_IDS_ALLFILES resource string, and the
    dialog caption is set to the FEC_IDS_FILEDIALOGTITLE
    resource string. If you want the control to be used for folders,
    set the flag in last parameter in DDX_FileEditCtrl()
    to FEC_FOLDER, set it to FEC_FILE for

    If you want more control over the dialogs, such as choosing
    multiple files, then you have to add a CFileEditCtrl
    variable to your dialog class. In DoDataExchange()
    add the second version of DDX_FileEditCtrl(). And
    then get a pointer to the OPENFILENAME or BROWSEINFO
    structures, using the GetOpenFileName() or GetBrowseInfo()
    functions, and set them accordingly. In the demo app I have an
    edit control with an ID of IDC_EDIT1 and a CFileEditCtrl
    variable m_FileEditCtrl.

    void CFileEditDemoDlg::DoDataExchange(CDataExchange* pDX)
        DDX_FileEditCtrl(pDX, IDC_EDIT1, m_FileEditCtrl, FEC_FILE);

    Because these functions are not supported by Class Wizard,
    they have to be placed outside the AFX_DATA_MAP code
    block. If you would like to add Class Wizard support, see MFC
    Technical Note 26 DDX and DDV routines
    and look under ClassWizard

    To retrieve the file names from the control, use the GetStartPosition()
    and GetNextPathName() member functions. In the demo
    app I did this in the CDumpDialog::OnInitDialog()
    function in order to fill the list box with the files entered by
    the user.

    BOOL CDumpDialog::OnInitDialog()
        CFileEditDemoDlg *pDemo = (CFileEditDemoDlg *)GetParent();
        int width = 0;
        CString str;
        CDC *pDC = m_List.GetDC();
        int saved = pDC->SaveDC();
        // call GetStartPosition() to get the position of the first
        //     file in the control
        POSITION pos = pDemo->m_fileeditctrl.GetStartPosition();
        while (pos)
            // add the file paths to the list
            str = pDemo->m_fileeditctrl.GetNextPathName(pos);
            CSize size(0, 0);
            size = pDC->GetTextExtent(str);
            width = width > ? width :;
        m_List.SetHorizontalExtent(width + 5);
       return TRUE;  // return TRUE unless you set the focus to a control
                     // EXCEPTION: OCX Property Pages should return FALSE

    When a user clicks on the browse button, the control will send
    a WM_NOTIFY message with a FEC_NM_PREBROWSE
    notification code to it’s parent window before it brings up the SHBrowseForFolder
    or CFileDialog dialogs. The NMHDR*
    pointer will point to a FEC_NOTIFY structure. The pFEC
    member will point to the CFileEditCtrl that sent the
    message. You can use this pointer to modify the OPENFILENAME
    or BROWSEINFO structures. If you set the LRESULT
    parameter of the OnNotify handler to a nonzero value, you will
    stop the dialogs from executing.

    The control will send another WM_NOTIFY message
    with a FEC_NM_POSTBROWSE notification code after the
    dialogs return and the controls window text has been updated. The
    NMHDR* pointer will once again point to a FEC_NOTIFY
    structure, but the LRESULT parameter will have no effect and can
    be ignored.

    typedef struct tagFEC_NOTIFY {
        NMHDR hdr;
        CFileEditCtrl* pFEC;       // pointer to control that sends
                                   //    this notification
        tagFEC_NOTIFY (CFileEditCtrl *FEC, UINT code);
    #define FEC_NM_PREBROWSE  1    // notification code sent before dialogs
                                   //    pop up
    #define FEC_NM_POSTBROWSE 2    // notification code sent after dialogs
                                   //    return

    User Functions

    These are the functions that are used to control the CFileEditCtrl

    CFileEditCtrl::CFileEditCtrl(BOOL bAutoDelete /* = FALSE */)

    The class constructor has a parameter bAutoDelete
    that is FALSE by default. Setting bAutoDelete
    to TRUE causes the control class to delete itself in
    its PostNCDestroy() function. If this is done, there
    is no way of getting the files entered after the dialog has


    The class destructor cleans up all the internal pointers.

    BOOL CFileEditCtrl::Create(DWORD dwFlags,
                               DWORD dwExStyle,
                               LPCTSTR lpszWindowName,
                               DWORD dwStyle,
                               const RECT& rect,
                               CWnd* pParentWnd,
                               UINT nID)

    Create creates a CFileEditCtrl
    window in any window that does not have a template. For
    information on the dwFlags parameter see SetFlags()
    below. All other parameters are passed on to CWnd::CreateEx().
    Returns TRUE on success, and FALSE on

    DWORD CFileEditCtrl::GetFlags()

    GetFlags() returns a DWORD containing the bit
    flags. See SetFlags() below for an explanation of
    the flags.

    BOOL CFileEditCtrl::ModifyFlags(DWORD remove, DWORD add)

    ModifyFlags() is used to modify the controls
    functionality. First the remove flags are removed,
    and then the add flags are added. Returns TRUE
    on success, and FALSE on failure. See SetFlags()
    below for an explanation of the flags.

    BOOL CFileEditCtrl::SetFlags(DWORD dwFlags)

    SetFlags() is used to set the controls
    functionality. SetFlags() returns TRUE
    on success, and FALSE on failure.



    FEC_FILE The control is set to accept files. When the ellipses
    button is clicked, the control starts the windows common
    File Open dialog. This flag cannot be used with the
    FEC_FOLDER flag.
    FEC_MULTIPLE Used with FEC_FILE. The control will accept multiple
    files. Has the same effect as the OFN_ALLOWMULTISELECT
    FEC_WILDCARDS Used with FEC_FILE. The control will accept and
    resolve any wildcards (‘*’ and/or ‘?’) in the file name.
    If the FEC_MULTIPLE flag is set, GetNextPathName()
    will return all the files that match. If FEC_MULTIPLE is
    not set, GetNextPathName() will return only
    the first match.
    will return the path name of any shortcut (*.lnk) files
    entered. If this flag is not set, GetNextPathName()
    will return the path name of the file the shortcut points
    to. Has the same effect as the OFN_NODEREFERENCELINKS
    FEC_FOLDER The control is set to accept folders. When the
    ellipses button is clicked, the control starts the
    SHBrowseForfolder dialog. This flag cannot be used with
    the FEC_FILE flag.
    FEC_TRAILINGSLASH Used with FEC_FOLDER. The folder path entered in the
    control will have a trailing slash.
    FEC_BUTTONLEFT The ellipses button will be placed on the left side
    of the control.
    FEC_BUTTONTIP Enables the browse button tooltip. The tooltip text
    is set with the FEC_IDS_BUTTONTIP resource string
    FEC_CLIENTTIP Enables the client area tooltip. The tooltip text is
    set with the SetClientTipText() member
    SetClientTipText(CString text)

    SetClientTipText() is used to set the text of the
    client area tooltip.

    BROWSEINFO* CFileEditCtrl::GetBrowseInfo() const

    GetBrowseInfo() returns a pointer to the internal
    BROWSEINFO structure. The return value is NULL
    if the control is set to find files. Use this pointer to modify
    the SHBrowseForFolder dialog.

    OPENFILENAME* CFileEditCtrl::GetOpenFileName() const

    GetOpenFileName() returns a pointer to the
    internal OPENFILENAME structure. The return value is
    NULL if the control is set to find folders. Use this
    pointer to modify the CFileDialog dialog.

    POSITION CFileEditCtrl::GetStartPosition()

    GetStartPosition() returns a MFC POSITION
    structure that is used as a starting point for the GetNextPathName()
    function. Returns NULL if there are no files entered
    in the control.

    CString CFileEditCtrl::GetNextPathName(POSITION &pos)

    GetNextPathName() returns a CString containing
    the full path name of the file entered in the control at the
    position referenced by the pos variable. GetNextPathName()
    updates pos to reference the next file entered in the control, or
    sets pos to NULL if there are no more
    files. Before calling GetNextPathName() for the
    first time, pos must be initialized by the GetStartPosition()

    void DDV_FileEditCtrl (CDataExchange *pDX, int nIDC)

    DDV_FileEditCtrl() is used to check that the
    entered file actually exists, and if it does, ensures that the
    user has entered either a file or a folder, depending on on the
    settings of the control. If you want your user to enter a
    nonexistant file or folder, do not use the DDV_FileEditCtrl()

    void DDX_FileEditCtrl (CDataExchange *pDX,
                           int nIDC,
                           CFileEditCtrl &rCFEC,
                           DWORD dwFlags)
    void DDX_FileEditCtrl (CDataExchange *pDX,
                           int nIDC,
                           CString& rStr,
                           DWORD dwFlags)

    These functions subclass the edit controls with the ID nIDC
    and pass the file data between the control and either the CFileEditCtrl
    referenced by rCFEC or the CString
    referenced by rStr. For information on dwFlags
    see SetFlags() above. The CString
    version does not accept the FEC_MULTIPLE flag (How can it
    return multiple files in one CString?).

    The Button

    In order to get the button to work I first had to override the
    OnNcCalcSize() function. This is the function that
    is used to calculate the size and position of a windows client
    area. In my override I called CEdit::OnNcCalcSize()
    to get the default size and position of the client area, then I
    adjusted the size of the client area and calculated the size and
    position of the button. The CRect m_rcButtonRect
    member variable is used to store this information.

    void CFileEditCtrl::OnNcCalcSize(BOOL bCalcValidRects,
                                     NCCALCSIZE_PARAMS FAR* lpncsp)
       // calculate the size of the client area and the button
       CEdit::OnNcCalcSize(bCalcValidRects, lpncsp);
       // set button area equal to client area of edit control
       m_rcButtonRect = lpncsp->rgrc[0];
       if (m_bButtonLeft)   // draw button on left side of the control
          // shrink left side of client area by 80% of the
          //    height of client area
          lpncsp->rgrc[0].left +=
             (lpncsp->rgrc[0].bottom - lpncsp->rgrc[0].top) * 8/10;
          // shrink button so its right side is at left side of client area
          m_rcButtonRect.right = lpncsp->rgrc[0].left;
       else          // draw the button on the right side of the control
          // shrink right side of client area by 80% of height of client area
          lpncsp->rgrc[0].right -=
               (lpncsp->rgrc[0].bottom - lpncsp->rgrc[0].top) * 8/10;
          // shrink button so its left side is at right side of client area
          m_rcButtonRect.left = lpncsp->rgrc[0].right;
       if (bCalcValidRects)
          // convert button coordinates from parent client coordinates
          //   to control window coordinates

    The only time OnNcCalcSize() is called is when
    the windows frame has changed, so to force a call to OnNcCalcSize()
    I had to call SetWindowPos() from SetFlags(),
    using the SWP_FRAMECHANGED flag.

    BOOL FileEditCtrl::SetFlags(DWORD dwFlags)
          // Force a call to CFileEditCtrl::OnNcCalcSize() to calculate button size

    I then needed to paint the button on the control, so I wrote
    the DrawButton() function. Because the button is not
    in the client area of the window, DrawButton() has
    to be called from OnNcPaint().

    void CFileEditCtrl::OnNcPaint()
       CEdit::OnNcPaint();      // draws the border around the control
       DrawButton (m_nButtonState);   // draw the button in its current state

    The next thing was to get mouse messages for the button.
    Because the button is not in the client area, it would not get
    client area mouse messages, and because it is not a border, it
    would not get nonclient mouse messages. To solve this problem I
    had to override OnNcHitTest() and get it to return HT_BORDER
    when the mouse cursor was over the button.

    UINT CFileEditCtrl::OnNcHitTest(CPoint point)
       UINT where = CEdit::OnNcHitTest(point);
       if (where == HTNOWHERE && ScreenPointInButtonRect(point))
          where = HTBORDER;
       return where;

    Now a mouse press on the button would generate a WM_NCLBUTTONDOWN
    message, so I had to override OnNcLButtonDown(). In OnNcLButtonDown()
    I would capture the mouse using SetCapture() and
    call DrawButton() to draw the button as down.
    Because once the mouse is captured, it no longer generates
    nonclient mouse messages, I would have to respond to WM_LBUTTONUP
    and WM_MOUSEMOVE messages in order to keep track of
    the mouse. Because CEdit::OnLButtonDown also
    captures the mouse, I could not use GetCapture to
    see if the button had captured the mouse, so I added the BOOL
    variable to keep track of it.

    void CFileEditCtrl::OnNcLButtonDown(UINT nHitTest, CPoint point)
       CEdit::OnNcLButtonDown(nHitTest, point);
       if (ScreenPointInButtonRect(point))
          m_bMouseCaptured = TRUE;

    By overriding OnMouseMove() I could keep track of
    the captured mouse, and draw the button as down if the mouse
    cursor was over the button, or as up if it was not.

    void CFileEditCtrl::OnMouseMove(UINT nFlags, CPoint point)
       CEdit::OnMouseMove(nFlags, point);
       if (m_bMouseCaptured)
          if (ScreenPointInButtonRect(point))
             if (m_nButtonState != BTN_DOWN)
                DrawButton (BTN_DOWN);
          else if (m_nButtonState != BTN_UP)
             DrawButton (BTN_UP);

    In the override of OnLButtonUp the mouse capture
    is released, the m_bMouseCaptured flag is cleared,
    and if the mouse cursor is over the button, the ButtonClicked()
    function is called. The ButtonClicked() function
    opens the appropriate dialog and posts a BN_CLICKED
    notification message to the control’s parent window.

    void CFileEditCtrl::OnLButtonUp(UINT nFlags, CPoint point)
       CEdit::OnLButtonUp(nFlags, point);
       if (m_bMouseCaptured)
          m_bMouseCaptured = FALSE;
          if (m_nButtonState != BTN_UP)
          if (ScreenPointInButtonRect(point))

    Revision History

    November 11, 2000  - allowed the control to work with dialog templates
    November 22, 2000  - register the control's window class, can now be
                         added to dialog as custom control
    January 4, 2001    - near total rewrite of the control, now derived from
                       - control can now be added to dialog template using
                         an edit control
                       - browse button now drawn in nonclient area of control
    January 5, 2001    - removed OnKillFocus(), replaced with OnDestroy()
    January 15, 2001   - added DDX_ and DDV_ support
                       - modified GetStartPosition() and GetNextPathName()
                       - modified how FECOpenFile() updates the control text
                         when multiple files are selected
                       - added FillBuffers()
                       - added support for relative paths
                       - added OnChange handler
                       - added drag and drop support
    January 26, 2001   - fixed bug where SHBrowseForFolder does not like
                         trailing slash
    January 27, 2001   - fixed bug where if control is initialized with text,
                         FillBuffers was not called.
    January 28, 2001   - removed GetFindFolder() and SetFindFolder() replaced
                         with GetFlags() and SetFlags()
                       - modified the DDX_ and DDV_ functions to accept these
                       - modified the Create() function to accept these flags
                       - allowed option for returned folder to contain
                         trailing slash
                       - allowed browse button to be on the left side of the
                       - added ScreenPointInButtonRect() to better tell if
                         mouse cursor is over the button
                       - modified how OnDropFiles() updates the control text
                         when multiple files are dropped
    February 25, 2001  - fixed EN_CHANGE notification bug. Now parent window
                         recieves this notification message
                         used ON_CONTROL_REFLECT_EX macro instead of
    April 12, 2001     - added OnSize handler, fixed button drawing problem
                         when control size changed
    April 21, 2001     - added a tooltip for the browse button
    May 12, 2001       - removed OnDestroy, replaced with PostNCDestroy
                       - added tooltip support to client area
                       - modified the FECBrowseForFolder and FECFolderProc
                       - added a one pixel neutral area between the client
                         area and browse button when the
                         button is on the right hand side of the control.
                         (looks better IMO)
    May 29, 2001 - PL -- removed the filename from the
                         variable, so when browsing back for file, we
                         open the correct folder.
                       - used smaller (exact size) arrays for file,
                         extension and path components.
                       - some cosmetic changes.
    May 29, 2001       - FECFolderProc now checks for UNC path.
                         SHBrowseForFolder can not be initialized with UNC
    June 2, 2001       - modified ButtonClicked function. Now sends a
                         WM_NOTIFY message to parent window before
                         showing dialog, allows parent window to cancel
                         action by setting result to nonzero. also sends
                         WM_NOTIFY message to parent window after dialog
                         closes with successful return
    June 9, 2001       - added OnNcLButtonDblClk handler. Double click on
                         button treated as two single clicks
    June 23, 2001      - placed a declaration for the FECFolderProc global
                         callback function into the header file
                       - fixed bug that occured when removing the filename
                         from the m_pCFileDialog->m_ofn.lpstrInitialDir
                         variable when there was no file to remove
    August 2, 2001     - replaced SetWindowText() with OnSetText() message
                         handler. now correctly handles WM_SETTEXT messages
    August 12, 2001    - added GetValidFolder() function and modified
                         FECOpenFile() function. we now start browsing in
                         the correct folder -- it finally works!!!  {:o)
                       - modified SetFlags() so the button could be moved
                         by setting the FEC_BUTTONLEFT flag
                       - removed the m_bCreatingControl variable
                       - removed the call to SetWindowPos() from the
                         Create() and DDX_FileEditCtrl() functions. Now
                         done in SetFlags() function
    August 14, 2001    - modified FECOpenFile(). Now sets the file name in
                         CFileDialog to first file name in FileEditCtrl
    August 18, 2001    - Set the tooltip font to the same font used in
                         the CFileEditCtrl
    September 2, 2001  - added the ModifyFlags() function and changed how
                         the flags are handled
                       - modified the GetFlags() function
                       - added the FEC_MULTIPLE and FEC_MULTIPLEFILES flags
                       - added support for wildcards ( '*' and '?') in
                         Involved :
                            modifying the GetStartPosition(),
                                      and FillBuffers() functions
                            adding the ExpandWildCards() function
                            replacing the m_lpstrFiles variable with
                                      the m_Files array
                            adding the FEC_WILDCARDS flag.
    September 3, 2001  - added ability to dereference shortcut files
                       - added the FEC_NODEREFERENCELINKS flag.
                       - added the DereferenceLink() function.
    September 5, 2001  - fixed the Create() function - now destroys the
                         control if the SetFlags() function fails
    September 8, 2001  - added the AddFiles() function to be better able
                         to handle shortcut (*.lnk) files
                         modified the OnDropFiles() function to be
                         better able to handle shortcut (*.lnk) files

    Be sure to check here for the latest updates.


    Download demo project – 38 Kb

More by Author

Must Read