Programming the OS/2 File Dialog in C++

Written by Stephane Bessette

Introduction
The purpose of a file dialog is to select files to open or the filename (and path) in which to store a document. From a user's standpoint, this type of dialog is fairly easy to use: select a drive, select a directory, select or type in a filename, and then click on Ok. However, the situation of a C programmer is not as rosy; there's quite a lot of repetitious work to undertake, and a few details to take into consideration. So a C++ approach will be taken in order to greatly simplify the use of the file dialogs.

First There Was C
The API function that handles the file dialog is WinFileDlg: WinFileDlg( HWND hwndP,       (1)             HWND hwndO,        (2)             PFILEDLG pfild)    (3)
 * 1) handle to the parent window
 * 2) handle to the owner window
 * 3) pointer to a FILEDLG structure

The FILEDLG structure contains many members. Some define the behavior of the dialog. For instance, pszTitle sets the text on the titlebar, pszOKButton sets the text of the OK button ("Open" or "Save as"), and fl, which can contain several flags. FDS_OPEN_DIALOG specifies a file open dialog, and FDS_MULTIPLESEL specifies that more than one file may be selected. And then there's FDS_SAVEAS_DIALOG to specify a file save as dialog, and FDS_ENABLEFILELB to allow the user to select a filename from the files listbox. Finally, the szFullName member is used to select the initial drive, the initial directory, and the initial filename or mask.

Other members of the FILEDLG structure are used to return information from the file dialog. For example, lReturn contains the ID of the button pressed (DID_OK or DID_CANCEL), szFullFile contains the filename in the entry field, papszFQFilename contains an array of selected filenames (when more than one can be selected), and ulFQFCount contains the number of selected filenames. One word of caution is in order when ulFQFCount reports that one file has selected. The selected file could be only in the entry field, in which case papszFQFilename would be NULL. Or the selected file could be from the listbox, in which case both the first (and only) element of papszFQFilename and szFullName will contain the same file selection.

The first step in the creation of a file dialog, is to initialize the FILEDLG structure: memset( &FileDialog, 0, sizeof( FILEDLG ) );  (1) FileDialog.cbSize = sizeof( FILEDLG );        (2)
 * 1) Set all members to zero/NULL
 * 2) Set cbSize to the size of the structure

Then we add in the necessary customizations, such as specifying whether this is a file open or a file save as dialog.

And now we're ready to launch the dialog with WinFileDlg. If the user presses the OK button, we have to retrieve the information. If only one filename was selected, we simply take it from the szFullFile member. This is a string containing the fully qualified name of the file: drive, directory, filename, and extension. But if more than one filenames were selected, then we have to retrieve them from the papszFQFilename member. This array of filenames also contains fully qualified names.

This wasn't too bad. But there are still some loose ends to take care of. What if during an open operation the user types in the name of a file that does not exist? And what if during a save as operation the user types in or selects an already existing filename? This requires additional code. And every time you'd implement file open or file save as dialogs, you'd have to retype all this code. But if you're lazy (as I am), you'll save yourself some typing and copy/paste those lines of code (yuck!!). Or you could create functions containing this code and then simply call these functions. Well that's one of the aspects of C++.

And Then There Was C++
I've written four classes to encapsulate the C code to handle a file dialog. I believe that the code is easy to understand, so I'll only write about the use of the classes. I've put everything in a single .hpp file so that it could be placed in an include directory and then not have to link additional files to a project (my laziness shows again).

The first class, Class_FileDialog, provides some actions that are common to the three other classes: setting some variables (the FILEDLG structure), functions to activate and retrieve information from the file dialogs, and a function to change the text on the titlebar of the file dialogs. This class is abstract: you cannot create instances from it.

Class_FileOpen is the first functional class. As you may guess from its name, it creates a file open dialog. Its constructor has an optional parameter. When specified, it will be parsed so that the specified drive, directory, and filename or mask will be used in the file open dialog. If it is not specified, then the dialog will display all the files in the current drive and the current directory. (The constructor of the three functional classes behave in the same way.) Here's how to create an instance that will display all the text files from c:\mmos2: Class_FileOpen Dlg( "c:\\mmos2\\*.txt" );

Once an instance has been created, and all the modifications have been completed (such as changing the text on the titlebar), the file open dialog can be launched: // char szFilename[CCHMAXPATH]          (1) if( Dlg.Do( szFilename, FALSE ) )       (2) // sz now contains the specified filename
 * 1) Previously defined and contains the name of the current file
 * 2) Retrieve a selection

First, a variable to hold the selected file must be created. It would hold the filename of the current work. Then the Do function would be called to obtain a single selection from the user. The function returns TRUE if the user pressed the OK button, and szFilename will now contain the selected filename. If the user pressed the CANCEL button or closed the dialog, the function returns FALSE and szFilename will be unchanged.

The second parameter, which is optional and defaults to TRUE, specifies whether the selected filename must exist. When set to TRUE, the existence of the file is verified. If it does not exist, the user is warned and offered the option of creating the file. If the user answers YES, then the file is created, the function returns TRUE and the selected filename in szFilename. If the user answers NO, then the function returns FALSE and szFilename is unchanged.

As you can see, only three lines of codes were required to use a file dialog that retrieved a filename. Not bad. And it also verified the existence of that selected file. Nice. Let's move ahead with the second functional class.

Class_FileOpenMultiple retrieves multiple selections. It uses a lot of the functionality of Class_FileOpen (reuse of existing code). Some code has been added so that filenames can be retrieved by the same means whether papszFQFilename is NULL or if it contains an array of filenames. But this is all hidden from the programmers (users of the class). Here's how the code to create an instance and retrieve all the selected files looks: Class_FileOpenMultiple Dlg( "*.txt" );         (1) PAPSZ psz;                                     (2) int count = Dlg.Do( psz );                     (3) for( int n = 0; n < count; n++ )               (4) // *psz[n]                                (5)
 * 1) Display only the files with the extension .txt from the current drive and directory
 * 2) Create a temporary pointer to an array of filenames
 * 3) Retrieve the number of selected filenames
 * 4) Process each selected filenames
 * 5) *psz[n] is the fully qualified name of one file (a char*)

As with Class_FileOpen, the Do function takes two parameters: a pointer to an array of strings to hold the selected filenames and whether the existence of the files should be verified. One item worth noting is that only the existence of the file typed into the entry field will be verified, not all the files selected from the listbox. But this is not a problem since the files displayed in the listbox do exist: they would not be displayed there if they did not exist! Also, please note that only five lines of code need to be written to use this type of dialog. Quite nice.

The third functional class implements a file save as dialog: Class_FileSaveAs. Let's look at it: // char szFilename[CCHMAXPATH]          (1) Class_FileSaveAs( szFilename );         (2) if( Dlg.Do( szFilename ) )              (3) {     // save the file }
 * 1) Previously defined and contains the name of the current file
 * 2) Display the current filename in its drive and directory
 * 3) Retrieve a selection

The Do function returns TRUE if a filename has been selected or typed in the entry field, and szFilename will then contain that filename. Again, the Do function has an optional parameter. If the default is used, TRUE, then the function will verify that the specified file does not exist. If it does, then the user will be asked whether it should be overwritten. If the user answers YES, then the file is overwritten the function returns TRUE and szFilename contains the selected file. If the user answers NO, then the file will not be overwritten, the function returns FALSE, and szFilename is unchanged. These three lines are sufficient to retrieve a new filename, and add some error-checking. Very nice.

Although these classes are adequate for most purposes, they lack support for extended attributes. But since I haven't touched on those yet, this will have to wait. Also, it should be possible to provide support for a custom dialog procedure. Again, that's put on hold until such a need arises.

Tip
If you get an error message to the effect that a constructor cannot execute, then you need to enclose that portion of the code within braces: case ID_MENU_FILE_OPEN: {     Class_FileOpen Dlg( "c:\\mmos2\\*.txt" ); if( Dlg.Do( szFilename, FALSE ) ) // perform the read action to open the file break; }