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

Multiple Selection in a File Dialog

By , 23 Nov 2002
 

Introduction

When using the Windows FileOpen dialog with multiple selection do you ever wonder how much memory you have to allocate for the buffer. Is one kilobyte going to be enough? Or should you make it ten? How about one Megabyte just to be safe?

It seems that no matter what you choose, you are either going to waste a whole bunch of memory just to be safe, or your user is going to select a bunch of files only to find their selection didn't work because your buffer was too small.

Well no more. If you use the method I am about to describe your problems will be over.

How To

The first and most important thing is that this method will only work with the Explorer style file dialogs because we make use of various messages that the old windows 3.1 style file dialogs do not support.

When you are browsing through your file system using the file dialog, the dialog will send WM_NOTIFY messages to the OFNHook procedure. ( Note: In MFC the hook procedure is set up automatically when you use the CFileDialog class, look up OPENFILENAME in MSDN if you want more information on setting up the OFNHook procedure if you are not using MFC. ) We are interested in trapping the CDN_SELCHANGE notification message. The CDN_SELCHANGE notification message is sent whenever the selection changes in the list box that displays the currently open folder. In MFC you can handle this message by overriding the CFileDialog::OnFileNameChange() function.

Now, in our CDN_SELCHANGE handler, the first thing that is required is to check if the buffer that was allotted using the OPENFILENAME structure is large enough. If it is, then we do not have to bother duplicating the default behaviour. If however, the buffer is too small then we have to take matters into our own hands.

To figure out the required size of the buffer we send two messages to the file dialog. They are the CDM_GETFOLDERPATH and CDM_GETSPEC messages. The CDM_GETFOLDERPATH message will return the required size of a buffer needed to hold the currently selected folder path, and the CDM_GETSPEC will return the required size of the buffer needed to hold all the file names. Now, just add these two values together and if the sum is greater than the nMaxFile member of the OPENFILENAME structure then the supplied buffer is too small.

Now, in order to retrieve the file names from the file dialog, we will have to set up two buffers of our own. One for the folder path and an other for the files. Use the CDM_GETFOLDERPATH message to fill the folder buffer, and use the CDM_GETSPEC message to fill the files buffer. All the files in the files buffer will be enclosed between quotation marks, and separated by spaces. ( In other words, exactly as they are shown in the "File Name" edit box. ) At this point it is also advisable to set some sort of flag to let us know later that we have used our own buffers, and not the default one.

void CFECFileDialog::OnFileNameChange()
{
    TCHAR dummy_buffer;
    
    // Get the required size for the 'files' buffer
    UINT nfiles = CommDlg_OpenSave_GetSpec(GetParent()->m_hWnd, 
        &dummy_buffer, 1);

    // Get the required size for the 'folder' buffer
    UINT nfolder = CommDlg_OpenSave_GetFolderPath(GetParent()->m_hWnd, 
        &dummy_buffer, 1);

    // Check if lpstrFile and nMaxFile are large enough
    if (nfiles + nfolder > m_ofn.nMaxFile)
    {
        bParsed = FALSE;
        if (Files)
            delete[] Files;
        Files = new TCHAR[nfiles + 1];
        CommDlg_OpenSave_GetSpec(GetParent()->m_hWnd, Files, nfiles);

        if (Folder)
            delete[] Folder;
        Folder = new TCHAR[nfolder + 1];
        CommDlg_OpenSave_GetFolderPath(GetParent()->m_hWnd, Folder, 
            nfolder);
    }
    else if (Files)
    {
        delete[] Files;
        Files = NULL;
        delete[] Folder;
        Folder = NULL;
    }

    CFileDialog::OnFileNameChange();
}

Now, another thing we have to handle is when the user clicks the OK button. When the OK button is clicked, the file dialog will return IDOK if there were no errors, however, in our case there will be an error as the default buffer was too small, so the file dialog will return IDCANCEL. What we have to do now is check the error code using the CommDlgExtendedError() function and check if the error was FNERR_BUFFERTOOSMALL ( defined in cderr.h ). If that was the error, and our flag was set to tell us we have used our own buffer, then all is well with the world and we can get the file names from our own buffer.

int CFECFileDialog::DoModal()
{
    if (Files)
    {
        delete[] Files;
        Files = NULL;
        delete[] Folder;
        Folder = NULL;
    }

    int ret = CFileDialog::DoModal();

    if (ret == IDCANCEL)
    {
        DWORD err = CommDlgExtendedError();
        if (err == FNERR_BUFFERTOOSMALL/*0x3003*/ && Files)
            ret = IDOK;
    }
    return ret;
}

All that is left is to extract the names from the buffers. In the supplied demo, I have overridden the GetStartPosition() and GetNextPathName() CFileDialog member functions in order to make this easier.

Using the CFECFileDialog class

If you want to use the supplied class in your own code, just add the FECFileDialog.h and FECFileDialog.cpp files to your project, and use the CFECFileDialog class the same as you would use the CFileDialog class.

That's it. I hope some of you find this useful, because I really hate to waste your time and mine :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

PJ Arends
President
Canada Canada
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralInstant Crash ... VS2010memberRedDK22 Apr '11 - 12:06 
The packaged manifest .exe is ok.
 
My output .exe! Bang crash-on-button-depression.
 
Windows Server 2008 RC2. Common dialog suspected.
 
I like this old-looking stuff. So even though it doesn't work for me, giving up anything that has this new MFC oopener is a brain-send.
GeneralMy vote of 5memberyenner3 Jan '11 - 16:14 
Excellent,thanks.
GeneralProblem with GetPathName() Functionmemberdsiyekd23 Feb '10 - 21:34 
When I tried to use the CFECFileDialog class, the GetPathName() function return wrong path name. If I selected fewer files, it works fine. But when I selected a lot of files, GetPathName returns something meaningless. I guess this has something to do with the memory you created to hold the file names. I guess you need to modify all the functions of CFileDialog relating the new memory.
 
Thanks
KD
GeneralCode worked for mememberGreg Niswonger16 Feb '10 - 2:00 
Nice quick solution to a problem. (VS 6.0) Thanks.
(Hopefully, a Windows 7 solution will appear by the time we need it.)
GeneralWindows 7memberGalo Vinueza S.6 Apr '09 - 19:17 
If anyone still checking this posts...
 
The are two little problems with windows 7, have anyone notice? is in the
void OnFileNameChange();

method, it seems that it likes more when
 
UINT nfiles = CommDlg_OpenSave_GetSpec(GetParent()->m_hWnd, &dummy_buffer, 1);
 
it's replaced by
 
 UINT nfiles = CommDlg_OpenSave_GetSpec(m_hWnd, &dummy_buffer, 1);
 
with the first one, the original, crashes with an Access violation.
 
Galo.
 
printf("Error: No keyboard found!");
printf("Press any key to continue");

GeneralRe: Windows 7memberaputic3 Aug '09 - 9:20 
Thanks!
Today I'm trying to compile mu VS 2005 app in VS 2008 and hit this problem
NewsRe: Windows 7 BugmemberANIL KUMAR SHARMA (INDIA)15 Sep '09 - 22:19 
There was a major bug in Windows 7 that breaks the functionality that was working till Vista. Its OnPaint when overridden and folder having . "dot" in it are filtered will cause the whole CFileDialog listview to be empty. For more details please look into https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=489672
 
[AKS]

GeneralRe: Windows 7 [modified]memberPJ Arends3 Oct '09 - 13:48 
Potential fix:
 
Add this code copied from http://msdn.microsoft.com/en-us/library/ms725491(VS.85).aspx[^]:
 
// IsWin7_or_Later is a fix for a windows7 bug reported at:
// http://www.codeproject.com/KB/dialog/pja_multiselect.aspx?msg=2995258#xx2995258xx

BOOL IsWin7_or_Later()
{
   OSVERSIONINFOEX osvi;
   DWORDLONG dwlConditionMask = 0;
   int op = VER_GREATER_EQUAL;
 
   // Initialize the OSVERSIONINFOEX structure.

   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
 
   // Windows 7 is actually version 6.1  WTF!
   osvi.dwMajorVersion = 6;
   osvi.dwMinorVersion = 1;
   osvi.wServicePackMajor = 0;
   osvi.wServicePackMinor = 0;
 
   // Initialize the condition mask.

   VER_SET_CONDITION( dwlConditionMask, VER_MAJORVERSION, op );
   VER_SET_CONDITION( dwlConditionMask, VER_MINORVERSION, op );
   VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMAJOR, op );
   VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMINOR, op );
 
   // Perform the test.

   return VerifyVersionInfo(
      &osvi, 
      VER_MAJORVERSION | VER_MINORVERSION | 
      VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
      dwlConditionMask);
}
 
Then add this line to the top of the OnFileNameChange() method:
    HWND hWnd = IsWin7_or_Later() ? m_hWnd : GetParent()->m_hWnd;
 
Then change the four instances of GetParent()->m_hWnd to hWnd.
 
The code should then work for all versions of windows. (hopefully Sigh | :sigh: )
 

You may be right
I may be crazy
-- Billy Joel --

 
Within you lies the power for good - Use it!
modified on Wednesday, November 11, 2009 3:30 PM

GeneralRe: Windows 7memberBernd Heusing11 Nov '09 - 4:22 
Unfortunately this code change does not fix the Windows7 problem.
First of all the version number of Windows7 is 6.1 and not 7.0. OMG | :OMG:
The second problem is, that the call of CommDlg_OpenSave_GetSpec always returns 0 with hWnd. So the change does prevent a crash, but the dialog will not work properly, because long strings (>260 characters) will be cut.Confused | :confused:
GeneralRe: Windows 7membermykel8 Jan '10 - 7:28 
Hi,
 
I ran into the same problem and did some debugging. If you compare the structure of the file dialog on Windows XP and 7 using Spy++ it is obvious - the structure of the file dialog changed completely. The new file dialog implementation uses some multi-threaded worker window implementation.
 
When setting a breakpoint in void OnFileNameChange() you will see that we stopped on a top-level window/thread called "WorkerW" which is not contained in the file dialog control hierarchy. Therefore calling GetParent() returns NULL, which is to be expected, because there is no parent.
 
See the following screenshots for details:
* VistaStyle = TRUE[^]
* VistaStyle = FALSE[^]
 
The solution seems to be getting the correct window handle (i.e. the window handle of the file dialog containing all the controls) in void OnFileNameChange() but up to now I see no possibility how to do so. My workaround is to disable the new Vista-style file dialog until somebody has a working solution for the problem. To do so I just changed the constructor of CFECFileDialog in the following way:
 
[FECFileDialog.h]
CFECFileDialog(BOOL bOpenFileDialog, // TRUE for FileOpen, FALSE for FileSaveAs
        LPCTSTR lpszDefExt = NULL,
        LPCTSTR lpszFileName = NULL,
        DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
        LPCTSTR lpszFilter = NULL,
        CWnd* pParentWnd = NULL,
        DWORD dwSize = 0,
        BOOL bVistaStyle = TRUE);
[FECFileDialog.cpp]
CFECFileDialog::CFECFileDialog(BOOL bOpenFileDialog, LPCTSTR lpszDefExt, LPCTSTR lpszFileName,
                               DWORD dwFlags, LPCTSTR lpszFilter, CWnd* pParentWnd, DWORD dwSize, BOOL bVistaStyle) :
CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd, dwSize, bVistaStyle)
{
    Files = NULL;
    Folder = NULL;
    bParsed = FALSE;
}
And then wherever using CFECFileDialog call it with dwSize = 0 and bVistaStyle = FALSE.
 
Finally, some links to background info and threads about the problem. Perhaps somebody will shed some light on us sometime... Frown | :(
* Tutorial: Adding Controls to CFileDialog in Vista[^]
* Multiple File Selection Bug in Windows7[^]
* Problem with Multiple File Selection Dialog in Windows7[^]
 
cheers,
Michael
 
OMM: "Let us be thankful we have an occupation to fill. Work hard, increase production, prevent accidents and be happy."

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 24 Nov 2002
Article Copyright 2002 by PJ Arends
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid