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;
UINT nfiles = CommDlg_OpenSave_GetSpec(GetParent()->m_hWnd,
&dummy_buffer, 1);
UINT nfolder = CommDlg_OpenSave_GetFolderPath(GetParent()->m_hWnd,
&dummy_buffer, 1);
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 && 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 :)